summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WORKSPACE2
-rw-r--r--lib/BUILD1
-rw-r--r--org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java3
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java8
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java9
-rw-r--r--org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs30
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java57
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java9
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java46
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java20
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java139
-rw-r--r--org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java99
-rw-r--r--org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java6
-rw-r--r--org.eclipse.jgit.lfs.server/.settings/.api_filters2
-rw-r--r--org.eclipse.jgit.packaging/pom.xml38
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java22
-rw-r--r--org.eclipse.jgit.test/BUILD3
-rw-r--r--org.eclipse.jgit.test/META-INF/MANIFEST.MF11
-rw-r--r--org.eclipse.jgit.test/pom.xml7
-rw-r--r--org.eclipse.jgit.test/tests.bzl24
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/log4j.properties5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java191
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java22
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java24
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java28
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java15
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java7
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java14
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java200
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java7
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java20
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java24
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java10
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java12
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java12
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java31
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java113
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java47
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java119
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java13
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java22
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java109
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java19
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java135
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java137
-rw-r--r--org.eclipse.jgit/.settings/.api_filters134
-rw-r--r--org.eclipse.jgit/pom.xml1
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java79
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java244
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java45
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java67
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java604
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java253
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java132
-rw-r--r--pom.xml45
78 files changed, 3023 insertions, 720 deletions
diff --git a/WORKSPACE b/WORKSPACE
index c9ffb73160..f4440a2153 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -15,7 +15,7 @@ versions.check(minimum_bazel_version = "0.19.0")
load("//tools:bazlets.bzl", "load_bazlets")
-load_bazlets(commit = "3afbeab55ece585dbfc7a980bf7214b24ddbbe86")
+load_bazlets(commit = "8528a0df69dadf6311d8d3f81c1b693afda8bcf1")
load(
"@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl",
diff --git a/lib/BUILD b/lib/BUILD
index d89ebab265..ad72a62776 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -12,6 +12,7 @@ java_library(
visibility = [
"//org.eclipse.jgit.archive:__pkg__",
"//org.eclipse.jgit.pgm.test:__pkg__",
+ "//org.eclipse.jgit.test:__pkg__",
],
exports = ["@commons-compress//jar"],
)
diff --git a/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java b/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
index 3ce0663762..8043d2b183 100644
--- a/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
+++ b/org.eclipse.jgit.ant.test/src/org/eclipse/jgit/ant/tasks/GitCloneTaskTest.java
@@ -65,12 +65,13 @@ public class GitCloneTaskTest extends LocalDiskRepositoryTestCase {
@Before
public void before() throws IOException {
+ dest = createTempFile();
+ FS.getFileStoreAttributes(dest.toPath().getParent());
project = new Project();
project.init();
enableLogging();
project.addTaskDefinition("git-clone", GitCloneTask.class);
task = (GitCloneTask) project.createTask("git-clone");
- dest = createTempFile();
task.setDest(dest);
}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
index 05510a05b0..946fb15a3d 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
@@ -58,12 +58,14 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.FS;
/**
* Dumps a file over HTTP GET (or its information via HEAD).
@@ -76,7 +78,7 @@ final class FileSender {
private final RandomAccessFile source;
- private final long lastModified;
+ private final Instant lastModified;
private final long fileLen;
@@ -89,7 +91,7 @@ final class FileSender {
this.source = new RandomAccessFile(path, "r");
try {
- this.lastModified = path.lastModified();
+ this.lastModified = FS.DETECTED.lastModifiedInstant(path);
this.fileLen = source.getChannel().size();
this.end = fileLen;
} catch (IOException e) {
@@ -114,7 +116,7 @@ final class FileSender {
}
}
- long getLastModified() {
+ Instant getLastModified() {
return lastModified;
}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
index 62f075c73c..5a27be6430 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
@@ -54,6 +54,7 @@ import static org.eclipse.jgit.util.HttpSupport.HDR_LAST_MODIFIED;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.time.Instant;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -76,7 +77,9 @@ abstract class ObjectFileServlet extends HttpServlet {
@Override
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 @@ abstract class ObjectFileServlet extends HttpServlet {
try {
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);
if (etag != null && etag.equals(ifNoneMatch)) {
diff --git a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
index 2ca78ff2d0..b6750293dc 100644
--- a/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit.junit/.settings/org.eclipse.jdt.core.prefs
@@ -1,10 +1,13 @@
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.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault
+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.methodParameters=do not generate
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.sourceFile=generate
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.assertIdentifier=error
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.missingEnumCaseDespiteDefault=disabled
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.missingJavadocCommentsVisibility=protected
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.nonExternalizedStringLiteral=ignore
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.nullReference=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.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
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.rawTypeReference=ignore
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.syntacticNullAnalysisForFields=disabled
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.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning
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.unnecessaryTypeCheck=error
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.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index 4b2eadf418..838537f3ae 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -51,6 +51,8 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -126,6 +128,10 @@ public abstract class LocalDiskRepositoryTestCase {
if (!tmp.delete() || !tmp.mkdir())
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.userGitConfig = new FileBasedConfig(new File(tmp,
"usergitconfig"), FS.DETECTED);
@@ -232,35 +238,30 @@ public abstract class LocalDiskRepositoryTestCase {
private static boolean recursiveDelete(final File dir,
boolean silent, boolean failOnError) {
assert !(silent && failOnError);
- if (!dir.exists())
- return silent;
- final File[] ls = dir.listFiles();
- if (ls != null)
- 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;
+ int options = FileUtils.RECURSIVE | FileUtils.RETRY
+ | FileUtils.SKIP_MISSING;
+ if (silent) {
+ options |= FileUtils.IGNORE_ERRORS;
}
- 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 msg = severity + ": Failed to delete " + e;
- if (failOnError)
+ String msg = severity + ": Failed to delete " + f;
+ if (failOnError) {
fail(msg);
- else
+ } else {
System.err.println(msg);
+ }
+ cause.printStackTrace(new PrintStream(System.err));
}
/** Constant <code>MOD_TIME=1</code> */
@@ -322,12 +323,13 @@ public abstract class LocalDiskRepositoryTestCase {
throws IllegalStateException, IOException {
DirCache dc = repo.readDirCache();
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
if (0 != (includedOptions & MOD_TIME)) {
- for (int i=0; i<dc.getEntryCount(); ++i)
- timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified()));
+ for (int i = 0; i < dc.getEntryCount(); ++i) {
+ timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
+ }
}
// iterate again, now produce the result string
@@ -339,7 +341,8 @@ public abstract class LocalDiskRepositoryTestCase {
sb.append(", stage:" + stage);
if (0 != (includedOptions & MOD_TIME)) {
sb.append(", time:t"+
- timeStamps.headSet(Long.valueOf(entry.getLastModified())).size());
+ timeStamps.headSet(entry.getLastModifiedInstant())
+ .size());
}
if (0 != (includedOptions & SMUDGE))
if (entry.isSmudged())
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
index 08220ce245..94df554aec 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java
@@ -56,4 +56,13 @@ public @interface Repeat {
* Number of repetitions
*/
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;
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
index 8165738ed8..8636f2a0aa 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java
@@ -81,9 +81,31 @@ public class RepeatRule implements TestRule {
private static Logger LOG = Logger
.getLogger(RepeatRule.class.getName());
+ /**
+ * Exception thrown if repeated execution of a test annotated with
+ * {@code @Repeat} failed.
+ */
public static class RepeatedTestException extends RuntimeException {
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) {
super(message, cause);
}
@@ -93,28 +115,45 @@ public class RepeatRule implements TestRule {
private final int repetitions;
+ private boolean abortOnFailure;
+
private final Statement statement;
- private RepeatStatement(int repetitions, Statement statement) {
+ private RepeatStatement(int repetitions, boolean abortOnFailure,
+ Statement statement) {
this.repetitions = repetitions;
+ this.abortOnFailure = abortOnFailure;
this.statement = statement;
}
@Override
public void evaluate() throws Throwable {
+ int failures = 0;
for (int i = 0; i < repetitions; i++) {
try {
statement.evaluate();
} catch (Throwable e) {
+ failures += 1;
RepeatedTestException ex = new RepeatedTestException(
MessageFormat.format(
"Repeated test failed when run for the {0}. time",
Integer.valueOf(i + 1)),
e);
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 class RepeatRule implements TestRule {
Repeat repeat = description.getAnnotation(Repeat.class);
if (repeat != null) {
int n = repeat.n();
- result = new RepeatStatement(n, statement);
+ boolean abortOnFailure = repeat.abortOnFailure();
+ result = new RepeatStatement(n, abortOnFailure, statement);
}
return result;
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
index 987f923b0e..5aacbbadec 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
@@ -57,7 +57,9 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -284,7 +286,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
dce = new DirCacheEntry(treeItr.getEntryPathString());
dce.setFileMode(treeItr.getEntryFileMode());
- dce.setLastModified(treeItr.getEntryLastModified());
+ dce.setLastModified(treeItr.getEntryLastModifiedInstant());
dce.setLength((int) len);
try (FileInputStream in = new FileInputStream(
treeItr.getEntryFile())) {
@@ -361,7 +363,8 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
* @throws InterruptedException
* @throws IOException
*/
- public static long fsTick(File lastFile) throws InterruptedException,
+ public static Instant fsTick(File lastFile)
+ throws InterruptedException,
IOException {
File tmp;
FS fs = FS.DETECTED;
@@ -375,15 +378,16 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
tmp = File.createTempFile("fsTickTmpFile", null,
lastFile.getParentFile());
}
- long res = FS.getFsTimerResolution(tmp.toPath()).toMillis();
+ long res = FS.getFileStoreAttributes(tmp.toPath())
+ .getFsTimestampResolution().toNanos();
long sleepTime = res / 10;
try {
- long startTime = fs.lastModified(lastFile);
- long actTime = fs.lastModified(tmp);
- while (actTime <= startTime) {
- Thread.sleep(sleepTime);
+ Instant startTime = fs.lastModifiedInstant(lastFile);
+ Instant actTime = fs.lastModifiedInstant(tmp);
+ while (actTime.compareTo(startTime) <= 0) {
+ TimeUnit.NANOSECONDS.sleep(sleepTime);
FileUtils.touch(tmp.toPath());
- actTime = fs.lastModified(tmp);
+ actTime = fs.lastModifiedInstant(tmp);
}
return actTime;
} finally {
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index 55a7766f60..02ffe4f409 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -1076,6 +1076,14 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
parents.add(prior.create());
}
+ /**
+ * set parent commit
+ *
+ * @param p
+ * parent commit
+ * @return this commit builder
+ * @throws Exception
+ */
public CommitBuilder parent(RevCommit p) throws Exception {
if (parents.isEmpty()) {
DirCacheBuilder b = tree.builder();
@@ -1088,29 +1096,71 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
return this;
}
+ /**
+ * Get parent commits
+ *
+ * @return parent commits
+ */
public List<RevCommit> parents() {
return Collections.unmodifiableList(parents);
}
+ /**
+ * Remove parent commits
+ *
+ * @return this commit builder
+ */
public CommitBuilder noParents() {
parents.clear();
return this;
}
+ /**
+ * Remove files
+ *
+ * @return this commit builder
+ */
public CommitBuilder noFiles() {
tree.clear();
return this;
}
+ /**
+ * Set top level tree
+ *
+ * @param treeId
+ * the top level tree
+ * @return this commit builder
+ */
public CommitBuilder setTopLevelTree(ObjectId treeId) {
topLevelTree = treeId;
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 {
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)
throws Exception {
return edit(new PathEdit(path) {
@@ -1122,6 +1172,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
});
}
+ /**
+ * Edit the index
+ *
+ * @param edit
+ * the index record update
+ * @return this commit builder
+ */
public CommitBuilder edit(PathEdit edit) {
DirCacheEditor e = tree.editor();
e.add(edit);
@@ -1129,6 +1186,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
return this;
}
+ /**
+ * Remove a file
+ *
+ * @param path
+ * path of the file
+ * @return this commit builder
+ */
public CommitBuilder rm(String path) {
DirCacheEditor e = tree.editor();
e.add(new DeletePath(path));
@@ -1137,49 +1201,111 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
return this;
}
+ /**
+ * Set commit message
+ *
+ * @param m
+ * the message
+ * @return this commit builder
+ */
public CommitBuilder message(String m) {
message = m;
return this;
}
+ /**
+ * Get the commit message
+ *
+ * @return the commit message
+ */
public String message() {
return message;
}
+ /**
+ * Tick the clock
+ *
+ * @param secs
+ * number of seconds
+ * @return this commit builder
+ */
public CommitBuilder tick(int secs) {
tick = secs;
return this;
}
+ /**
+ * Set author and committer identity
+ *
+ * @param ident
+ * identity to set
+ * @return this commit builder
+ */
public CommitBuilder ident(PersonIdent ident) {
author = ident;
committer = ident;
return this;
}
+ /**
+ * Set the author identity
+ *
+ * @param a
+ * the author's identity
+ * @return this commit builder
+ */
public CommitBuilder author(PersonIdent a) {
author = a;
return this;
}
+ /**
+ * Get the author identity
+ *
+ * @return the author identity
+ */
public PersonIdent author() {
return author;
}
+ /**
+ * Set the committer identity
+ *
+ * @param c
+ * the committer identity
+ * @return this commit builder
+ */
public CommitBuilder committer(PersonIdent c) {
committer = c;
return this;
}
+ /**
+ * Get the committer identity
+ *
+ * @return the committer identity
+ */
public PersonIdent committer() {
return committer;
}
+ /**
+ * Insert changeId
+ *
+ * @return this commit builder
+ */
public CommitBuilder insertChangeId() {
changeId = "";
return this;
}
+ /**
+ * Insert given changeId
+ *
+ * @param c
+ * changeId
+ * @return this commit builder
+ */
public CommitBuilder insertChangeId(String c) {
// Validate, but store as a string so we can use "" as a sentinel.
ObjectId.fromString(c);
@@ -1187,6 +1313,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
return this;
}
+ /**
+ * Create the commit
+ *
+ * @return the new commit
+ * @throws Exception
+ * if creation failed
+ */
public RevCommit create() throws Exception {
if (self == null) {
TestRepository.this.tick(tick);
@@ -1247,6 +1380,12 @@ public class TestRepository<R extends Repository> implements AutoCloseable {
+ cid.getName() + "\n"); //$NON-NLS-1$
}
+ /**
+ * Create child commit builder
+ *
+ * @return child commit builder
+ * @throws Exception
+ */
public CommitBuilder child() throws Exception {
return new CommitBuilder(this);
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
new file mode 100644
index 0000000000..1f8070d919
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
@@ -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);
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
index 10823b8788..ec44da4cac 100644
--- a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/LfsServerTest.java
@@ -82,6 +82,7 @@ import org.eclipse.jgit.lfs.lib.LongObjectId;
import org.eclipse.jgit.lfs.server.LargeFileRepository;
import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
import org.eclipse.jgit.lfs.test.LongObjectIdTestUtils;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
import org.junit.After;
@@ -119,6 +120,11 @@ public abstract class LfsServerTest {
@Before
public void setup() throws Exception {
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();
ServletContextHandler app = server.addContext("/lfs");
dir = Paths.get(tmp.toString(), "lfs");
diff --git a/org.eclipse.jgit.lfs.server/.settings/.api_filters b/org.eclipse.jgit.lfs.server/.settings/.api_filters
index 2a47dd398b..72beb7cdfa 100644
--- a/org.eclipse.jgit.lfs.server/.settings/.api_filters
+++ b/org.eclipse.jgit.lfs.server/.settings/.api_filters
@@ -3,7 +3,7 @@
<resource path="META-INF/MANIFEST.MF">
<filter id="924844039">
<message_arguments>
- <message_argument value="5.3.2"/>
+ <message_argument value="5.3.3"/>
<message_argument value="5.3.0"/>
</message_arguments>
</filter>
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index f4d8c4f8e1..22f47f59df 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -47,10 +47,6 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
- <prerequisites>
- <maven>3.0</maven>
- </prerequisites>
-
<groupId>org.eclipse.jgit</groupId>
<artifactId>jgit.tycho.parent</artifactId>
<version>5.3.3-SNAPSHOT</version>
@@ -232,6 +228,21 @@
<version>${tycho-version}</version>
</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>
<groupId>org.eclipse.tycho.extras</groupId>
<artifactId>tycho-pack200a-plugin</artifactId>
<version>${tycho-extras-version}</version>
@@ -251,6 +262,25 @@
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
</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>
</pluginManagement>
</build>
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
index 7f99d76bda..14a60a3b46 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
@@ -48,8 +48,10 @@ package org.eclipse.jgit.pgm.debug;
import static java.lang.Integer.valueOf;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -67,25 +69,27 @@ class ShowDirCache extends TextBuiltin {
/** {@inheritDoc} */
@Override
protected void run() throws Exception {
- final SimpleDateFormat fmt;
- fmt = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss.SSS"); //$NON-NLS-1$
+ final DateTimeFormatter fmt = DateTimeFormatter
+ .ofPattern("yyyy-MM-dd,HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
+ .withLocale(Locale.getDefault())
+ .withZone(ZoneId.systemDefault());
final DirCache cache = db.readDirCache();
for (int i = 0; i < cache.getEntryCount(); i++) {
final DirCacheEntry ent = cache.getEntry(i);
final FileMode mode = FileMode.fromBits(ent.getRawMode());
final int len = ent.getLength();
- long lastModified = ent.getLastModified();
- final Date mtime = new Date(lastModified);
+ Instant mtime = ent.getLastModifiedInstant();
final int stage = ent.getStage();
outw.print(mode);
outw.format(" %6d", valueOf(len)); //$NON-NLS-1$
outw.print(' ');
- if (millis)
- outw.print(lastModified);
- else
+ if (millis) {
+ outw.print(mtime.toEpochMilli());
+ } else {
outw.print(fmt.format(mtime));
+ }
outw.print(' ');
outw.print(ent.getObjectId().name());
outw.print(' ');
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index 95fb79b22e..2d2f760c26 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -23,7 +23,6 @@ HELPERS = glob(
"revwalk/RevWalkTestCase.java",
"transport/ObjectIdMatcher.java",
"transport/SpiTransport.java",
- "treewalk/FileTreeIteratorWithTimeControl.java",
"treewalk/filter/AlwaysCloneTreeFilter.java",
"test/resources/SampleDataRepositoryTestCase.java",
"util/CPUTimeStopWatch.java",
@@ -37,7 +36,7 @@ DATA = [
RESOURCES = glob(["resources/**"])
-tests(glob(
+tests(tests = glob(
["tst/**/*.java"],
exclude = HELPERS + DATA,
))
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 14eef1351f..5533304913 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -11,10 +11,17 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
com.jcraft.jsch;version="[0.1.54,0.2.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.bouncycastle.util.encoders;version="[1.60.0,2.0.0)",
org.eclipse.jgit.annotations;version="[5.3.3,5.4.0)",
org.eclipse.jgit.api;version="[5.3.3,5.4.0)",
org.eclipse.jgit.api.errors;version="[5.3.3,5.4.0)",
+ org.eclipse.jgit.archive;version="[5.3.3,5.4.0)",
org.eclipse.jgit.attributes;version="[5.3.3,5.4.0)",
org.eclipse.jgit.awtui;version="[5.3.3,5.4.0)",
org.eclipse.jgit.blame;version="[5.3.3,5.4.0)",
@@ -38,6 +45,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
org.eclipse.jgit.internal.transport.parser;version="[5.3.3,5.4.0)",
org.eclipse.jgit.junit;version="[5.3.3,5.4.0)",
org.eclipse.jgit.junit.ssh;version="[5.3.3,5.4.0)",
+ org.eclipse.jgit.junit.time;version="[5.3.3,5.4.0)",
org.eclipse.jgit.lfs;version="[5.3.3,5.4.0)",
org.eclipse.jgit.lib;version="[5.3.3,5.4.0)",
org.eclipse.jgit.merge;version="[5.3.3,5.4.0)",
@@ -70,7 +78,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.stubbing;version="[2.13.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)",
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"
Export-Package: org.eclipse.jgit.transport.ssh;version="5.3.3";x-friends:="org.eclipse.jgit.ssh.apache.test"
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 21c06428f2..9f66d7ed73 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -133,6 +133,12 @@
<artifactId>org.eclipse.jgit.pgm</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.tukaani</groupId>
+ <artifactId>xz</artifactId>
+ <optional>true</optional>
+ </dependency>
</dependencies>
<profiles>
@@ -144,6 +150,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
+ <version>${maven-surefire-plugin-version}</version>
<configuration>
<argLine>@{argLine} -Djgit.test.long=true</argLine>
</configuration>
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index dc2102964f..d2f6d705b6 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -7,15 +7,16 @@ def tests(tests):
for src in tests:
name = src[len("tst/"):len(src) - len(".java")].replace("/", "_")
labels = []
+ timeout = "moderate"
if name.startswith("org_eclipse_jgit_"):
- l = name[len("org.eclipse.jgit_"):]
- if l.startswith("internal_storage_"):
- l = l[len("internal.storage_"):]
- i = l.find("_")
- if i > 0:
- labels.append(l[:i])
+ package = name[len("org.eclipse.jgit_"):]
+ if package.startswith("internal_storage_"):
+ package = package[len("internal.storage_"):]
+ index = package.find("_")
+ if index > 0:
+ labels.append(package[:index])
else:
- labels.append(i)
+ labels.append(index)
if "lib" not in labels:
labels.append("lib")
@@ -53,9 +54,17 @@ def tests(tests):
additional_deps = [
"//lib:mockito",
]
+ if src.endswith("ArchiveCommandTest.java"):
+ additional_deps = [
+ "//lib:commons-compress",
+ "//lib:xz",
+ "//org.eclipse.jgit.archive:jgit-archive",
+ ]
heap_size = "-Xmx256m"
if src.endswith("HugeCommitMessageTest.java"):
heap_size = "-Xmx512m"
+ if src.endswith("EolRepositoryTest.java") or src.endswith("GcCommitSelectionTest.java"):
+ timeout = "long"
junit_tests(
name = name,
@@ -73,4 +82,5 @@ def tests(tests):
],
flaky = flaky,
jvm_flags = [heap_size, "-Dfile.encoding=UTF-8"],
+ timeout = timeout,
)
diff --git a/org.eclipse.jgit.test/tst-rsrc/log4j.properties b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
index 14620ffae4..ee1ac35158 100644
--- a/org.eclipse.jgit.test/tst-rsrc/log4j.properties
+++ b/org.eclipse.jgit.test/tst-rsrc/log4j.properties
@@ -7,3 +7,8 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
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.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
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 3fee51a885..b28e26a639 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -1267,7 +1267,7 @@ public class AddCommandTest extends RepositoryTestCase {
DirCacheEntry entry = new DirCacheEntry(path, stage);
entry.setObjectId(id);
entry.setFileMode(FileMode.REGULAR_FILE);
- entry.setLastModified(file.lastModified());
+ entry.setLastModified(FS.DETECTED.lastModifiedInstant(file));
entry.setLength((int) file.length());
builder.add(entry);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index 1c41018161..0f2e6b8ac6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -47,20 +47,44 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
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.InputStream;
import java.io.OutputStream;
+import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
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.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.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.StringUtils;
import org.junit.After;
import org.junit.Before;
@@ -68,9 +92,14 @@ import org.junit.Test;
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_FILE_CONTENTS = "Unexpected file 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;
@@ -78,25 +107,20 @@ public class ArchiveCommandTest extends RepositoryTestCase {
public void setup() {
format = new MockFormat();
ArchiveCommand.registerFormat(format.SUFFIXES.get(0), format);
+ ArchiveFormats.registerAll();
}
@Override
@After
public void tearDown() {
ArchiveCommand.unregisterFormat(format.SUFFIXES.get(0));
+ ArchiveFormats.unregisterAll();
}
@Test
public void archiveHeadAllFiles() throws IOException, GitAPIException {
try (Git git = new Git(db)) {
- 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();
+ createTestContent(git);
git.archive().setOutputStream(new MockOutputStream())
.setFormat(format.SUFFIXES.get(0))
@@ -191,6 +215,157 @@ public class ArchiveCommandTest extends RepositoryTestCase {
}
}
+ @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
implements ArchiveCommand.Format<MockOutputStream> {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
index 98a8adcc58..1f3527f479 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
@@ -43,6 +43,7 @@
*/
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.R_HEADS;
import static org.hamcrest.CoreMatchers.is;
@@ -60,6 +61,9 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
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.CreateBranchCommand.SetupUpstreamMode;
@@ -75,6 +79,7 @@ import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lfs.BuiltinLFS;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@@ -87,6 +92,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
@@ -376,14 +382,14 @@ public class CheckoutCommandTest extends RepositoryTestCase {
File file = new File(db.getWorkTree(), "Test.txt");
long size = file.length();
- long mTime = file.lastModified() - 5000L;
- assertTrue(file.setLastModified(mTime));
+ Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(),
+ -5000L);
DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS());
DirCacheEntry entry = cache.getEntry("Test.txt");
assertNotNull(entry);
entry.setLength(0);
- entry.setLastModified(0);
+ entry.setLastModified(EPOCH);
cache.write();
assertTrue(cache.commit());
@@ -391,10 +397,12 @@ public class CheckoutCommandTest extends RepositoryTestCase {
entry = cache.getEntry("Test.txt");
assertNotNull(entry);
assertEquals(0, entry.getLength());
- assertEquals(0, entry.getLastModified());
+ assertEquals(EPOCH, entry.getLastModifiedInstant());
- db.getIndexFile().setLastModified(
- db.getIndexFile().lastModified() - 5000);
+ Files.setLastModifiedTime(db.getIndexFile().toPath(),
+ FileTime.from(FS.DETECTED
+ .lastModifiedInstant(db.getIndexFile())
+ .minusMillis(5000L)));
assertNotNull(git.checkout().setName("test").call());
@@ -402,7 +410,7 @@ public class CheckoutCommandTest extends RepositoryTestCase {
entry = cache.getEntry("Test.txt");
assertNotNull(entry);
assertEquals(size, entry.getLength());
- assertEquals(mTime, entry.getLastModified());
+ assertEquals(mTime, entry.getLastModifiedInstant());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index cd96f41a42..e4b7ed7ba7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -65,6 +65,7 @@ import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@@ -319,11 +320,11 @@ public class CommitCommandTest extends RepositoryTestCase {
public void commitUpdatesSmudgedEntries() throws Exception {
try (Git git = new Git(db)) {
File file1 = writeTrashFile("file1.txt", "content1");
- assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
File file2 = writeTrashFile("file2.txt", "content2");
- assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
File file3 = writeTrashFile("file3.txt", "content3");
- assertTrue(file3.setLastModified(file3.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L);
assertNotNull(git.add().addFilepattern("file1.txt")
.addFilepattern("file2.txt").addFilepattern("file3.txt").call());
@@ -354,11 +355,12 @@ public class CommitCommandTest extends RepositoryTestCase {
assertEquals(0, cache.getEntry("file2.txt").getLength());
assertEquals(0, cache.getEntry("file3.txt").getLength());
- long indexTime = db.getIndexFile().lastModified();
- db.getIndexFile().setLastModified(indexTime - 5000);
+ TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
+ -5000L);
write(file1, "content4");
- assertTrue(file1.setLastModified(file1.lastModified() + 2500));
+
+ TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L);
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
.call());
@@ -376,9 +378,9 @@ public class CommitCommandTest extends RepositoryTestCase {
public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception {
try (Git git = new Git(db)) {
File file1 = writeTrashFile("file1.txt", "content1");
- assertTrue(file1.setLastModified(file1.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L);
File file2 = writeTrashFile("file2.txt", "content2");
- assertTrue(file2.setLastModified(file2.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L);
assertNotNull(git.add().addFilepattern("file1.txt")
.addFilepattern("file2.txt").call());
@@ -407,11 +409,11 @@ public class CommitCommandTest extends RepositoryTestCase {
assertEquals(0, cache.getEntry("file1.txt").getLength());
assertEquals(0, cache.getEntry("file2.txt").getLength());
- long indexTime = db.getIndexFile().lastModified();
- db.getIndexFile().setLastModified(indexTime - 5000);
+ TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(),
+ -5000L);
write(file1, "content5");
- assertTrue(file1.setLastModified(file1.lastModified() + 1000));
+ TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L);
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt")
.call());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
index 43c3a8cf92..3a93839e8c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
@@ -44,7 +44,6 @@ package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -55,6 +54,7 @@ import java.util.List;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -230,7 +230,7 @@ public class DiffCommandTest extends RepositoryTestCase {
@Test
public void testNoOutputStreamSet() throws Exception {
File file = writeTrashFile("test.txt", "a");
- assertTrue(file.setLastModified(file.lastModified() - 5000));
+ TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L);
try (Git git = new Git(db)) {
git.add().addFilepattern(".").call();
write(file, "b");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 9b12011aab..dd7230bdbf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -42,6 +42,7 @@
*/
package org.eclipse.jgit.api;
+import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -52,6 +53,9 @@ import static org.junit.Assert.fail;
import java.io.File;
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.errors.GitAPIException;
@@ -252,13 +256,13 @@ public class ResetCommandTest extends RepositoryTestCase {
public void testMixedResetRetainsSizeAndModifiedTime() throws Exception {
git = new Git(db);
- writeTrashFile("a.txt", "a").setLastModified(
- System.currentTimeMillis() - 60 * 1000);
+ Files.setLastModifiedTime(writeTrashFile("a.txt", "a").toPath(),
+ FileTime.from(Instant.now().minusSeconds(60)));
assertNotNull(git.add().addFilepattern("a.txt").call());
assertNotNull(git.commit().setMessage("a commit").call());
- writeTrashFile("b.txt", "b").setLastModified(
- System.currentTimeMillis() - 60 * 1000);
+ Files.setLastModifiedTime(writeTrashFile("b.txt", "b").toPath(),
+ FileTime.from(Instant.now().minusSeconds(60)));
assertNotNull(git.add().addFilepattern("b.txt").call());
RevCommit commit2 = git.commit().setMessage("b commit").call();
assertNotNull(commit2);
@@ -268,12 +272,12 @@ public class ResetCommandTest extends RepositoryTestCase {
DirCacheEntry aEntry = cache.getEntry("a.txt");
assertNotNull(aEntry);
assertTrue(aEntry.getLength() > 0);
- assertTrue(aEntry.getLastModified() > 0);
+ assertTrue(aEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
DirCacheEntry bEntry = cache.getEntry("b.txt");
assertNotNull(bEntry);
assertTrue(bEntry.getLength() > 0);
- assertTrue(bEntry.getLastModified() > 0);
+ assertTrue(bEntry.getLastModifiedInstant().compareTo(EPOCH) > 0);
assertSameAsHead(git.reset().setMode(ResetType.MIXED)
.setRef(commit2.getName()).call());
@@ -282,13 +286,17 @@ public class ResetCommandTest extends RepositoryTestCase {
DirCacheEntry mixedAEntry = cache.getEntry("a.txt");
assertNotNull(mixedAEntry);
- assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified());
- assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified());
+ assertEquals(aEntry.getLastModifiedInstant(),
+ mixedAEntry.getLastModifiedInstant());
+ assertEquals(aEntry.getLastModifiedInstant(),
+ mixedAEntry.getLastModifiedInstant());
DirCacheEntry mixedBEntry = cache.getEntry("b.txt");
assertNotNull(mixedBEntry);
- assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified());
- assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified());
+ assertEquals(bEntry.getLastModifiedInstant(),
+ mixedBEntry.getLastModifiedInstant());
+ assertEquals(bEntry.getLastModifiedInstant(),
+ mixedBEntry.getLastModifiedInstant());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
index b6291bfca4..50753ae1bd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
@@ -53,6 +53,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
+import java.time.Instant;
import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.events.IndexChangedListener;
@@ -99,7 +100,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
public void testBuildOneFile_FinishWriteCommit() throws Exception {
final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE;
- final long lastModified = 1218123387057L;
+ final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342;
final DirCacheEntry entOrig;
{
@@ -117,7 +118,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage());
- assertEquals(lastModified, entOrig.getLastModified());
+ assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid());
b.add(entOrig);
@@ -139,7 +140,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage());
- assertEquals(lastModified, entOrig.getLastModified());
+ assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid());
}
@@ -149,7 +150,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
public void testBuildOneFile_Commit() throws Exception {
final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE;
- final long lastModified = 1218123387057L;
+ final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342;
final DirCacheEntry entOrig;
{
@@ -167,7 +168,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage());
- assertEquals(lastModified, entOrig.getLastModified());
+ assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid());
b.add(entOrig);
@@ -187,7 +188,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
assertEquals(mode.getBits(), entOrig.getRawMode());
assertEquals(0, entOrig.getStage());
- assertEquals(lastModified, entOrig.getLastModified());
+ assertEquals(lastModified, entOrig.getLastModifiedInstant());
assertEquals(length, entOrig.getLength());
assertFalse(entOrig.isAssumeValid());
}
@@ -204,7 +205,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase {
final String path = "a-file-path";
final FileMode mode = FileMode.REGULAR_FILE;
// "old" date in 2008
- final long lastModified = 1218123387057L;
+ final Instant lastModified = Instant.ofEpochMilli(1218123387057L);
final int length = 1342;
DirCacheEntry entOrig;
boolean receivedEvent = false;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
index 86e2852872..475819dbb7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.dircache;
+import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
@@ -188,7 +189,7 @@ public class DirCacheEntryTest {
e.setAssumeValid(false);
e.setCreationTime(2L);
e.setFileMode(FileMode.EXECUTABLE_FILE);
- e.setLastModified(3L);
+ e.setLastModified(EPOCH.plusMillis(3L));
e.setLength(100L);
e.setObjectId(ObjectId
.fromString("0123456789012345678901234567890123456789"));
@@ -199,7 +200,7 @@ public class DirCacheEntryTest {
f.setAssumeValid(true);
f.setCreationTime(10L);
f.setFileMode(FileMode.SYMLINK);
- f.setLastModified(20L);
+ f.setLastModified(EPOCH.plusMillis(20L));
f.setLength(100000000L);
f.setObjectId(ObjectId
.fromString("1234567890123456789012345678901234567890"));
@@ -212,7 +213,7 @@ public class DirCacheEntryTest {
ObjectId.fromString("1234567890123456789012345678901234567890"),
e.getObjectId());
assertEquals(FileMode.SYMLINK, e.getFileMode());
- assertEquals(20L, e.getLastModified());
+ assertEquals(EPOCH.plusMillis(20L), e.getLastModifiedInstant());
assertEquals(100000000L, e.getLength());
if (keepStage)
assertEquals(DirCacheEntry.STAGE_2, e.getStage());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
index 643daa5c95..6bec056737 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
@@ -56,6 +56,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.time.Instant;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -71,6 +72,7 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.junit.After;
import org.junit.Before;
@@ -235,7 +237,8 @@ public class ConcurrentRepackTest extends RepositoryTestCase {
private static void write(File[] files, PackWriter pw)
throws IOException {
- final long begin = files[0].getParentFile().lastModified();
+ final Instant begin = FS.DETECTED
+ .lastModifiedInstant(files[0].getParentFile());
NullProgressMonitor m = NullProgressMonitor.INSTANCE;
try (OutputStream out = new BufferedOutputStream(
@@ -252,7 +255,8 @@ public class ConcurrentRepackTest extends RepositoryTestCase {
}
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) {
FileUtils.delete(f);
assertFalse(f + " was removed", f.exists());
@@ -260,14 +264,14 @@ public class ConcurrentRepackTest extends RepositoryTestCase {
touch(begin, list[0].getParentFile());
}
- private static void touch(long begin, File dir) {
- while (begin >= dir.lastModified()) {
+ private static void touch(Instant begin, File dir) throws IOException {
+ while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) {
try {
Thread.sleep(25);
} catch (InterruptedException ie) {
//
}
- dir.setLastModified(System.currentTimeMillis());
+ FS.DETECTED.setLastModified(dir.toPath(), Instant.now());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
index 5ebdeb6e8f..6fa35d64b0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
@@ -42,49 +42,68 @@
*/
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.assertTrue;
+import static org.junit.Assert.fail;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
+import java.time.Duration;
+import java.time.Instant;
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.Stats;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
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
public void setUp() throws Exception {
- trash = File.createTempFile("tmp_", "");
- trash.delete();
- assertTrue("mkdir " + trash, trash.mkdir());
+ trash = Files.createTempDirectory("tmp_");
+ // measure timer resolution before the test to avoid time critical tests
+ // are affected by time needed for measurement
+ fsAttrCache = FS
+ .getFileStoreAttributes(trash.getParent());
}
@Before
@After
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) {
- long initialLastModified = f.lastModified();
+ private static void waitNextTick(Path f) throws IOException {
+ Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f);
do {
- f.setLastModified(System.currentTimeMillis());
- } while (f.lastModified() == initialLastModified);
+ FS.DETECTED.setLastModified(f, Instant.now());
+ } while (FS.DETECTED.lastModifiedInstant(f)
+ .equals(initialLastModified));
}
/**
@@ -94,12 +113,12 @@ public class FileSnapshotTest {
*/
@Test
public void testActuallyIsModifiedTrivial() throws Exception {
- File f1 = createFile("simple");
- waitNextSec(f1);
- FileSnapshot save = FileSnapshot.save(f1);
+ Path f1 = createFile("simple");
+ waitNextTick(f1);
+ FileSnapshot save = FileSnapshot.save(f1.toFile());
append(f1, (byte) 'x');
- waitNextSec(f1);
- assertTrue(save.isModified(f1));
+ waitNextTick(f1);
+ assertTrue(save.isModified(f1.toFile()));
}
/**
@@ -112,11 +131,17 @@ public class FileSnapshotTest {
*/
@Test
public void testNewFileWithWait() throws Exception {
- File f1 = createFile("newfile");
- waitNextSec(f1);
- FileSnapshot save = FileSnapshot.save(f1);
- Thread.sleep(1500);
- assertTrue(save.isModified(f1));
+ // if filesystem timestamp resolution is high the snapshot won't be
+ // racily clean
+ Assume.assumeTrue(
+ fsAttrCache.getFsTimestampResolution()
+ .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 class FileSnapshotTest {
*/
@Test
public void testNewFileNoWait() throws Exception {
- File f1 = createFile("newfile");
- FileSnapshot save = FileSnapshot.save(f1);
- assertTrue(save.isModified(f1));
+ // if filesystem timestamp resolution is smaller than time needed to
+ // create a file and FileSnapshot the snapshot won't be racily clean
+ 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 class FileSnapshotTest {
@Test
public void testSimulatePackfileReplacement() throws Exception {
Assume.assumeFalse(SystemReader.getInstance().isWindows());
- File f1 = createFile("file"); // inode y
- File f2 = createFile("fool"); // Guarantees new inode x
+ Path f1 = createFile("file"); // inode y
+ Path f2 = createFile("fool"); // Guarantees new inode x
// wait on f2 since this method resets lastModified of the file
// and leaves lastModified of f1 untouched
- waitNextSec(f2);
- waitNextSec(f2);
- FileTime timestamp = Files.getLastModifiedTime(f1.toPath());
- FileSnapshot save = FileSnapshot.save(f1);
- Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x
+ waitNextTick(f2);
+ waitNextTick(f2);
+ FileTime timestamp = Files.getLastModifiedTime(f1);
+ FileSnapshot save = FileSnapshot.save(f1.toFile());
+ Files.move(f2, f1, // Now "file" is inode x
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE);
- Files.setLastModifiedTime(f1.toPath(), timestamp);
- assertTrue(save.isModified(f1));
+ Files.setLastModifiedTime(f1, timestamp);
+ assertTrue(save.isModified(f1.toFile()));
assertTrue("unexpected change of fileKey", save.wasFileKeyChanged());
assertFalse("unexpected size change", save.wasSizeChanged());
assertFalse("unexpected lastModified change",
@@ -171,24 +220,83 @@ public class FileSnapshotTest {
*/
@Test
public void testFileSizeChanged() throws Exception {
- File f = createFile("file");
- FileTime timestamp = Files.getLastModifiedTime(f.toPath());
- FileSnapshot save = FileSnapshot.save(f);
+ Path f = createFile("file");
+ FileTime timestamp = Files.getLastModifiedTime(f);
+ FileSnapshot save = FileSnapshot.save(f.toFile());
append(f, (byte) 'x');
- Files.setLastModifiedTime(f.toPath(), timestamp);
- assertTrue(save.isModified(f));
+ Files.setLastModifiedTime(f, timestamp);
+ assertTrue(save.isModified(f.toFile()));
assertTrue(save.wasSizeChanged());
}
- private File createFile(String string) throws IOException {
- trash.mkdirs();
- File f = File.createTempFile(string, "tdat", trash);
- files.add(f);
- return f;
+ @Test
+ public void fileSnapshotEquals() throws Exception {
+ // 0 sized FileSnapshot.
+ FileSnapshot fs1 = FileSnapshot.MISSING_FILE;
+ // UNKNOWN_SIZE FileSnapshot.
+ FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant());
+
+ assertTrue(fs1.equals(fs2));
+ assertTrue(fs2.equals(fs1));
+ }
+
+ @SuppressWarnings("boxing")
+ @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(File f, byte b) throws IOException {
- try (FileOutputStream os = new FileOutputStream(f, true)) {
+ private static void append(Path f, byte b) throws IOException {
+ try (OutputStream os = Files.newOutputStream(f,
+ StandardOpenOption.APPEND)) {
os.write(b);
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
index d16998db55..eaa245b4eb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -147,9 +147,10 @@ public abstract class GcTestCase extends LocalDiskRepositoryTestCase {
return tip;
}
- protected long lastModified(AnyObjectId objectId) throws IOException {
- return repo.getFS().lastModified(
- repo.getObjectDatabase().fileFor(objectId));
+ protected long lastModified(AnyObjectId objectId) {
+ return repo.getFS()
+ .lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId))
+ .toEpochMilli();
}
protected static void fsTick() throws InterruptedException, IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index a3a302d81a..9d47c7e3bc 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -66,6 +66,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
@@ -158,13 +159,14 @@ public class ObjectDirectoryTest extends RepositoryTestCase {
// To deal with racy-git situations JGit's Filesnapshot class will
// report a file/folder potentially dirty if
- // cachedLastReadTime-cachedLastModificationTime < 2500ms. This
- // causes JGit to always rescan a file after modification. But:
- // this was true only if the difference between current system time
- // and cachedLastModification time was less than 2500ms. If the
- // modification is more than 2500ms ago we may have reported a
- // file/folder to be clean although it has not been rescanned. A
- // Bug. To show the bug we sleep for more than 2500ms
+ // cachedLastReadTime-cachedLastModificationTime < filesystem
+ // timestamp resolution. This causes JGit to always rescan a file
+ // after modification. But: this was true only if the difference
+ // between current system time and cachedLastModification time was
+ // less than 2500ms. If the modification is more than 2500ms ago we
+ // may have reported a file/folder to be clean although it has not
+ // been rescanned. A bug. To show the bug we sleep for more than
+ // 2500ms
Thread.sleep(2600);
File[] ret = packsFolder.listFiles(new FilenameFilter() {
@@ -174,7 +176,9 @@ public class ObjectDirectoryTest extends RepositoryTestCase {
}
});
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
assertFalse(receivingDB.getObjectDatabase().has(unknownID));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
index a1433e9fe5..d5bc61a692 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
@@ -59,6 +59,7 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
//import java.nio.file.attribute.BasicFileAttributes;
import java.text.ParseException;
+import java.time.Instant;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
@@ -80,6 +81,7 @@ import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.util.FS;
import org.junit.Test;
public class PackFileSnapshotTest extends RepositoryTestCase {
@@ -188,7 +190,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
AnyObjectId chk1 = pf.getPackChecksum();
String name = pf.getPackName();
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
// this test is done before the filesystem timer ticks again.
@@ -198,15 +201,15 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
// content and checksum are different since compression level differs
AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
.getPackChecksum();
- long m2 = packFilePath.toFile().lastModified();
- assumeFalse(m2 == m1);
+ Instant m2 = fs.lastModifiedInstant(packFilePath);
+ assumeFalse(m2.equals(m1));
// Repack to create packfile with same name, length. Lastmodified is
// equal to the previous one because we are in the same filesystem timer
// slot. Content and its checksum are different
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
.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
// available packs. If we would ask for a known objectid then JGit would
@@ -214,7 +217,7 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
db.getObjectDatabase().has(unknownID);
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum());
- assumeTrue(m3 == m2);
+ assumeTrue(m3.equals(m2));
}
// Try repacking so fast that we get two new packs which differ only in
@@ -253,7 +256,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
// Repack to create third packfile
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
.getPackChecksum();
- long m3 = packFilePath.toFile().lastModified();
+ FS fs = db.getFS();
+ Instant m3 = fs.lastModifiedInstant(packFilePath);
db.getObjectDatabase().has(unknownID);
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum());
@@ -265,8 +269,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
// Copy copy2 to packfile data to force modification of packfile without
// changing the packfile's filekey.
copyPack(packFileBasePath, ".copy2", "");
- long m2 = packFilePath.toFile().lastModified();
- assumeFalse(m3 == m2);
+ Instant m2 = fs.lastModifiedInstant(packFilePath);
+ assumeFalse(m3.equals(m2));
db.getObjectDatabase().has(unknownID);
assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks())
@@ -275,8 +279,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
// Copy copy2 to packfile data to force modification of packfile without
// changing the packfile's filekey.
copyPack(packFileBasePath, ".copy1", "");
- long m1 = packFilePath.toFile().lastModified();
- assumeTrue(m2 == m1);
+ Instant m1 = fs.lastModifiedInstant(packFilePath);
+ assumeTrue(m2.equals(m1));
db.getObjectDatabase().has(unknownID);
assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks())
.getPackChecksum());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
index 7b3684c865..2357edf6cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
@@ -60,6 +60,7 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -72,6 +73,7 @@ import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.Repeat;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -80,6 +82,7 @@ import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.util.FS;
import org.junit.Before;
import org.junit.Test;
@@ -671,6 +674,7 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
assertEquals(B, all.get(HEAD).getObjectId());
}
+ @Repeat(n = 100, abortOnFailure = false)
@Test
public void testFindRef_DiscoversModifiedLoose() throws IOException {
Map<String, Ref> all;
@@ -1370,10 +1374,8 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
private void writePackedRefs(String content) throws IOException {
File pr = new File(diskRepo.getDirectory(), "packed-refs");
write(pr, content);
-
- final long now = System.currentTimeMillis();
- final int oneHourAgo = 3600 * 1000;
- pr.setLastModified(now - oneHourAgo);
+ FS fs = diskRepo.getFS();
+ fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600));
}
private void deleteLooseRef(String name) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index c6138495fc..e2887d9053 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -59,6 +59,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.time.Instant;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -82,6 +83,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
import org.junit.Rule;
@@ -790,12 +792,14 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase {
*
* @param name
* 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);
- 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
- path.setLastModified(set);
- assertTrue("time changed", old != path.lastModified());
+ fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set));
+ assertFalse("time changed", old.equals(fs.lastModifiedInstant(path)));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index 21d8d66adf..22dc471552 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -51,8 +51,10 @@ package org.eclipse.jgit.lib;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.DAYS;
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.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.eclipse.jgit.util.FileUtils.pathToString;
import static org.junit.Assert.assertArrayEquals;
@@ -1224,8 +1226,18 @@ public class ConfigTest {
@Test
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(2, parseTime("2ms", MILLISECONDS));
+ assertEquals(2, parseTime("2000microseconds", MILLISECONDS));
assertEquals(200, parseTime("200 milliseconds", MILLISECONDS));
assertEquals(0, parseTime("0s", SECONDS));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
index b9bbbeb9e5..a3634141c3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
@@ -37,8 +37,12 @@
*/
package org.eclipse.jgit.lib;
+import static java.time.Instant.EPOCH;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.time.Instant;
+
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -63,11 +67,11 @@ public class IndexModificationTimesTest extends RepositoryTestCase {
DirCacheEntry entry = dc.getEntry(path);
DirCacheEntry entry2 = dc.getEntry(path);
- assertTrue("last modified shall not be zero!",
- entry.getLastModified() != 0);
+ assertFalse("last modified shall not be the epoch!",
+ entry.getLastModifiedInstant().equals(EPOCH));
- assertTrue("last modified shall not be zero!",
- entry2.getLastModified() != 0);
+ assertFalse("last modified shall not be the epoch!",
+ entry2.getLastModifiedInstant().equals(EPOCH));
writeTrashFile(path, "new content");
git.add().addFilepattern(path).call();
@@ -77,11 +81,11 @@ public class IndexModificationTimesTest extends RepositoryTestCase {
entry = dc.getEntry(path);
entry2 = dc.getEntry(path);
- assertTrue("last modified shall not be zero!",
- entry.getLastModified() != 0);
+ assertFalse("last modified shall not be the epoch!",
+ entry.getLastModifiedInstant().equals(EPOCH));
- assertTrue("last modified shall not be zero!",
- entry2.getLastModified() != 0);
+ assertFalse("last modified shall not be the epoch!",
+ entry2.getLastModifiedInstant().equals(EPOCH));
}
}
@@ -97,7 +101,7 @@ public class IndexModificationTimesTest extends RepositoryTestCase {
DirCache dc = db.readDirCache();
DirCacheEntry entry = dc.getEntry(path);
- long masterLastMod = entry.getLastModified();
+ Instant masterLastMod = entry.getLastModifiedInstant();
git.checkout().setCreateBranch(true).setName("side").call();
@@ -110,7 +114,7 @@ public class IndexModificationTimesTest extends RepositoryTestCase {
dc = db.readDirCache();
entry = dc.getEntry(path);
- long sideLastMode = entry.getLastModified();
+ Instant sideLastMod = entry.getLastModifiedInstant();
Thread.sleep(2000);
@@ -120,9 +124,10 @@ public class IndexModificationTimesTest extends RepositoryTestCase {
dc = db.readDirCache();
entry = dc.getEntry(path);
- assertTrue("shall have equal mod time!", masterLastMod == sideLastMode);
- assertTrue("shall not equal master timestamp!",
- entry.getLastModified() == masterLastMod);
+ assertTrue("shall have equal mod time!",
+ masterLastMod.equals(sideLastMod));
+ assertTrue("shall have equal master timestamp!",
+ entry.getLastModifiedInstant().equals(masterLastMod));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
index 11100b63c0..d3e4efef53 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
@@ -42,88 +42,26 @@
*/
package org.eclipse.jgit.lib;
-import static java.lang.Long.valueOf;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.TreeSet;
+import java.time.Instant;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl;
-import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
-import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
+import org.eclipse.jgit.util.FS;
import org.junit.Test;
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
public void testRacyGitDetection() throws Exception {
@@ -137,10 +75,10 @@ public class RacyGitTests extends RepositoryTestCase {
fsTick(db.getIndexFile());
// create two files
- File a = addToWorkDir("a", "a");
- File b = addToWorkDir("b", "b");
- assertTrue(a.setLastModified(b.lastModified()));
- assertTrue(b.setLastModified(b.lastModified()));
+ File a = writeToWorkDir("a", "a");
+ 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
// doesn't match the modtime of index-file after next persistance
@@ -158,23 +96,36 @@ public class RacyGitTests extends RepositoryTestCase {
fsTick(db.getIndexFile());
// Create a racy git situation. This is a situation that the index is
- // updated and then a file is modified within a second. By changing the
- // index file artificially, we create a fake racy situation.
- File updatedA = addToWorkDir("a", "a2");
- assertTrue(updatedA.setLastModified(updatedA.lastModified() + 100));
+ // updated and then a file is modified within the same tick of the
+ // 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));
- assertTrue(db.getIndexFile()
- .setLastModified(updatedA.lastModified() + 90));
+ FS.DETECTED.setLastModified(db.getIndexFile().toPath(),
+ newLastModified);
- db.readDirCache();
- // although racily clean a should not be reported as being dirty
+ DirCache dc = db.readDirCache();
+ // check index state: although racily clean a should not be reported as
+ // being dirty since we forcefully reset the index to match the working
+ // tree
assertEquals(
"[a, mode:100644, time:t1, smudged, length:0, content:a2]"
+ "[b, mode:100644, time:t0, length:1, content:b]",
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);
try (FileOutputStream fos = new FileOutputStream(f)) {
fos.write(content.getBytes(UTF_8));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
index f6fc00ce43..2ae9c11a7d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.merge;
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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -54,6 +55,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
@@ -1090,13 +1092,13 @@ public class MergerTest extends RepositoryTestCase {
@Theory
public void checkForCorrectIndex(MergeStrategy strategy) throws Exception {
File f;
- long lastTs4, lastTsIndex;
+ Instant lastTs4, lastTsIndex;
Git git = Git.wrap(db);
File indexFile = db.getIndexFile();
// Create initial content and remember when the last file was written.
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
// files and that the index is in a new file system timer tick. Make
@@ -1109,8 +1111,9 @@ public class MergerTest extends RepositoryTestCase {
checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index");
assertEquals("Commit should not touch working tree file 4", lastTs4,
- FS.DETECTED.lastModified(new File(db.getWorkTree(), "4")));
- lastTsIndex = FS.DETECTED.lastModified(indexFile);
+ FS.DETECTED
+ .lastModifiedInstant(new File(db.getWorkTree(), "4")));
+ lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile);
// Do modifications on the master branch. Then add and commit. This
// should touch only "0", "2 and "3"
@@ -1124,7 +1127,7 @@ public class MergerTest extends RepositoryTestCase {
checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+ 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"
fsTick(indexFile);
@@ -1133,7 +1136,7 @@ public class MergerTest extends RepositoryTestCase {
checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*"
+ 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
// may have smudged entries now. Check that we have the right content
@@ -1146,13 +1149,13 @@ public class MergerTest extends RepositoryTestCase {
indexState(CONTENT));
fsTick(indexFile);
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig");
- lastTs4 = FS.DETECTED.lastModified(f);
+ lastTs4 = FS.DETECTED.lastModifiedInstant(f);
fsTick(f);
git.add().addFilepattern(".").call();
checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3",
"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"
fsTick(indexFile);
@@ -1163,7 +1166,7 @@ public class MergerTest extends RepositoryTestCase {
checkConsistentLastModified("0", "1", "2", "3", "4");
checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*"
+ 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"
fsTick(indexFile);
@@ -1330,9 +1333,10 @@ public class MergerTest extends RepositoryTestCase {
assertEquals(
"IndexEntry with path "
+ path
- + " has lastmodified with is different from the worktree file",
- FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path)
- .getLastModified());
+ + " has lastmodified which is different from the worktree file",
+ FS.DETECTED.lastModifiedInstant(new File(workTree, path)),
+ dc.getEntry(path)
+ .getLastModifiedInstant());
}
// Assert that modification timestamps of working tree files are as
@@ -1341,21 +1345,22 @@ public class MergerTest extends RepositoryTestCase {
// then this file must be younger then file i. A path "*<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
- private void checkModificationTimeStampOrder(String... pathes)
- throws IOException {
- long lastMod = Long.MIN_VALUE;
+ private void checkModificationTimeStampOrder(String... pathes) {
+ Instant lastMod = EPOCH;
for (String p : pathes) {
boolean strong = p.startsWith("<");
boolean fixed = p.charAt(strong ? 1 : 0) == '*';
p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0));
- long curMod = fixed ? Long.valueOf(p).longValue()
- : FS.DETECTED.lastModified(new File(db.getWorkTree(), p));
- if (strong)
+ Instant curMod = fixed ? Instant.parse(p)
+ : FS.DETECTED
+ .lastModifiedInstant(new File(db.getWorkTree(), p));
+ if (strong) {
assertTrue("path " + p + " is not younger than predecesssor",
- curMod > lastMod);
- else
+ curMod.compareTo(lastMod) > 0);
+ } else {
assertTrue("path " + p + " is older than predecesssor",
- curMod >= lastMod);
+ curMod.compareTo(lastMod) >= 0);
+ }
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
index b401d2bea2..43648206f0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/FileBasedConfigTest.java
@@ -46,12 +46,13 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.util.FileUtils.pathToString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
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 org.eclipse.jgit.errors.ConfigInvalidException;
@@ -85,42 +86,44 @@ public class FileBasedConfigTest {
private static final String CONTENT3 = "[" + USER + "]\n\t" + NAME + " = "
+ ALICE + "\n" + "[" + USER + "]\n\t" + EMAIL + " = " + ALICE_EMAIL;
- private File trash;
+ private Path trash;
@Before
public void setUp() throws Exception {
- trash = File.createTempFile("tmp_", "");
- trash.delete();
- assertTrue("mkdir " + trash, trash.mkdir());
+ trash = Files.createTempDirectory("tmp_");
+ FS.getFileStoreAttributes(trash.getParent());
}
@After
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
public void testSystemEncoding() throws IOException, ConfigInvalidException {
- final File file = createFile(CONTENT1.getBytes(UTF_8));
- final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(CONTENT1.getBytes(UTF_8));
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
config.setString(USER, null, NAME, BOB);
config.save();
- assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file));
+ assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
}
@Test
public void testUTF8withoutBOM() throws IOException, ConfigInvalidException {
- final File file = createFile(CONTENT1.getBytes(UTF_8));
- final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(CONTENT1.getBytes(UTF_8));
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
config.setString(USER, null, NAME, BOB);
config.save();
- assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file));
+ assertArrayEquals(CONTENT2.getBytes(UTF_8), IO.readFully(file.toFile()));
}
@Test
@@ -131,8 +134,9 @@ public class FileBasedConfigTest {
bos1.write(0xBF);
bos1.write(CONTENT1.getBytes(UTF_8));
- final File file = createFile(bos1.toByteArray());
- final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos1.toByteArray());
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
@@ -144,7 +148,7 @@ public class FileBasedConfigTest {
bos2.write(0xBB);
bos2.write(0xBF);
bos2.write(CONTENT2.getBytes(UTF_8));
- assertArrayEquals(bos2.toByteArray(), IO.readFully(file));
+ assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
}
@Test
@@ -153,8 +157,9 @@ public class FileBasedConfigTest {
bos1.write(" \n\t".getBytes(UTF_8));
bos1.write(CONTENT1.getBytes(UTF_8));
- final File file = createFile(bos1.toByteArray());
- final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos1.toByteArray());
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
@@ -164,19 +169,20 @@ public class FileBasedConfigTest {
final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
bos2.write(" \n\t".getBytes(UTF_8));
bos2.write(CONTENT2.getBytes(UTF_8));
- assertArrayEquals(bos2.toByteArray(), IO.readFully(file));
+ assertArrayEquals(bos2.toByteArray(), IO.readFully(file.toFile()));
}
@Test
public void testIncludeAbsolute()
throws IOException, ConfigInvalidException {
- final File includedFile = createFile(CONTENT1.getBytes(UTF_8));
+ final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
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 FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos.toByteArray());
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
}
@@ -184,13 +190,14 @@ public class FileBasedConfigTest {
@Test
public void testIncludeRelativeDot()
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();
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 FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos.toByteArray(), "dir1");
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
}
@@ -198,14 +205,15 @@ public class FileBasedConfigTest {
@Test
public void testIncludeRelativeDotDot()
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();
bos.write("[include]\npath=".getBytes(UTF_8));
- bos.write(("../" + includedFile.getParentFile().getName() + "/"
- + includedFile.getName()).getBytes(UTF_8));
+ bos.write(("../" + includedFile.getParent().getFileName() + "/"
+ + includedFile.getFileName()).getBytes(UTF_8));
- final File file = createFile(bos.toByteArray(), "dir2");
- final FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos.toByteArray(), "dir2");
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(ALICE, config.getString(USER, null, NAME));
}
@@ -213,13 +221,14 @@ public class FileBasedConfigTest {
@Test
public void testIncludeRelativeDotDotNotFound()
throws IOException, ConfigInvalidException {
- final File includedFile = createFile(CONTENT1.getBytes(UTF_8));
+ final Path includedFile = createFile(CONTENT1.getBytes(UTF_8));
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
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 FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(bos.toByteArray());
+ final FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.load();
assertEquals(null, config.getString(USER, null, NAME));
}
@@ -227,16 +236,16 @@ public class FileBasedConfigTest {
@Test
public void testIncludeWithTilde()
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();
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();
- 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();
assertEquals(ALICE, config.getString(USER, null, NAME));
}
@@ -246,13 +255,14 @@ public class FileBasedConfigTest {
throws IOException, ConfigInvalidException {
// use a content with multiple sections and multiple key/value pairs
// 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");
- FileBasedConfig config = new FileBasedConfig(file, FS.DETECTED);
+ final Path file = createFile(new byte[0], "dir2");
+ FileBasedConfig config = new FileBasedConfig(file.toFile(),
+ FS.DETECTED);
config.setString("include", null, "path",
- ("../" + includedFile.getParentFile().getName() + "/"
- + includedFile.getName()));
+ ("../" + includedFile.getParent().getFileName() + "/"
+ + includedFile.getFileName()));
// just by setting the include.path, it won't be included
assertEquals(null, config.getString(USER, null, NAME));
@@ -267,7 +277,7 @@ public class FileBasedConfigTest {
assertEquals(2,
new StringTokenizer(expectedText, "\n", false).countTokens());
- config = new FileBasedConfig(file, FS.DETECTED);
+ config = new FileBasedConfig(file.toFile(), FS.DETECTED);
config.load();
String actualText = config.toText();
@@ -285,16 +295,17 @@ public class FileBasedConfigTest {
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);
}
- private File createFile(byte[] content, String subdir) throws IOException {
- File dir = subdir != null ? new File(trash, subdir) : trash;
- dir.mkdirs();
+ private Path createFile(byte[] content, String subdir) throws IOException {
+ Path dir = subdir != null ? trash.resolve(subdir) : trash;
+ Files.createDirectories(dir);
- File f = File.createTempFile(getClass().getName(), null, dir);
- try (FileOutputStream os = new FileOutputStream(f, true)) {
+ Path f = Files.createTempFile(dir, getClass().getName(), null);
+ try (OutputStream os = Files.newOutputStream(f,
+ StandardOpenOption.APPEND)) {
os.write(content);
}
return f;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
index 1a22e10f4c..2e5027f7ec 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
@@ -56,10 +56,13 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.OpenSshConfig.Host;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
@@ -91,13 +94,19 @@ public class OpenSshConfigTest extends RepositoryTestCase {
}
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 {
try (final OutputStreamWriter fw = new OutputStreamWriter(
new FileOutputStream(configFile), UTF_8)) {
fw.write(data);
+ TimeUnit.NANOSECONDS.sleep(resolution);
+ } catch (InterruptedException e) {
+ Thread.interrupted();
}
- } while (lastMtime == configFile.lastModified());
+ } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
index 6f61912f2b..754341dfea 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
@@ -54,6 +54,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.security.MessageDigest;
+import java.time.Instant;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand.ResetType;
@@ -88,7 +89,7 @@ import org.junit.Test;
public class FileTreeIteratorTest extends RepositoryTestCase {
private final String[] paths = { "a,", "a,b", "a/b", "a0b" };
- private long[] mtime;
+ private Instant[] mtime;
@Override
@Before
@@ -101,11 +102,11 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
// This should stress the sorting code better than doing it in
// the correct order.
//
- mtime = new long[paths.length];
+ mtime = new Instant[paths.length];
for (int i = paths.length - 1; i >= 0; i--) {
final String s = paths[i];
writeTrashFile(s, s);
- mtime[i] = FS.DETECTED.lastModified(new File(trash, s));
+ mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s));
}
}
@@ -201,7 +202,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[0], nameOf(top));
assertEquals(paths[0].length(), top.getEntryLength());
- assertEquals(mtime[0], top.getEntryLastModified());
+ assertEquals(mtime[0], top.getEntryLastModifiedInstant());
top.next(1);
assertFalse(top.first());
@@ -209,7 +210,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[1], nameOf(top));
assertEquals(paths[1].length(), top.getEntryLength());
- assertEquals(mtime[1], top.getEntryLastModified());
+ assertEquals(mtime[1], top.getEntryLastModifiedInstant());
top.next(1);
assertFalse(top.first());
@@ -224,7 +225,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
assertFalse(sub.eof());
assertEquals(paths[2], nameOf(sub));
assertEquals(paths[2].length(), subfti.getEntryLength());
- assertEquals(mtime[2], subfti.getEntryLastModified());
+ assertEquals(mtime[2], subfti.getEntryLastModifiedInstant());
sub.next(1);
assertTrue(sub.eof());
@@ -235,7 +236,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode);
assertEquals(paths[3], nameOf(top));
assertEquals(paths[3].length(), top.getEntryLength());
- assertEquals(mtime[3], top.getEntryLastModified());
+ assertEquals(mtime[3], top.getEntryLastModifiedInstant());
top.next(1);
assertTrue(top.eof());
@@ -347,20 +348,21 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
@Test
public void testIsModifiedFileSmudged() throws Exception {
File f = writeTrashFile("file", "content");
+ FS fs = db.getFS();
try (Git git = new Git(db)) {
// The idea of this test is to check the smudged handling
// Hopefully fsTick will make sure our entry gets smudged
fsTick(f);
writeTrashFile("file", "content");
- long lastModified = f.lastModified();
+ Instant lastModified = fs.lastModifiedInstant(f);
git.add().addFilepattern("file").call();
writeTrashFile("file", "conten2");
- f.setLastModified(lastModified);
+ fs.setLastModified(f.toPath(), lastModified);
// We cannot trust this to go fast enough on
// a system with less than one-second lastModified
// resolution, so we force the index to have the
// same timestamp as the file we look at.
- db.getIndexFile().setLastModified(lastModified);
+ fs.setLastModified(db.getIndexFile().toPath(), lastModified);
}
DirCacheEntry dce = db.readDirCache().getEntry("file");
FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java
deleted file mode 100644
index fc79d4586d..0000000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java
+++ /dev/null
@@ -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();
- }
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
index 6dfa6ef5aa..99d435100b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.util;
+import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -67,6 +68,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.RepositoryCache;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
@@ -107,7 +109,7 @@ public class FSTest {
assertTrue(fs.exists(link));
String targetName = fs.readSymLink(link);
assertEquals("b", targetName);
- assertTrue(fs.lastModified(link) > 0);
+ assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
assertTrue(fs.exists(link));
assertFalse(fs.canExecute(link));
// The length of a symbolic link is a length of the target file path.
@@ -121,8 +123,9 @@ public class FSTest {
// Now create the link target
FileUtils.createNewFile(target);
assertTrue(fs.exists(link));
- assertTrue(fs.lastModified(link) > 0);
- assertTrue(fs.lastModified(target) > fs.lastModified(link));
+ assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
+ assertTrue(fs.lastModifiedInstant(target)
+ .compareTo(fs.lastModifiedInstant(link)) > 0);
assertFalse(fs.canExecute(link));
fs.setExecute(target, true);
assertFalse(fs.canExecute(link));
@@ -229,7 +232,8 @@ public class FSTest {
.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH)
.withZone(ZoneId.systemDefault());
Path dir = Files.createTempDirectory("probe-filesystem");
- Duration resolution = FS.getFsTimerResolution(dir);
+ Duration resolution = FS.getFileStoreAttributes(dir)
+ .getFsTimestampResolution();
long resolutionNs = resolution.toNanos();
assertTrue(resolutionNs > 0);
for (int i = 0; i < 10; i++) {
@@ -252,4 +256,11 @@ public class FSTest {
}
}
}
+
+ // bug 548682
+ @Test
+ public void testRepoCacheRelativePathUnbornRepo() {
+ assertFalse(RepositoryCache.FileKey
+ .isGitRepository(new File("repo.git"), FS.DETECTED));
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java
new file mode 100644
index 0000000000..5894f7dc5c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/SimpleLruCacheTest.java
@@ -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"));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java
new file mode 100644
index 0000000000..8b253828c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StatsTest.java
@@ -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);
+ }
+}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index eb93d3e2b0..2d50e3a3b5 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -3,11 +3,31 @@
<resource path="META-INF/MANIFEST.MF">
<filter id="924844039">
<message_arguments>
- <message_argument value="5.3.2"/>
+ <message_argument value="5.3.3"/>
<message_argument value="5.3.0"/>
</message_arguments>
</filter>
</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">
<filter id="1142947843">
<message_arguments>
@@ -22,6 +42,34 @@
</message_arguments>
</filter>
</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">
<filter id="336658481">
<message_arguments>
@@ -78,20 +126,98 @@
</message_arguments>
</filter>
</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">
<filter id="1142947843">
<message_arguments>
- <message_argument value="5.2.3"/>
- <message_argument value="getFsTimerResolution(Path)"/>
+ <message_argument value="5.1.9"/>
+ <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>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
<filter id="1142947843">
<message_arguments>
- <message_argument value="5.2.3"/>
+ <message_argument value="5.1.8"/>
<message_argument value="touch(Path)"/>
</message_arguments>
</filter>
</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>
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index d22ff1fb14..6097858185 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -223,6 +223,7 @@
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
+ <version>${spotbugs-maven-plugin-version}</version>
<configuration>
<excludeFilterFile>findBugs/FindBugsExcludeFilter.xml</excludeFilterFile>
</configuration>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 51910f8da6..466eb0ffbf 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -104,6 +104,7 @@ cannotReadObjectsPath=Cannot read {0}/{1}: {2}
cannotReadTree=Cannot read tree {0}
cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
+cannotSaveConfig=Cannot save config file ''{0}''
cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit.
cannotStoreObjects=cannot store objects
cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID
@@ -398,6 +399,7 @@ invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1}
invalidPathPeriodAtEndWindows=Invalid path (period 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}
+invalidPurgeFactor=Invalid purgeFactor {0}, values have to be in range between 0 and 1
invalidRedirectLocation=Invalid redirect location {0} -> {1}
invalidRefAdvertisementLine=Invalid ref advertisement line: ''{1}''
invalidReflogRevision=Invalid reflog revision: {0}
@@ -568,8 +570,10 @@ pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport
pushNotPermitted=push not permitted
pushOptionsNotSupported=Push options not supported; received {0}
rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
+readConfigFailed=Reading config file ''{0}'' failed
readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
+readLastModifiedFailed=Reading lastModified of {0} failed
readTimedOut=Read timed out after {0} ms
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.
@@ -691,6 +695,7 @@ theFactoryMustNotBeNull=The factory must not be null
threadInterruptedWhileRunning="Current thread interrupted while running {0}"
timeIsUncertain=Time is uncertain
timerAlreadyTerminated=Timer already terminated
+timeoutMeasureFsTimestampResolution=measuring filesystem timestamp resolution for ''{0}'' timed out, fall back to resolution of 2 seconds
tooManyCommands=Too many commands
tooManyFilters=Too many "filter" lines in request
tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)?
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index f0408ab3d4..a2cd4aeb2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -50,6 +50,7 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Instant;
import java.util.Collection;
import java.util.LinkedList;
@@ -228,7 +229,7 @@ public class AddCommand extends GitCommand<DirCache> {
if (GITLINK != mode) {
entry.setLength(f.getEntryLength());
- entry.setLastModified(f.getEntryLastModified());
+ entry.setLastModified(f.getEntryLastModifiedInstant());
long len = f.getEntryContentLength();
// We read and filter the content multiple times.
// f.getEntryContentLength() reads and filters the input and
@@ -241,7 +242,7 @@ public class AddCommand extends GitCommand<DirCache> {
}
} else {
entry.setLength(0);
- entry.setLastModified(0);
+ entry.setLastModified(Instant.ofEpochSecond(0));
entry.setObjectId(f.getEntryObjectId());
}
builder.add(entry);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
index 27bb5a90b9..3f7306bf37 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
@@ -56,13 +56,18 @@ import java.util.concurrent.ConcurrentMap;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
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.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -375,13 +380,15 @@ public class ArchiveCommand extends GitCommand<OutputStream> {
MutableObjectId idBuf = new MutableObjectId();
ObjectReader reader = walk.getObjectReader();
- walk.reset(rw.parseTree(tree));
- if (!paths.isEmpty())
+ RevObject o = rw.peel(rw.parseAny(tree));
+ walk.reset(getTree(o));
+ if (!paths.isEmpty()) {
walk.setFilter(PathFilterGroup.createFromStrings(paths));
+ }
// Put base directory into archive
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);
}
@@ -392,17 +399,18 @@ public class ArchiveCommand extends GitCommand<OutputStream> {
if (walk.isSubtree())
walk.enterSubtree();
- if (mode == FileMode.GITLINK)
+ if (mode == FileMode.GITLINK) {
// TODO(jrn): Take a callback to recurse
// into submodules.
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;
}
walk.getObjectId(idBuf, 0);
- fmt.putEntry(outa, tree, name, mode, reader.open(idBuf));
+ fmt.putEntry(outa, o, name, mode, reader.open(idBuf));
}
return out;
} finally {
@@ -534,4 +542,19 @@ public class ArchiveCommand extends GitCommand<OutputStream> {
this.paths = Arrays.asList(paths);
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;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 9f071c4c4e..61d51cc913 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -413,7 +413,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
final DirCacheEntry dcEntry = new DirCacheEntry(path);
long entryLength = fTree.getEntryLength();
dcEntry.setLength(entryLength);
- dcEntry.setLastModified(fTree.getEntryLastModified());
+ dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
boolean objectExists = (dcTree != null
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 13ce4e7690..d7c9ad5e04 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -422,7 +422,7 @@ public class ResetCommand extends GitCommand<Ref> {
DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
- entry.setLastModified(indexEntry.getLastModified());
+ entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength());
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 2136e51c44..aeb9395c1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -362,7 +362,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
- entry.setLastModified(indexEntry.getLastModified());
+ entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength());
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
index c32890d8a4..0d010adb26 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
@@ -300,7 +300,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
final DirCacheEntry entry = new DirCacheEntry(
treeWalk.getRawPath());
entry.setLength(wtIter.getEntryLength());
- entry.setLastModified(wtIter.getEntryLastModified());
+ entry.setLastModified(
+ wtIter.getEntryLastModifiedInstant());
entry.setFileMode(wtIter.getEntryFileMode());
long contentLength = wtIter.getEntryContentLength();
try (InputStream in = wtIter.openEntryStream()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 14653fe17f..0cfd16b58a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -58,6 +58,7 @@ import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -497,8 +498,7 @@ public class DirCache {
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
snapshot = FileSnapshot.save(liveFile);
- int smudge_s = (int) (snapshot.lastModified() / 1000);
- int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
+ Instant smudge = snapshot.lastModifiedInstant();
// Load the individual file entries.
//
@@ -507,8 +507,9 @@ public class DirCache {
sortedEntries = new DirCacheEntry[entryCnt];
final MutableInteger infoAt = new MutableInteger();
- for (int i = 0; i < entryCnt; i++)
- sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge_s, smudge_ns);
+ for (int i = 0; i < entryCnt; i++) {
+ sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
+ }
// After the file entries are index extensions, and then a footer.
//
@@ -679,8 +680,8 @@ public class DirCache {
// so we use the current timestamp as a approximation.
myLock.createCommitSnapshot();
snapshot = myLock.getCommitSnapshot();
- smudge_s = (int) (snapshot.lastModified() / 1000);
- smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000;
+ smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond());
+ smudge_ns = snapshot.lastModifiedInstant().getNano();
} else {
// Used in unit tests only
smudge_ns = 0;
@@ -1025,7 +1026,7 @@ public class DirCache {
DirCacheEntry entry = iIter.getDirCacheEntry();
if (entry.isSmudged() && iIter.idEqual(fIter)) {
entry.setLength(fIter.getEntryLength());
- entry.setLastModified(fIter.getEntryLastModified());
+ entry.setLastModified(fIter.getEntryLastModifiedInstant());
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 307fd3f310..2d6228657a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -50,6 +50,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -427,8 +428,10 @@ public class DirCacheCheckout {
// update the timestamp of the index with the one from the
// file if not set, as we are sure to be in sync here.
DirCacheEntry entry = i.getDirCacheEntry();
- if (entry.getLastModified() == 0)
- entry.setLastModified(f.getEntryLastModified());
+ Instant mtime = entry.getLastModifiedInstant();
+ if (mtime == null || mtime.equals(Instant.EPOCH)) {
+ entry.setLastModified(f.getEntryLastModifiedInstant());
+ }
keep(entry, f);
}
} else
@@ -640,7 +643,7 @@ public class DirCacheCheckout {
File gitlinkDir = new File(repo.getWorkTree(), path);
FileUtils.mkdirs(gitlinkDir, true);
FS fs = repo.getFS();
- entry.setLastModified(fs.lastModified(gitlinkDir));
+ entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
}
private static ArrayList<String> filterOut(ArrayList<String> strings,
@@ -1482,7 +1485,7 @@ public class DirCacheCheckout {
}
fs.createSymLink(f, target);
entry.setLength(bytes.length);
- entry.setLastModified(fs.lastModified(f));
+ entry.setLastModified(fs.lastModifiedInstant(f));
return;
}
@@ -1551,7 +1554,7 @@ public class DirCacheCheckout {
FileUtils.delete(tmpFile);
}
}
- entry.setLastModified(fs.lastModified(f));
+ entry.setLastModified(fs.lastModifiedInstant(f));
}
// Run an external filter command
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index 6b1d4f4d8a..d2a59c1310 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -56,6 +56,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.Arrays;
import org.eclipse.jgit.errors.CorruptObjectException;
@@ -145,8 +146,8 @@ public class DirCacheEntry {
private byte inCoreFlags;
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
- final InputStream in, final MessageDigest md, final int smudge_s,
- final int smudge_ns) throws IOException {
+ final InputStream in, final MessageDigest md, final Instant smudge)
+ throws IOException {
info = sharedInfo;
infoOffset = infoAt.value;
@@ -215,8 +216,9 @@ public class DirCacheEntry {
md.update(nullpad, 0, padLen);
}
- if (mightBeRacilyClean(smudge_s, smudge_ns))
+ if (mightBeRacilyClean(smudge)) {
smudgeRacilyClean();
+ }
}
/**
@@ -344,8 +346,29 @@ public class DirCacheEntry {
* @param smudge_ns
* nanoseconds component of the index's last modified time.
* @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) {
+ 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
// and was not generated from scratch in memory. In such cases
// the entry is 'racily clean' if the entry's cached modification
@@ -355,8 +378,9 @@ public class DirCacheEntry {
//
final int base = infoOffset + P_MTIME;
final int mtime = NB.decodeInt32(info, base);
- if (smudge_s == mtime)
- return smudge_ns <= NB.decodeInt32(info, base + 4);
+ if (smudge.getEpochSecond() == mtime) {
+ return smudge.getNano() <= NB.decodeInt32(info, base + 4);
+ }
return false;
}
@@ -563,22 +587,51 @@ public class DirCacheEntry {
*
* @return last modification time of this file, in milliseconds since the
* Java epoch (midnight Jan 1, 1970 UTC).
+ * @deprecated use {@link #getLastModifiedInstant()} instead
*/
+ @Deprecated
public long getLastModified() {
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.
*
* @param when
* new cached modification date of the file, in milliseconds.
+ * @deprecated use {@link #setLastModified(Instant)} instead
*/
+ @Deprecated
public void setLastModified(long 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.
* <p>
* One of the indicators that the file has been modified by an application
@@ -692,7 +745,8 @@ public class DirCacheEntry {
@SuppressWarnings("nls")
@Override
public String toString() {
- return getFileMode() + " " + getLength() + " " + getLastModified()
+ return getFileMode() + " " + getLength() + " "
+ + getLastModifiedInstant()
+ " " + getObjectId() + " " + getStage() + " "
+ getPathString() + "\n";
}
@@ -750,12 +804,25 @@ public class DirCacheEntry {
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) {
final int base = infoOffset + pIdx;
NB.encodeInt32(info, base, (int) (when / 1000));
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() {
if (isExtended())
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 018b6431c1..d76524ef81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -165,6 +165,7 @@ public class JGitText extends TranslationBundle {
/***/ public String cannotReadTree;
/***/ public String cannotRebaseWithoutCurrentHead;
/***/ public String cannotResolveLocalTrackingRefForUpdating;
+ /***/ public String cannotSaveConfig;
/***/ public String cannotSquashFixupWithoutPreviousCommit;
/***/ public String cannotStoreObjects;
/***/ public String cannotResolveUniquelyAbbrevObjectId;
@@ -459,6 +460,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidPathPeriodAtEndWindows;
/***/ public String invalidPathSpaceAtEndWindows;
/***/ public String invalidPathReservedOnWindows;
+ /***/ public String invalidPurgeFactor;
/***/ public String invalidRedirectLocation;
/***/ public String invalidRefAdvertisementLine;
/***/ public String invalidReflogRevision;
@@ -629,8 +631,10 @@ public class JGitText extends TranslationBundle {
/***/ public String pushNotPermitted;
/***/ public String pushOptionsNotSupported;
/***/ public String rawLogMessageDoesNotParseAsLogEntry;
+ /***/ public String readConfigFailed;
/***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed;
+ /***/ public String readLastModifiedFailed;
/***/ public String readTimedOut;
/***/ public String receivePackObjectTooLarge1;
/***/ public String receivePackObjectTooLarge2;
@@ -747,6 +751,7 @@ public class JGitText extends TranslationBundle {
/***/ public String tagAlreadyExists;
/***/ public String tagNameInvalid;
/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported;
+ /***/ public String timeoutMeasureFsTimestampResolution;
/***/ public String transactionAborted;
/***/ public String theFactoryMustNotBeNull;
/***/ public String threadInterruptedWhileRunning;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index 1de3135001..976f946e5d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -43,19 +43,24 @@
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.IOException;
import java.nio.file.attribute.BasicFileAttributes;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
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.Objects;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.NonNull;
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.
@@ -74,6 +79,8 @@ import org.eclipse.jgit.util.FS;
* file is less than 3 seconds ago.
*/
public class FileSnapshot {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(FileSnapshot.class);
/**
* An unknown file size.
*
@@ -81,8 +88,14 @@ public class FileSnapshot {
*/
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 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.
* <p>
@@ -90,8 +103,8 @@ public class FileSnapshot {
* file, but only after {@link #isModified(File)} gets invoked. The returned
* snapshot contains only invalid status information.
*/
- public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1,
- UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
+ public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME,
+ UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
/**
* 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
* path does not exist.
*/
- public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0,
- Duration.ZERO, MISSING_FILEKEY) {
+ public static final FileSnapshot MISSING_FILE = new FileSnapshot(
+ Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) {
@Override
public boolean isModified(File path) {
return FS.DETECTED.exists(path);
@@ -122,6 +135,22 @@ public class FileSnapshot {
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) {
Object fileKey = fileAttributes.fileKey();
return fileKey == null ? MISSING_FILEKEY : fileKey;
@@ -141,18 +170,41 @@ public class FileSnapshot {
* @param modified
* the last modification time of the file
* @return the snapshot.
+ * @deprecated use {@link #save(Instant)} instead.
*/
+ @Deprecated
public static FileSnapshot save(long modified) {
- final long read = System.currentTimeMillis();
- return new FileSnapshot(read, modified, -1, Duration.ZERO,
- MISSING_FILEKEY);
+ final Instant read = Instant.now();
+ return new FileSnapshot(read, Instant.ofEpochMilli(modified),
+ 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. */
- private final long lastModified;
+ private final Instant lastModified;
/** 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}. */
private boolean cannotBeRacilyClean;
@@ -162,8 +214,8 @@ public class FileSnapshot {
* When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */
private final long size;
- /** measured filesystem timestamp resolution */
- private Duration fsTimestampResolution;
+ /** measured FileStore attributes */
+ private FileStoreAttributes fileStoreAttributeCache;
/**
* Object that uniquely identifies the given file, or {@code
@@ -171,31 +223,57 @@ public class FileSnapshot {
*/
private final Object fileKey;
+ private final File file;
+
/**
* Record a snapshot for a specific file path.
* <p>
* This method should be invoked before the file is accessed.
*
- * @param path
- * the path to later remember. The path's current status
+ * @param file
+ * the path to remember meta data for. The path's current status
* information is saved.
*/
- protected FileSnapshot(File path) {
- this.lastRead = System.currentTimeMillis();
- this.fsTimestampResolution = FS
- .getFsTimerResolution(path.toPath().getParent());
+ protected FileSnapshot(File file) {
+ this(file, true);
+ }
+
+ /**
+ * 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;
try {
- fileAttributes = FS.DETECTED.fileAttributes(path);
+ fileAttributes = FS.DETECTED.fileAttributes(file);
} catch (IOException e) {
- this.lastModified = path.lastModified();
- this.size = path.length();
+ this.lastModified = Instant.ofEpochMilli(file.lastModified());
+ this.size = file.length();
this.fileKey = MISSING_FILEKEY;
return;
}
- this.lastModified = fileAttributes.lastModifiedTime().toMillis();
+ this.lastModified = fileAttributes.lastModifiedTime().toInstant();
this.size = fileAttributes.size();
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;
@@ -206,11 +284,17 @@ public class FileSnapshot {
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) {
+ this.file = null;
this.lastRead = read;
this.lastModified = modified;
- this.fsTimestampResolution = fsTimestampResolution;
+ this.fileStoreAttributeCache = new FileStoreAttributes(
+ fsTimestampResolution);
this.size = size;
this.fileKey = fileKey;
}
@@ -219,8 +303,19 @@ public class FileSnapshot {
* Get time of last snapshot update
*
* @return time of last snapshot update
+ * @deprecated use {@link #lastModifiedInstant()} instead
*/
+ @Deprecated
public long lastModified() {
+ return lastModified.toEpochMilli();
+ }
+
+ /**
+ * Get time of last snapshot update
+ *
+ * @return time of last snapshot update
+ */
+ public Instant lastModifiedInstant() {
return lastModified;
}
@@ -239,16 +334,16 @@ public class FileSnapshot {
* @return true if the path needs to be read again.
*/
public boolean isModified(File path) {
- long currLastModified;
+ Instant currLastModified;
long currSize;
Object currFileKey;
try {
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
- currLastModified = fileAttributes.lastModifiedTime().toMillis();
+ currLastModified = fileAttributes.lastModifiedTime().toInstant();
currSize = fileAttributes.size();
currFileKey = getFileKey(fileAttributes);
} catch (IOException e) {
- currLastModified = path.lastModified();
+ currLastModified = Instant.ofEpochMilli(path.lastModified());
currSize = path.length();
currFileKey = MISSING_FILEKEY;
}
@@ -290,7 +385,7 @@ public class FileSnapshot {
* the other snapshot.
*/
public void setClean(FileSnapshot other) {
- final long now = other.lastRead;
+ final Instant now = other.lastRead;
if (!isRacyClean(now)) {
cannotBeRacilyClean = true;
}
@@ -304,9 +399,10 @@ public class FileSnapshot {
* if sleep was interrupted
*/
public void waitUntilNotRacy() throws InterruptedException {
- while (isRacyClean(System.currentTimeMillis())) {
- TimeUnit.NANOSECONDS
- .sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10);
+ long timestampResolution = fileStoreAttributeCache
+ .getFsTimestampResolution().toNanos();
+ while (isRacyClean(Instant.now())) {
+ TimeUnit.NANOSECONDS.sleep(timestampResolution);
}
}
@@ -318,7 +414,8 @@ public class FileSnapshot {
* @return true if the two snapshots share the same information.
*/
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);
}
@@ -341,8 +438,7 @@ public class FileSnapshot {
/** {@inheritDoc} */
@Override
public int hashCode() {
- return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size),
- fileKey);
+ return Objects.hash(lastModified, Long.valueOf(size), fileKey);
}
/**
@@ -377,6 +473,22 @@ public class FileSnapshot {
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} */
@SuppressWarnings("nls")
@Override
@@ -387,24 +499,46 @@ public class FileSnapshot {
if (this == MISSING_FILE) {
return "MISSING_FILE";
}
- DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS",
- Locale.US);
- return "FileSnapshot[modified: " + f.format(new Date(lastModified))
- + ", read: " + f.format(new Date(lastRead)) + ", size:" + size
+ return "FileSnapshot[modified: " + dateFmt.format(lastModified)
+ + ", read: " + dateFmt.format(lastRead) + ", size:" + size
+ ", fileKey: " + fileKey + "]";
}
- private boolean isRacyClean(long read) {
- // add a 10% safety margin
- long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10;
- return wasRacyClean = (read - lastModified) * 1_000_000 <= racyNanos;
+ private boolean isRacyClean(Instant read) {
+ racyThreshold = getEffectiveRacyThreshold();
+ delta = Duration.between(lastModified, read).toNanos();
+ 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 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(long currLastModified) {
+ private boolean isModified(Instant currLastModified) {
// Any difference indicates the path was modified.
- lastModifiedChanged = lastModified != currLastModified;
+ lastModifiedChanged = !lastModified.equals(currLastModified);
if (lastModifiedChanged) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "file={}, lastModified changed from {} to {}", //$NON-NLS-1$
+ file, dateFmt.format(lastModified),
+ dateFmt.format(currLastModified));
+ }
return true;
}
@@ -412,26 +546,40 @@ public class FileSnapshot {
// after the last modification that any new modifications
// are certain to change the last modified time.
if (cannotBeRacilyClean) {
+ LOG.debug("file={}, cannot be racily clean", file); //$NON-NLS-1$
return false;
}
if (!isRacyClean(lastRead)) {
// Our last read should have marked cannotBeRacilyClean,
// but this thread may not have seen the change. The read
// of the volatile field lastRead should have fixed that.
+ LOG.debug("file={}, is unmodified", file); //$NON-NLS-1$
return false;
}
// We last read this path too close to its last observed
// modification time. We may have missed a modification.
// Scan again, to ensure we still see the same state.
+ LOG.debug("file={}, is racily clean", file); //$NON-NLS-1$
return true;
}
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) {
- 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;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 00124bcf27..7400308c86 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -371,8 +371,9 @@ public class GC {
continue oldPackLoop;
if (!oldPack.shouldBeKept()
- && repo.getFS().lastModified(
- oldPack.getPackFile()) < packExpireDate) {
+ && repo.getFS()
+ .lastModifiedInstant(oldPack.getPackFile())
+ .toEpochMilli() < packExpireDate) {
oldPack.close();
if (shouldLoosen) {
loosen(inserter, reader, oldPack, ids);
@@ -560,8 +561,10 @@ public class GC {
String fName = f.getName();
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
continue;
- if (repo.getFS().lastModified(f) >= expireDate)
+ if (repo.getFS().lastModifiedInstant(f)
+ .toEpochMilli() >= expireDate) {
continue;
+ }
try {
ObjectId id = ObjectId.fromString(d + fName);
if (objectsToKeep.contains(id))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index b80c58ca9c..420e737543 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -56,8 +56,12 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
@@ -422,9 +426,16 @@ public class LockFile {
public void waitForStatChange() throws InterruptedException {
FileSnapshot o = FileSnapshot.save(ref);
FileSnapshot n = FileSnapshot.save(lck);
+ long fsTimeResolution = FS.getFileStoreAttributes(lck.toPath())
+ .getFsTimestampResolution().toNanos();
while (o.equals(n)) {
- Thread.sleep(25 /* milliseconds */);
- lck.setLastModified(System.currentTimeMillis());
+ TimeUnit.NANOSECONDS.sleep(fsTimeResolution);
+ try {
+ Files.setLastModifiedTime(lck.toPath(),
+ FileTime.from(Instant.now()));
+ } catch (IOException e) {
+ n.waitUntilNotRacy();
+ }
n = FileSnapshot.save(lck);
}
}
@@ -474,12 +485,23 @@ public class LockFile {
* Get the modification time of the output file when it was committed.
*
* @return modification time of the lock file right before we committed it.
+ * @deprecated use {@link #getCommitLastModifiedInstant()} instead
*/
+ @Deprecated
public long getCommitLastModified() {
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.
*
* @return get the {@link FileSnapshot} just before commit.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index 73ad38c95a..88e05af414 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -60,6 +60,7 @@ import java.nio.channels.FileChannel.MapMode;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@@ -107,7 +108,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
public static final Comparator<PackFile> SORT = new Comparator<PackFile>() {
@Override
public int compare(PackFile a, PackFile b) {
- return b.packLastModified - a.packLastModified;
+ return b.packLastModified.compareTo(a.packLastModified);
}
};
@@ -132,7 +133,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
private int activeCopyRawData;
- int packLastModified;
+ Instant packLastModified;
private PackFileSnapshot fileSnapshot;
@@ -172,7 +173,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
public PackFile(File packFile, int extensions) {
this.packFile = packFile;
this.fileSnapshot = PackFileSnapshot.save(packFile);
- this.packLastModified = (int) (fileSnapshot.lastModified() >> 10);
+ this.packLastModified = fileSnapshot.lastModifiedInstant();
this.extensions = extensions;
// Multiply by 31 here so we can more directly combine with another
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index e8a6ba7308..c1e94a0a3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -50,6 +50,7 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -65,6 +66,7 @@ import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.fnmatch.FileNameMatcher;
import org.eclipse.jgit.transport.SshConstants;
+import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
@@ -129,7 +131,7 @@ public class OpenSshConfigFile {
private final String localUserName;
/** 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
@@ -223,8 +225,8 @@ public class OpenSshConfigFile {
}
private synchronized State refresh() {
- final long mtime = configFile.lastModified();
- if (mtime != lastModified) {
+ final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
+ if (!mtime.equals(lastModified)) {
State newState = new State();
try (BufferedReader br = Files
.newBufferedReader(configFile.toPath(), UTF_8)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 9763fb72fe..5ae9d41db2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -476,4 +476,23 @@ public final class ConfigConstants {
* @since 5.2
*/
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";
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 6a66cf682f..fb239399ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -226,6 +226,14 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
inputUnit = wantUnit;
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$
inputUnit = TimeUnit.MILLISECONDS;
inputMul = 1;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index 9ea1868ebb..75334ddb0c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -47,6 +47,7 @@
package org.eclipse.jgit.merge;
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.lib.ConfigConstants.CONFIG_DIFF_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM;
@@ -59,6 +60,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -464,7 +466,7 @@ public class ResolveMerger extends ThreeWayMerger {
* @return the entry which was added to the index
*/
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)) {
DirCacheEntry e = new DirCacheEntry(path, stage);
e.setFileMode(p.getEntryFileMode());
@@ -491,7 +493,7 @@ public class ResolveMerger extends ThreeWayMerger {
e.getStage());
newEntry.setFileMode(e.getFileMode());
newEntry.setObjectId(e.getObjectId());
- newEntry.setLastModified(e.getLastModified());
+ newEntry.setLastModified(e.getLastModifiedInstant());
newEntry.setLength(e.getLength());
builder.add(newEntry);
return newEntry;
@@ -667,16 +669,17 @@ public class ResolveMerger extends ThreeWayMerger {
// 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.
DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_0, 0, 0);
+ DirCacheEntry.STAGE_0, EPOCH, 0);
addToCheckout(tw.getPathString(), e, attributes);
}
return true;
} else {
// FileModes are not mergeable. We found a conflict on modes.
// For conflicting entries we don't know lastModified and length.
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH,
+ 0);
unmergedPaths.add(tw.getPathString());
mergeResults.put(
tw.getPathString(),
@@ -708,7 +711,7 @@ public class ResolveMerger extends ThreeWayMerger {
// the new content.
// This will happen later. Set these values to 0 for know.
DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_0, 0, 0);
+ DirCacheEntry.STAGE_0, EPOCH, 0);
if (e != null) {
addToCheckout(tw.getPathString(), e, attributes);
}
@@ -737,16 +740,16 @@ public class ResolveMerger extends ThreeWayMerger {
// detected later
if (nonTree(modeO) && !nonTree(modeT)) {
if (nonTree(modeB))
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
unmergedPaths.add(tw.getPathString());
enterSubtree = false;
return true;
}
if (nonTree(modeT) && !nonTree(modeO)) {
if (nonTree(modeB))
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
unmergedPaths.add(tw.getPathString());
enterSubtree = false;
return true;
@@ -773,9 +776,9 @@ public class ResolveMerger extends ThreeWayMerger {
boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT);
// Don't attempt to resolve submodule link conflicts
if (gitlinkConflict || !attributes.canBeContentMerged()) {
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
if (gitlinkConflict) {
MergeResult<SubmoduleConflict> result = new MergeResult<>(
@@ -822,10 +825,10 @@ public class ResolveMerger extends ThreeWayMerger {
MergeResult<RawText> result = contentMerge(base, ours, theirs,
attributes);
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_3, 0, 0);
+ DirCacheEntry.STAGE_3, EPOCH, 0);
// OURS was deleted checkout THEIRS
if (modeO == 0) {
@@ -957,9 +960,9 @@ public class ResolveMerger extends ThreeWayMerger {
// A conflict occurred, the file will contain conflict markers
// the index will be populated with the three stages and the
// workdir (if used) contains the halfway merged content.
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0);
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
mergeResults.put(tw.getPathString(), result);
return;
}
@@ -976,7 +979,7 @@ public class ResolveMerger extends ThreeWayMerger {
? FileMode.REGULAR_FILE : FileMode.fromBits(newMode));
if (mergedFile != null) {
dce.setLastModified(
- nonNullRepo().getFS().lastModified(mergedFile));
+ nonNullRepo().getFS().lastModifiedInstant(mergedFile));
dce.setLength((int) mergedFile.length());
}
dce.setObjectId(insertMergeResult(rawMerged, attributes));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index 2b31ebd8e3..84cd6adb8d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -148,11 +148,37 @@ public class FileBasedConfig extends StoredConfig {
*/
@Override
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;
int retries = 0;
while (true) {
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 {
final byte[] in = IO.readFully(getFile());
final ObjectId newHash = hash(in);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
index 8562376aa9..7dd019ba27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
@@ -49,6 +49,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.time.Instant;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
@@ -126,7 +127,7 @@ public class NetRC {
private File netrc;
- private long lastModified;
+ private Instant lastModified;
private Map<String, NetRCEntry> hosts = new HashMap<>();
@@ -190,8 +191,10 @@ public class NetRC {
if (netrc == null)
return null;
- if (this.lastModified != this.netrc.lastModified())
+ if (!this.lastModified
+ .equals(FS.DETECTED.lastModifiedInstant(this.netrc))) {
parse();
+ }
NetRCEntry entry = this.hosts.get(host);
@@ -212,7 +215,7 @@ public class NetRC {
private void parse() {
this.hosts.clear();
- this.lastModified = this.netrc.lastModified();
+ this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
try (BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(netrc), UTF_8))) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 3d25c2314e..d432c94450 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -53,6 +53,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.time.Instant;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -406,8 +407,14 @@ public class FileTreeIterator extends WorkingTreeIterator {
}
@Override
+ @Deprecated
public long getLastModified() {
- return attributes.getLastModifiedTime();
+ return attributes.getLastModifiedInstant().toEpochMilli();
+ }
+
+ @Override
+ public Instant getLastModifiedInstant() {
+ return attributes.getLastModifiedInstant();
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index b768acd050..90524fedaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -59,6 +59,7 @@ import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@@ -646,12 +647,24 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
*
* @return last modified time of this file, in milliseconds since the epoch
* (Jan 1, 1970 UTC).
+ * @deprecated use {@link #getEntryLastModifiedInstant()} instead
*/
+ @Deprecated
public long getEntryLastModified() {
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.
* <p>
* Efficient implementations are not required. The caller will usually
@@ -925,30 +938,28 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
// Git under windows only stores seconds so we round the timestamp
// 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
// compare the whole second part.
- long cacheLastModified = entry.getLastModified();
- long fileLastModified = getEntryLastModified();
- long lastModifiedMillis = fileLastModified % 1000;
- long cacheMillis = cacheLastModified % 1000;
- if (getOptions().getCheckStat() == CheckStat.MINIMAL) {
- fileLastModified = fileLastModified - lastModifiedMillis;
- cacheLastModified = cacheLastModified - cacheMillis;
- } else if (cacheMillis == 0)
- fileLastModified = fileLastModified - lastModifiedMillis;
- // Some Java version on Linux return whole seconds only even when
- // the file systems supports more precision.
- else if (lastModifiedMillis == 0)
- cacheLastModified = cacheLastModified - cacheMillis;
-
- if (fileLastModified != cacheLastModified)
+ Instant cacheLastModified = entry.getLastModifiedInstant();
+ Instant fileLastModified = getEntryLastModifiedInstant();
+ if ((getOptions().getCheckStat() == CheckStat.MINIMAL)
+ || (cacheLastModified.getNano() == 0)
+ // Some Java version on Linux return whole seconds only even
+ // when the file systems supports more precision.
+ || (fileLastModified.getNano() == 0)) {
+ if (fileLastModified.getEpochSecond() != cacheLastModified
+ .getEpochSecond()) {
+ return MetadataDiff.DIFFER_BY_TIMESTAMP;
+ }
+ }
+ if (!fileLastModified.equals(cacheLastModified)) {
return MetadataDiff.DIFFER_BY_TIMESTAMP;
- else if (!entry.isSmudged())
- // The file is clean when you look at timestamps.
- return MetadataDiff.EQUAL;
- else
+ } else if (entry.isSmudged()) {
return MetadataDiff.SMUDGED;
+ }
+ // The file is clean when when comparing timestamps
+ return MetadataDiff.EQUAL;
}
/**
@@ -1275,10 +1286,26 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* instance member instead.
*
* @return time since the epoch (in ms) of the last change.
+ * @deprecated use {@link #getLastModifiedInstant()} instead
*/
+ @Deprecated
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.
* <p>
* Efficient implementations are not required. The caller will obtain
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index fb958872ac..10a9919391 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.util;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@@ -53,7 +54,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.PrintStream;
+import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileStore;
@@ -65,27 +68,39 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.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.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
import org.eclipse.jgit.treewalk.FileTreeIterator.FileModeStrategy;
import org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry;
@@ -188,81 +203,478 @@ public abstract class FS {
}
}
- 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);
- 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);
+
+ 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;
+ }
- static Duration getFsTimestampResolution(Path file) {
+ private static FileStoreAttributes getFileStoreAttributes(Path dir) {
+ FileStore s;
try {
- Path dir = Files.isDirectory(file) ? file : file.getParent();
- if (!dir.toFile().canWrite()) {
- // can not determine FileStore of an unborn directory or in
- // a read-only directory
- return FALLBACK_TIMESTAMP_RESOLUTION;
- }
- 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.exists(dir)) {
+ s = Files.getFileStore(dir);
+ FileStoreAttributes c = attributeCache.get(s);
+ if (c != null) {
+ return c;
+ }
+ if (!Files.isWritable(dir)) {
+ // cannot measure resolution in a read-only directory
+ LOG.debug(
+ "{}: cannot measure timestamp resolution in read-only directory {}", //$NON-NLS-1$
+ Thread.currentThread(), dir);
+ return FALLBACK_FILESTORE_ATTRIBUTES;
}
+ } else {
+ // cannot determine FileStore of an unborn directory
+ LOG.debug(
+ "{}: cannot measure timestamp resolution of unborn directory {}", //$NON-NLS-1$
+ Thread.currentThread(), dir);
+ return FALLBACK_FILESTORE_ATTRIBUTES;
}
- return c.getFsTimestampResolution();
- } catch (IOException | InterruptedException e) {
- LOG.warn(e.getMessage(), e);
- return FALLBACK_TIMESTAMP_RESOLUTION;
+ CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
+ .supplyAsync(() -> {
+ Lock lock = locks.computeIfAbsent(s,
+ l -> new ReentrantLock());
+ if (!lock.tryLock()) {
+ LOG.debug(
+ "{}: couldn't get lock to measure timestamp resolution in {}", //$NON-NLS-1$
+ Thread.currentThread(), dir);
+ return Optional.empty();
+ }
+ Optional<FileStoreAttributes> attributes = Optional
+ .empty();
+ try {
+ // Some earlier future might have set the value
+ // and removed itself since we checked for the
+ // value above. Hence check cache again.
+ FileStoreAttributes c = attributeCache
+ .get(s);
+ if (c != null) {
+ return Optional.of(c);
+ }
+ attributes = readFromConfig(s);
+ if (attributes.isPresent()) {
+ attributeCache.put(s, attributes.get());
+ return attributes;
+ }
+
+ Optional<Duration> resolution = measureFsTimestampResolution(
+ s, dir);
+ if (resolution.isPresent()) {
+ c = new FileStoreAttributes(
+ resolution.get());
+ attributeCache.put(s, c);
+ // for high timestamp resolution measure
+ // minimal racy interval
+ if (c.fsTimestampResolution
+ .toNanos() < 100_000_000L) {
+ c.minimalRacyInterval = measureMinimalRacyInterval(
+ dir);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(c.toString());
+ }
+ saveToConfig(s, c);
+ }
+ attributes = Optional.of(c);
+ } finally {
+ lock.unlock();
+ locks.remove(s);
+ }
+ return attributes;
+ });
+ f.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;
}
- private Duration fsTimestampResolution;
+ @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;
+ }
- Duration getFsTimestampResolution() {
- return fsTimestampResolution;
+ private static void write(Path p, String body) throws IOException {
+ FileUtils.mkdirs(p.getParent().toFile(), true);
+ try (Writer w = new OutputStreamWriter(Files.newOutputStream(p),
+ UTF_8)) {
+ w.write(body);
+ }
+ }
+
+ private static String read(Path p) throws IOException {
+ final byte[] body = IO.readFully(p.toFile());
+ return new String(body, 0, body.length, UTF_8);
}
- private FileStoreAttributeCache(Path dir)
- throws IOException, InterruptedException {
+ 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$
- Files.createFile(probe);
try {
- FileTime startTime = Files.getLastModifiedTime(probe);
- FileTime actTime = startTime;
- long sleepTime = 512;
- while (actTime.compareTo(startTime) <= 0) {
- TimeUnit.NANOSECONDS.sleep(sleepTime);
- FileUtils.touch(probe);
- actTime = Files.getLastModifiedTime(probe);
- // limit sleep time to max. 100ms
- if (sleepTime < 100_000_000L) {
- sleepTime = sleepTime * 2;
- }
+ 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);
}
- fsTimestampResolution = Duration.between(startTime.toInstant(),
- actTime.toInstant());
+ 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 {
- Files.delete(probe);
+ deleteProbe(probe);
}
+ return Optional.empty();
}
- @SuppressWarnings("nls")
+ 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;
+ }
+
+ /**
+ * Construct a FileStoreAttributeCache entry for the given filesystem
+ * timestamp resolution
+ *
+ * @param fsTimestampResolution
+ */
+ public FileStoreAttributes(
+ @NonNull Duration fsTimestampResolution) {
+ this.fsTimestampResolution = fsTimestampResolution;
+ this.minimalRacyInterval = Duration.ZERO;
+ }
+
+ @SuppressWarnings({ "nls", "boxing" })
@Override
public String toString() {
- return "FileStoreAttributeCache[" + attributeCache.keySet()
- .stream()
- .map(key -> "FileStore[" + key + "]: fsTimestampResolution="
- + attributeCache.get(key).getFsTimestampResolution())
- .collect(Collectors.joining(",\n")) + "]";
+ return String.format(
+ "FileStoreAttributes[fsTimestampResolution=%,d µs, "
+ + "minimalRacyInterval=%,d µs]",
+ fsTimestampResolution.toNanos() / 1000,
+ minimalRacyInterval.toNanos() / 1000);
}
+
}
/** The auto-detected implementation selected for this operating system and JRE. */
@@ -280,6 +692,19 @@ public abstract class FS {
}
/**
+ * Whether FileStore attributes should be determined asynchronously
+ *
+ * @param asynch
+ * whether FileStore attributes should be determined
+ * asynchronously. If false access to cached attributes may block
+ * for some seconds for the first call per FileStore
+ * @since 5.1.9
+ */
+ public static void setAsyncFileStoreAttributes(boolean asynch) {
+ FileStoreAttributes.setBackground(asynch);
+ }
+
+ /**
* Auto-detect the appropriate file system abstraction, taking into account
* the presence of a Cygwin installation on the system. Using jgit in
* combination with Cygwin requires a more elaborate (and possibly slower)
@@ -307,18 +732,18 @@ public abstract class FS {
}
/**
- * Get an estimate for the filesystem timestamp resolution from a cache of
- * timestamp resolution per FileStore, if not yet available it is measured
- * for a probe file under the given directory.
+ * Get cached FileStore attributes, if not yet available measure them using
+ * a probe file under the given directory.
*
* @param dir
* the directory under which the probe file will be created to
* measure the timer resolution.
* @return measured filesystem timestamp resolution
- * @since 5.2.3
+ * @since 5.1.9
*/
- public static Duration getFsTimerResolution(@NonNull Path dir) {
- return FileStoreAttributeCache.getFsTimestampResolution(dir);
+ public static FileStoreAttributes getFileStoreAttributes(
+ @NonNull Path dir) {
+ return FileStoreAttributes.get(dir);
}
private volatile Holder<File> userHome;
@@ -432,12 +857,42 @@ public abstract class FS {
* @return last modified time of f
* @throws java.io.IOException
* @since 3.0
+ * @deprecated use {@link #lastModifiedInstant(Path)} instead
*/
+ @Deprecated
public long lastModified(File f) throws IOException {
return FileUtils.lastModified(f);
}
/**
+ * Get the last modified time of a file system object. If the OS/JRE support
+ * symbolic links, the modification time of the link is returned, rather
+ * than that of the link target.
+ *
+ * @param p
+ * a {@link Path} object.
+ * @return last modified time of p
+ * @since 5.1.9
+ */
+ public Instant lastModifiedInstant(Path p) {
+ return FileUtils.lastModifiedInstant(p);
+ }
+
+ /**
+ * Get the last modified time of a file system object. If the OS/JRE support
+ * symbolic links, the modification time of the link is returned, rather
+ * than that of the link target.
+ *
+ * @param f
+ * a {@link File} object.
+ * @return last modified time of p
+ * @since 5.1.9
+ */
+ public Instant lastModifiedInstant(File f) {
+ return FileUtils.lastModifiedInstant(f.toPath());
+ }
+
+ /**
* Set the last modified time of a file system object. If the OS/JRE support
* symbolic links, the link is modified, not the target,
*
@@ -447,12 +902,29 @@ public abstract class FS {
* last modified time
* @throws java.io.IOException
* @since 3.0
+ * @deprecated use {@link #setLastModified(Path, Instant)} instead
*/
+ @Deprecated
public void setLastModified(File f, long time) throws IOException {
FileUtils.setLastModified(f, time);
}
/**
+ * Set the last modified time of a file system object. If the OS/JRE support
+ * symbolic links, the link is modified, not the target,
+ *
+ * @param p
+ * a {@link Path} object.
+ * @param time
+ * last modified time
+ * @throws java.io.IOException
+ * @since 5.1.9
+ */
+ public void setLastModified(Path p, Instant time) throws IOException {
+ FileUtils.setLastModified(p, time);
+ }
+
+ /**
* Get the length of a file or link, If the OS/JRE supports symbolic links
* it's the length of the link, else the length of the target.
*
@@ -1526,9 +1998,19 @@ public abstract class FS {
/**
* @return the time (milliseconds since 1970-01-01) when this object was
* last modified
+ * @deprecated use getLastModifiedInstant instead
*/
+ @Deprecated
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;
@@ -1539,7 +2021,7 @@ public abstract class FS {
private final long creationTime;
- private final long lastModifiedTime;
+ private final Instant lastModifiedInstant;
private final boolean isExecutable;
@@ -1557,7 +2039,7 @@ public abstract class FS {
Attributes(FS fs, File file, boolean exists, boolean isDirectory,
boolean isExecutable, boolean isSymbolicLink,
boolean isRegularFile, long creationTime,
- long lastModifiedTime, long length) {
+ Instant lastModifiedInstant, long length) {
this.fs = fs;
this.file = file;
this.exists = exists;
@@ -1566,7 +2048,7 @@ public abstract class FS {
this.isSymbolicLink = isSymbolicLink;
this.isRegularFile = isRegularFile;
this.creationTime = creationTime;
- this.lastModifiedTime = lastModifiedTime;
+ this.lastModifiedInstant = lastModifiedInstant;
this.length = length;
}
@@ -1578,7 +2060,7 @@ public abstract class FS {
* @param path
*/
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 abstract class FS {
boolean exists = isDirectory || isFile;
boolean canExecute = exists && !isDirectory && canExecute(path);
boolean isSymlink = false;
- long lastModified = exists ? path.lastModified() : 0L;
+ Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
long createTime = 0L;
return new Attributes(this, path, exists, isDirectory, canExecute,
isSymlink, isFile, createTime, lastModified, -1);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
index 98797dc64f..7c07270363 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -149,7 +149,7 @@ public class FS_Win32 extends FS {
attrs.isSymbolicLink(),
attrs.isRegularFile(),
attrs.creationTime().toMillis(),
- attrs.lastModifiedTime().toMillis(),
+ attrs.lastModifiedTime().toInstant(),
attrs.size());
result.add(new FileEntry(f, fs, attributes,
fileModeStrategy));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 9bba6ca8a3..80f188cb2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -49,7 +49,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
import java.io.IOException;
-import java.io.OutputStream;
+import java.nio.channels.FileChannel;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
@@ -57,6 +57,7 @@ import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
@@ -66,6 +67,7 @@ import java.nio.file.attribute.PosixFilePermission;
import java.text.MessageFormat;
import java.text.Normalizer;
import java.text.Normalizer.Form;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -74,11 +76,14 @@ import java.util.regex.Pattern;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.FS.Attributes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* File Utilities
*/
public class FileUtils {
+ private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
/**
* Option to delete given {@code File}
@@ -654,13 +659,32 @@ public class FileUtils {
* @return lastModified attribute for given file, not following symbolic
* links
* @throws IOException
+ * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
+ * FileTime
*/
+ @Deprecated
static long lastModified(File file) throws IOException {
return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
.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.
*
* @param file
@@ -678,11 +702,22 @@ public class FileUtils {
* @param time
* @throws IOException
*/
+ @Deprecated
static void setLastModified(File file, long time) throws IOException {
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
* @return {@code true} if the given file exists, not following symbolic
* links
@@ -786,7 +821,7 @@ public class FileUtils {
readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), //
- readAttributes.lastModifiedTime().toMillis(),
+ readAttributes.lastModifiedTime().toInstant(),
readAttributes.isSymbolicLink() ? Constants
.encode(readSymLink(file)).length
: readAttributes.size());
@@ -825,7 +860,7 @@ public class FileUtils {
readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), //
- readAttributes.lastModifiedTime().toMillis(),
+ readAttributes.lastModifiedTime().toInstant(),
readAttributes.size());
return attributes;
} catch (IOException e) {
@@ -916,11 +951,13 @@ public class FileUtils {
* @param f
* the file to touch
* @throws IOException
- * @since 5.2.3
+ * @since 5.1.8
*/
public static void touch(Path f) throws IOException {
- try (OutputStream fos = Files.newOutputStream(f)) {
- // touch the file
+ try (FileChannel fc = FileChannel.open(f, StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND, StandardOpenOption.SYNC)) {
+ // touch
}
+ Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
new file mode 100644
index 0000000000..709d9ee73d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SimpleLruCache.java
@@ -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();
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
new file mode 100644
index 0000000000..e9307d342b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
@@ -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());
+ }
+}
diff --git a/pom.xml b/pom.xml
index 48abd185e6..275d1d634a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -194,7 +194,7 @@
<osgi-core-version>4.3.1</osgi-core-version>
<servlet-api-version>3.1.0</servlet-api-version>
<jetty-version>9.4.14.v20181114</jetty-version>
- <japicmp-version>0.13.0</japicmp-version>
+ <japicmp-version>0.14.1</japicmp-version>
<httpclient-version>4.5.6</httpclient-version>
<httpcore-version>4.4.10</httpcore-version>
<slf4j-version>1.7.2</slf4j-version>
@@ -204,10 +204,12 @@
<gson-version>2.8.2</gson-version>
<bouncycastle-version>1.60</bouncycastle-version>
<spotbugs-maven-plugin-version>3.1.11</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-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 -->
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -239,7 +241,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.1.1</version>
+ <version>3.1.2</version>
<configuration>
<archive>
<manifestEntries>
@@ -282,7 +284,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
- <version>3.0.1</version>
+ <version>3.1.0</version>
</plugin>
<plugin>
@@ -294,7 +296,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>${maven-surefire-version}</version>
+ <version>${maven-surefire-plugin-version}</version>
<configuration>
<forkCount>${test-fork-count}</forkCount>
<reuseForks>true</reuseForks>
@@ -328,7 +330,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
- <version>3.11.0</version>
+ <version>3.12.0</version>
<configuration>
<sourceEncoding>utf-8</sourceEncoding>
<minimumTokens>100</minimumTokens>
@@ -366,7 +368,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
- <version>0.8.3</version>
+ <version>0.8.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -383,7 +385,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
- <version>${maven-surefire-version}</version>
+ <version>${maven-surefire-report-plugin-version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -395,7 +397,26 @@
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven-project-info-reports-plugin-version}</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-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>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
@@ -856,7 +877,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-javac</artifactId>
- <version>2.8.4</version>
+ <version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
@@ -906,7 +927,7 @@
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>ecj</artifactId>
- <version>3.16.0</version>
+ <version>3.17.0</version>
</dependency>
</dependencies>
</plugin>