]> source.dussan.org Git - jgit.git/commitdiff
Use Instant instead of milliseconds for filesystem timestamp handling 37/145437/15
authorMatthias Sohn <matthias.sohn@sap.com>
Tue, 2 Jul 2019 23:07:14 +0000 (01:07 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Thu, 18 Jul 2019 01:27:52 +0000 (03:27 +0200)
This enables higher file timestamp resolution on filesystems like ext4,
Mac APFS (1ns) or NTFS (100ns) providing high timestamp resolution on
filesystem level.

Note:
- on some OSes Java 8,9 truncate milliseconds, see
https://bugs.openjdk.java.net/browse/JDK-8177809, fixed in Java 10
- UnixFileAttributes truncates timestamp resolution to microseconds when
converting the internal representation to FileTime exposed in the API,
see https://bugs.openjdk.java.net/browse/JDK-8181493
- WindowsFileAttributes also provides only microsecond resolution

Change-Id: I25ffff31a3c6f725fc345d4ddc2f26da3b88f6f2
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
50 files changed:
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java [new file with mode: 0644]
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
org.eclipse.jgit.test/META-INF/MANIFEST.MF
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
org.eclipse.jgit/.settings/.api_filters
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java

index 05510a05b0795e74e4be63f1f579246a0526722b..946fb15a3decb4eea117dc68e0d6042916d033cf 100644 (file)
@@ -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;
        }
 
index 62f075c73ca1fb01efe72079a51ee5f32c9814ab..5a27be64300ffc4839b0e38a952d2a4bf3aacf68 100644 (file)
@@ -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)) {
index 12b216950008d59dfa944e7a7c6116e3b3c4e9ce..50e6f2da5a166b86fe8aa85540859534809f91b0 100644 (file)
@@ -51,6 +51,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.Collections;
 import java.util.HashMap;
@@ -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())
index 987f923b0e0f86b6492df61f546523c8f85d6043..49f5c5febb4fc5928e104fbc53c8551c945b126a 100644 (file)
@@ -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,15 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase {
                        tmp = File.createTempFile("fsTickTmpFile", null,
                                        lastFile.getParentFile());
                }
-               long res = FS.getFsTimerResolution(tmp.toPath()).toMillis();
+               long res = FS.getFsTimerResolution(tmp.toPath()).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/time/TimeUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
new file mode 100644 (file)
index 0000000..1f8070d
--- /dev/null
@@ -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);
+               }
+       }
+
+}
index 7f99d76bda2bf94da34d56f88883d3da2cc5beef..14a60a3b466104b000af20fcec9b0af1b87d7e82 100644 (file)
@@ -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(' ');
index f469173aace89d0c583e253f5ce78059ddfe8a45..89ce34dbf4fc9e945386fa4479fc2523e607ef70 100644 (file)
@@ -40,6 +40,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  org.eclipse.jgit.internal.storage.reftable;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.internal.storage.reftree;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.junit;version="[5.1.9,5.2.0)",
+ org.eclipse.jgit.junit.time;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.lib;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.merge;version="[5.1.9,5.2.0)",
@@ -62,12 +63,12 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  org.eclipse.jgit.util;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)",
  org.eclipse.jgit.util.sha1;version="[5.1.9,5.2.0)",
- org.tukaani.xz;version="[1.6.0,2.0)",
  org.junit;version="[4.12,5.0.0)",
  org.junit.experimental.theories;version="[4.12,5.0.0)",
  org.junit.rules;version="[4.12,5.0.0)",
  org.junit.runner;version="[4.12,5.0.0)",
  org.junit.runners;version="[4.12,5.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)"
index 911f6591498aa97ba2e844b039098e8aaf79a660..c67c86a937f66ea9bf832af30b8db193318ce453 100644 (file)
@@ -1266,7 +1266,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);
index 71df59e1ff998912030d35ddcf1e8d83b045ae87..08ad7b8bccf2a43e22fdf293ade4ca4719e84555 100644 (file)
@@ -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;
@@ -74,6 +78,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;
@@ -86,6 +91,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;
@@ -360,14 +366,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());
 
@@ -375,10 +381,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());
 
@@ -386,7 +394,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
index 3a13aa5a415bc1c2f6253f77477f0b902eaeb0b6..a6072a0f5f4def9d60ec2465baca1209a997888b 100644 (file)
@@ -61,6 +61,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.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -311,11 +312,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());
@@ -346,11 +347,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());
 
@@ -368,9 +370,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());
@@ -399,11 +401,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());
index 43c3a8cf92b4efc7d5b4531e9afd9e93d9bd6c59..3a93839e8cdcd23fc09255b3513c66109550fbc2 100644 (file)
@@ -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");
index 2b97b307bf153adfc72a81bb1e3ddbc472549c7b..588387d3e6e0fc78f01ac972b4169ba035bd83a9 100644 (file)
@@ -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;
@@ -246,13 +250,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);
@@ -262,12 +266,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());
@@ -276,13 +280,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
index d12f302dae6ab67656ffd1d1e50bf73c4cada840..d9a420377928d1244cf93d40dbaa397946327b96 100644 (file)
@@ -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;
@@ -98,7 +99,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;
                {
@@ -116,7 +117,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);
@@ -138,7 +139,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());
                }
@@ -148,7 +149,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;
                {
@@ -166,7 +167,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);
@@ -186,7 +187,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());
                }
@@ -203,7 +204,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;
index 86e2852872ee84cb24ca4987c20216949ed6921a..475819dbb73cb1e5b9350755ab56f9a2e6a8518c 100644 (file)
@@ -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());
index 643daa5c9585f11ff8cdb0e0e22ea62c865a6a92..6bec0567373c50f5e17af29dc253b0723ba4ec46 100644 (file)
@@ -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());
                }
        }
 
index 6e458fbbf0b54bfd8db3b019c2d594536099bfe9..467f6956bae1561d01a619ecd1017eeb27aa737b 100644 (file)
@@ -51,9 +51,11 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.FileTime;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
@@ -80,11 +82,12 @@ public class FileSnapshotTest {
                FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
        }
 
-       private static void waitNextSec(File f) {
-               long initialLastModified = f.lastModified();
+       private static void waitNextTick(File f) throws IOException {
+               Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f);
                do {
-                       f.setLastModified(System.currentTimeMillis());
-               } while (f.lastModified() == initialLastModified);
+                       FS.DETECTED.setLastModified(f.toPath(), Instant.now());
+               } while (FS.DETECTED.lastModifiedInstant(f)
+                               .equals(initialLastModified));
        }
 
        /**
@@ -95,10 +98,10 @@ public class FileSnapshotTest {
        @Test
        public void testActuallyIsModifiedTrivial() throws Exception {
                File f1 = createFile("simple");
-               waitNextSec(f1);
+               waitNextTick(f1);
                FileSnapshot save = FileSnapshot.save(f1);
                append(f1, (byte) 'x');
-               waitNextSec(f1);
+               waitNextTick(f1);
                assertTrue(save.isModified(f1));
        }
 
@@ -113,7 +116,7 @@ public class FileSnapshotTest {
        @Test
        public void testNewFileWithWait() throws Exception {
                File f1 = createFile("newfile");
-               waitNextSec(f1);
+               waitNextTick(f1);
                FileSnapshot save = FileSnapshot.save(f1);
                Thread.sleep(1500);
                assertTrue(save.isModified(f1));
@@ -146,8 +149,8 @@ public class FileSnapshotTest {
                File 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);
+               waitNextTick(f2);
+               waitNextTick(f2);
                FileTime timestamp = Files.getLastModifiedTime(f1.toPath());
                FileSnapshot save = FileSnapshot.save(f1);
                Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x
@@ -185,7 +188,7 @@ public class FileSnapshotTest {
                // 0 sized FileSnapshot.
                FileSnapshot fs1 = FileSnapshot.MISSING_FILE;
                // UNKNOWN_SIZE FileSnapshot.
-               FileSnapshot fs2 = FileSnapshot.save(fs1.lastModified());
+               FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant());
 
                assertTrue(fs1.equals(fs2));
                assertTrue(fs2.equals(fs1));
index d16998db55ad72b3fe7da914726b196ba0789f0a..eaa245b4eb15a2d8f82ccd77b9b0477ebfaf1ff6 100644 (file)
@@ -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 {
index cbb73bb08e683cadccbcbcabc873f5087c166a90..8cc06d93f21c60340eccc982f191074db4502c0a 100644 (file)
@@ -65,6 +65,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;
@@ -157,13 +158,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() {
@@ -173,7 +175,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.hasObject(unknownID));
index a1433e9fe5ced697ddc1f1ddd95364b3245e70d8..d5bc61a692656361f968f5ea7371a4fba0ac44e9 100644 (file)
@@ -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());
index 5a2bd9c3330078054732913743de0de0f8b946ba..ac0a34dedff077bf6b290fbe48ac6539bfdcab60 100644 (file)
@@ -59,6 +59,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.Map;
@@ -78,6 +79,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;
 
@@ -1319,10 +1321,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) {
index 02073226d0db3a1082bb12f8ac45b368154d2886..a4509695d9bd2b5ad5dc6e5ee6f35a12a58cc7ce 100644 (file)
@@ -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)));
        }
 }
index b9bbbeb9e5709ee82c17f223b76ad6a795f2b08f..a3634141c305bc080f463575558924e947602bf3 100644 (file)
  */
 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));
                }
        }
 
index d044e65cf4727b3a91120e5e4d1eb7752dea26d8..d3e4efef537a514f734a041605f13ba77f765c89 100644 (file)
@@ -50,12 +50,15 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+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.WorkingTreeOptions;
+import org.eclipse.jgit.util.FS;
 import org.junit.Test;
 
 public class RacyGitTests extends RepositoryTestCase {
@@ -74,8 +77,8 @@ public class RacyGitTests extends RepositoryTestCase {
                // create two files
                File a = writeToWorkDir("a", "a");
                File b = writeToWorkDir("b", "b");
-               assertTrue(a.setLastModified(b.lastModified()));
-               assertTrue(b.setLastModified(b.lastModified()));
+               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
@@ -97,10 +100,11 @@ public class RacyGitTests extends RepositoryTestCase {
                // filesystem timestamp resolution. By changing the index file
                // artificially, we create a fake racy situation.
                File updatedA = writeToWorkDir("a", "a2");
-               long newLastModified = updatedA.lastModified() + 100;
-               assertTrue(updatedA.setLastModified(newLastModified));
+               Instant newLastModified = TimeUtil
+                               .setLastModifiedWithOffset(updatedA.toPath(), 100L);
                resetIndex(new FileTreeIterator(db));
-               assertTrue(db.getIndexFile().setLastModified(newLastModified));
+               FS.DETECTED.setLastModified(db.getIndexFile().toPath(),
+                               newLastModified);
 
                DirCache dc = db.readDirCache();
                // check index state: although racily clean a should not be reported as
index 7f5dba6975a11cea598f2a852a909079b1f13cd3..f22b7d6adb3209723946129bc35c04d836fcbe7f 100644 (file)
@@ -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);
+                       }
                }
        }
 
index 19fcbfd7a2207b302cffef9b3d63366b88f1d490..7777a3c99395b442a3f73e50c0fd0d7477bca2ff 100644 (file)
@@ -57,10 +57,12 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.time.Instant;
 
 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 +93,14 @@ public class OpenSshConfigTest extends RepositoryTestCase {
        }
 
        private void config(String data) throws IOException {
-               long lastMtime = configFile.lastModified();
+               FS fs = db.getFS();
+               Instant lastMtime = fs.lastModifiedInstant(configFile);
                do {
                        try (final OutputStreamWriter fw = new OutputStreamWriter(
                                        new FileOutputStream(configFile), UTF_8)) {
                                fw.write(data);
                        }
-               } while (lastMtime == configFile.lastModified());
+               } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
        }
 
        @Test
index 33e32cd8134feebbbb0fa84e685fd21ec55ed8b2..9a0e7d2eac17554d92abaeebf40e52a4643b1d4d 100644 (file)
@@ -52,6 +52,7 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.IOException;
 import java.security.MessageDigest;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
@@ -86,7 +87,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
@@ -99,11 +100,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));
                }
        }
 
@@ -199,7 +200,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());
@@ -207,7 +208,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());
@@ -222,7 +223,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());
@@ -233,7 +234,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());
@@ -345,20 +346,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
index 6b3d58bae7a437000ea89f5d189754320163e295..bde8a8a6b36902b5c7da3380a3d6baf4e5899b4c 100644 (file)
@@ -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;
@@ -106,7 +107,7 @@ public class FSTest {
                assertTrue(fs.exists(link));
                String targetName = fs.readSymLink(link);
                assertEquals("Ã¥", targetName);
-               assertTrue(fs.lastModified(link) > 0);
+               assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
                assertTrue(fs.exists(link));
                assertFalse(fs.canExecute(link));
                assertEquals(2, fs.length(link));
@@ -119,8 +120,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));
index 5f58e7e45712e7c235dd0876654437481a699729..951a5e30f8ffc23b7cd6949164d2cf83fd220fdb 100644 (file)
@@ -8,6 +8,20 @@
             </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="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>
             </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="fileAttributes(File)"/>
             </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="setAsyncfileStoreAttrCache(boolean)"/>
             </message_arguments>
         </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="5.1.9"/>
+                <message_argument value="setLastModified(Path, Instant)"/>
+            </message_arguments>
+        </filter>
         <filter id="1142947843">
             <message_arguments>
                 <message_argument value="5.2.3"/>
             </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/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
         <filter id="1142947843">
             <message_arguments>
index 9af6cce59d9888e96962d1ca81ea491d9782b863..6ac6955f06bbadf7e49fae08f319113a11a3659c 100644 (file)
@@ -561,6 +561,7 @@ 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.
index f0408ab3d4bd30db2d5b59cf6b1952253c38baa6..a2cd4aeb2a5fcbeb68f97ab9ff504388852451a6 100644 (file)
@@ -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);
index d07532c0924d9074853c2063e81748240a1b9191..cea28fac18372df9bd139af2d55b58f6dc0bf015 100644 (file)
@@ -392,7 +392,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
index 13ce4e7690ac93d89bdd0fd636530e3fb730a5ed..d7c9ad5e045b6ac8caac006026093077815a1980 100644 (file)
@@ -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());
                                }
 
index 01d070cbd94433146fe40ab4f5dd8fd6d0e542e4..ff7c4c64bcf2380d78188094488fc721284a8459 100644 (file)
@@ -332,7 +332,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());
                                }
 
index c32890d8a49c32baa721dae585d3a4d0b5d7e6a6..0d010adb262eb387e6077e062492963f79ca9b82 100644 (file)
@@ -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()) {
index 14653fe17f3f2f734a16425febcda3738bca152a..340214b068465dcf6a816f16b7b9e311cb9bf72a 100644 (file)
@@ -497,8 +497,9 @@ 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;
+               // TODO (ms) combine smudge_s and smudge_ns into Duration
+               int smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond());
+               int smudge_ns = snapshot.lastModifiedInstant().getNano();
 
                // Load the individual file entries.
                //
@@ -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());
                                }
                        }
                }
index ca1e3ab27517e287ffed10c1f35dc81ecdaa57fb..5110d77dc9bb8c00bb3c6a0d85aafa928901348a 100644 (file)
@@ -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;
@@ -425,8 +426,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);
                                }
                        } else
@@ -611,7 +614,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,
@@ -1433,7 +1436,7 @@ public class DirCacheCheckout {
                        }
                        fs.createSymLink(f, target);
                        entry.setLength(bytes.length);
-                       entry.setLastModified(fs.lastModified(f));
+                       entry.setLastModified(fs.lastModifiedInstant(f));
                        return;
                }
 
@@ -1502,7 +1505,7 @@ public class DirCacheCheckout {
                                FileUtils.delete(tmpFile);
                        }
                }
-               entry.setLastModified(fs.lastModified(f));
+               entry.setLastModified(fs.lastModifiedInstant(f));
        }
 
        // Run an external filter command
index 6b1d4f4d8aba97206c5f5c411dbceb2b1d465564..4b36f6b96b103018f4e9faec229334f22c0d77e2 100644 (file)
@@ -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;
@@ -144,6 +145,7 @@ public class DirCacheEntry {
        /** Flags which are never stored to disk. */
        private byte inCoreFlags;
 
+       // TODO (ms): use Instant to combine smudge_s and smudge_ns
        DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
                        final InputStream in, final MessageDigest md, final int smudge_s,
                        final int smudge_ns) throws IOException {
@@ -563,21 +565,50 @@ 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>
@@ -692,7 +723,8 @@ public class DirCacheEntry {
        @SuppressWarnings("nls")
        @Override
        public String toString() {
-               return getFileMode() + " " + getLength() + " " + getLastModified()
+               return getFileMode() + " " + getLength() + " "
+                               + getLastModifiedInstant()
                                + " " + getObjectId() + " " + getStage() + " "
                                + getPathString() + "\n";
        }
@@ -750,12 +782,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;
index 79133d203ca2f085f1880b01532585fde023978b..38c063d0711761b6cd36996fee9513ffc7534eca 100644 (file)
@@ -622,6 +622,7 @@ public class JGitText extends TranslationBundle {
        /***/ public String readConfigFailed;
        /***/ public String readerIsRequired;
        /***/ public String readingObjectsFromLocalRepositoryFailed;
+       /***/ public String readLastModifiedFailed;
        /***/ public String readTimedOut;
        /***/ public String receivePackObjectTooLarge1;
        /***/ public String receivePackObjectTooLarge2;
index 1b13ccf76591e7ab8e0c563266e77ddd35135e8d..fe3703719c1ab7e6e0e461cbea7e04b52b42e386 100644 (file)
@@ -48,10 +48,10 @@ import static org.eclipse.jgit.lib.Constants.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;
@@ -87,8 +87,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>
@@ -96,8 +102,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.
@@ -106,8 +112,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);
@@ -163,18 +169,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();
+               final Instant read = Instant.now();
+               return new FileSnapshot(read, Instant.ofEpochMilli(modified),
+                               UNKNOWN_SIZE, Duration.ZERO, 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, Duration.ZERO,
                                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;
@@ -222,7 +251,7 @@ public class FileSnapshot {
         */
        protected FileSnapshot(File file, boolean useConfig) {
                this.file = file;
-               this.lastRead = System.currentTimeMillis();
+               this.lastRead = Instant.now();
                this.fsTimestampResolution = useConfig
                                ? FS.getFsTimerResolution(file.toPath().getParent())
                                : FALLBACK_TIMESTAMP_RESOLUTION;
@@ -230,18 +259,20 @@ public class FileSnapshot {
                try {
                        fileAttributes = FS.DETECTED.fileAttributes(file);
                } catch (IOException e) {
-                       this.lastModified = file.lastModified();
+                       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);
-               LOG.debug("file={}, create new FileSnapshot: lastRead={} ms" //$NON-NLS-1$
-                               + ", lastModified={} ms, size={}, fileKey={}", //$NON-NLS-1$
-                               file, Long.valueOf(lastRead), Long.valueOf(lastModified),
-                               Long.valueOf(size), fileKey);
+               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;
@@ -252,7 +283,7 @@ public class FileSnapshot {
 
        private boolean wasRacyClean;
 
-       private FileSnapshot(long read, long modified, long size,
+       private FileSnapshot(Instant read, Instant modified, long size,
                        @NonNull Duration fsTimestampResolution, @NonNull Object fileKey) {
                this.file = null;
                this.lastRead = read;
@@ -266,8 +297,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;
        }
 
@@ -286,16 +328,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;
                }
@@ -337,7 +379,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;
                }
@@ -351,7 +393,7 @@ public class FileSnapshot {
         *             if sleep was interrupted
         */
        public void waitUntilNotRacy() throws InterruptedException {
-               while (isRacyClean(System.currentTimeMillis())) {
+               while (isRacyClean(Instant.now())) {
                        TimeUnit.NANOSECONDS
                                        .sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10);
                }
@@ -366,7 +408,7 @@ public class FileSnapshot {
         */
        public boolean equals(FileSnapshot other) {
                boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size;
-               return lastModified == other.lastModified && sizeEq
+               return lastModified.equals(other.lastModified) && sizeEq
                                && Objects.equals(fileKey, other.fileKey);
        }
 
@@ -389,8 +431,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);
        }
 
        /**
@@ -435,34 +476,37 @@ 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) {
+       private boolean isRacyClean(Instant read) {
                // add a 10% safety margin
                long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10;
-               long delta = (read - lastModified) * 1_000_000;
+               long delta = Duration.between(lastModified, read).toNanos();
                wasRacyClean = delta <= racyNanos;
-               LOG.debug("file={}, isRacyClean={}, read={} ms, lastModified={} ms," //$NON-NLS-1$
-                               + " delta={} ns, racy<={} ns", //$NON-NLS-1$
-                               file, Boolean.valueOf(wasRacyClean), Long.valueOf(read),
-                               Long.valueOf(lastModified), Long.valueOf(delta),
-                               Long.valueOf(racyNanos));
+               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(racyNanos));
+               }
                return wasRacyClean;
        }
 
-       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) {
-                       LOG.debug("file={}, lastModified changed from {} to {}", //$NON-NLS-1$
-                                       file, Long.valueOf(lastModified),
-                                       Long.valueOf(currLastModified));
+                       if (LOG.isDebugEnabled()) {
+                               LOG.debug(
+                                               "file={}, lastModified changed from {} to {}", //$NON-NLS-1$
+                                               file, dateFmt.format(lastModified),
+                                               dateFmt.format(currLastModified));
+                       }
                        return true;
                }
 
index 3c830e88c16e680e5f6b1368bab0373b16f3c15e..791a10828944d048c12b93f304bb8e267b9bd6b5 100644 (file)
@@ -372,8 +372,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);
@@ -561,8 +562,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))
index b80c58ca9c463ca18a2954e2ee24d60c15d8273a..ccca0279aa32732a5787fbae36869a9e1a0a4d6b 100644 (file)
@@ -56,8 +56,11 @@ 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 org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
@@ -424,7 +427,12 @@ public class LockFile {
                FileSnapshot n = FileSnapshot.save(lck);
                while (o.equals(n)) {
                        Thread.sleep(25 /* milliseconds */);
-                       lck.setLastModified(System.currentTimeMillis());
+                       try {
+                               Files.setLastModifiedTime(lck.toPath(),
+                                               FileTime.from(Instant.now()));
+                       } catch (IOException e) {
+                               n.waitUntilNotRacy();
+                       }
                        n = FileSnapshot.save(lck);
                }
        }
@@ -474,11 +482,22 @@ 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.
         *
index 73ad38c95a5724a2074b283b916ef5edac16c17e..88e05af414cad89403eb5dc34ffdf6e2addb9eda 100644 (file)
@@ -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
index f60c95f6472b8e268137d51e23dd2cd6f2a460bc..d4282e0b85273cea4bcf3857ad2171d60092e4eb 100644 (file)
@@ -46,6 +46,7 @@
  */
 package org.eclipse.jgit.merge;
 
+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));
index e688f6340dd1f5fc05e6bf29091441f692bcb473..4f1eba66d33f587cd5ad8d1455f5a392c5586c7d 100644 (file)
@@ -46,6 +46,7 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
@@ -123,7 +124,7 @@ public class NetRC {
 
        private File netrc;
 
-       private long lastModified;
+       private Instant lastModified;
 
        private Map<String, NetRCEntry> hosts = new HashMap<>();
 
@@ -187,8 +188,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);
 
@@ -209,7 +212,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 FileReader(netrc))) {
                        String line = null;
index 480055c2e41947127d3d8ca59de120d706a1db4d..4dd5df9cd6970b855b06c477fe194a1de8648094 100644 (file)
@@ -51,6 +51,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -166,7 +167,7 @@ public class OpenSshConfig implements ConfigRepository {
        private final File configFile;
 
        /** 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
@@ -224,8 +225,8 @@ public class OpenSshConfig implements ConfigRepository {
        }
 
        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 (FileInputStream in = new FileInputStream(configFile)) {
                                newState.entries = parse(in);
index 3d25c2314e7b534efb5bf8a6524b40c16d3d1b35..d432c94450eb91d8b7d4dedb49b502773eef9103 100644 (file)
@@ -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
index 7c6bfb9d63935625ef7afc32073bd7197c3b112f..299f07fb09c1e9b1e78fda1a1e61582403252782 100644 (file)
@@ -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;
@@ -645,11 +646,23 @@ 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>
@@ -924,30 +937,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;
        }
 
        /**
@@ -1274,9 +1285,25 @@ 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>
index 6e3e336e94264c1b4b22ed381bfe2d04cba8a038..e8570d741715eec1f0f89b0597c85b445de09981 100644 (file)
@@ -44,6 +44,7 @@
 package org.eclipse.jgit.util;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
 import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION;
 
 import java.io.BufferedReader;
@@ -66,6 +67,7 @@ import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.text.MessageFormat;
 import java.time.Duration;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -625,11 +627,41 @@ 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,
@@ -640,11 +672,28 @@ 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.
@@ -1712,9 +1761,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;
@@ -1725,7 +1784,7 @@ public abstract class FS {
 
                private final long creationTime;
 
-               private final long lastModifiedTime;
+               private final Instant lastModifiedInstant;
 
                private final boolean isExecutable;
 
@@ -1743,7 +1802,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;
@@ -1752,7 +1811,7 @@ public abstract class FS {
                        this.isSymbolicLink = isSymbolicLink;
                        this.isRegularFile = isRegularFile;
                        this.creationTime = creationTime;
-                       this.lastModifiedTime = lastModifiedTime;
+                       this.lastModifiedInstant = lastModifiedInstant;
                        this.length = length;
                }
 
@@ -1764,7 +1823,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);
                }
 
                /**
@@ -1810,7 +1869,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);
index 98797dc64fff67ebf8d69888d358b356293c0d44..7c072703633a65f78144e3fdf23f2c302989d8fa 100644 (file)
@@ -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));
index 9650602fea5bc16ffd6c772a16fd0ea9832a22e6..80f188cb2cc1208c1aa3cb176f85b6d919b77438 100644 (file)
@@ -76,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}
@@ -656,12 +659,31 @@ 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.
         *
@@ -680,10 +702,21 @@ 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
@@ -788,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());
@@ -827,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) {