Browse Source

Use Instant instead of milliseconds for filesystem timestamp handling

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>
stable-5.1
Matthias Sohn 5 years ago
parent
commit
95e8264cc8
  1. 8
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
  2. 9
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
  3. 11
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
  4. 19
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java
  5. 99
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java
  6. 22
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java
  7. 5
      org.eclipse.jgit.test/META-INF/MANIFEST.MF
  8. 2
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
  9. 22
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java
  10. 24
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
  11. 4
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java
  12. 28
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
  13. 15
      org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java
  14. 7
      org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java
  15. 14
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java
  16. 23
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
  17. 7
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
  18. 20
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
  19. 24
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
  20. 8
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
  21. 12
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
  22. 31
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java
  23. 14
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java
  24. 47
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
  25. 7
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java
  26. 22
      org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java
  27. 8
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java
  28. 62
      org.eclipse.jgit/.settings/.api_filters
  29. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  30. 5
      org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
  31. 2
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  32. 2
      org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
  33. 2
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
  34. 3
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
  35. 11
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
  36. 13
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
  37. 47
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
  38. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  39. 126
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
  40. 9
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
  41. 21
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
  42. 7
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
  43. 45
      org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
  44. 9
      org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java
  45. 7
      org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java
  46. 9
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
  47. 67
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
  48. 71
      org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
  49. 2
      org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
  50. 37
      org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java

8
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java

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

9
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java

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

11
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java

@ -51,6 +51,7 @@ import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -322,12 +323,13 @@ public abstract class LocalDiskRepositoryTestCase {
throws IllegalStateException, IOException { throws IllegalStateException, IOException {
DirCache dc = repo.readDirCache(); DirCache dc = repo.readDirCache();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
TreeSet<Long> timeStamps = new TreeSet<>(); TreeSet<Instant> timeStamps = new TreeSet<>();
// iterate once over the dircache just to collect all time stamps // iterate once over the dircache just to collect all time stamps
if (0 != (includedOptions & MOD_TIME)) { if (0 != (includedOptions & MOD_TIME)) {
for (int i=0; i<dc.getEntryCount(); ++i) for (int i = 0; i < dc.getEntryCount(); ++i) {
timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
}
} }
// iterate again, now produce the result string // iterate again, now produce the result string
@ -339,7 +341,8 @@ public abstract class LocalDiskRepositoryTestCase {
sb.append(", stage:" + stage); sb.append(", stage:" + stage);
if (0 != (includedOptions & MOD_TIME)) { if (0 != (includedOptions & MOD_TIME)) {
sb.append(", time:t"+ sb.append(", time:t"+
timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); timeStamps.headSet(entry.getLastModifiedInstant())
.size());
} }
if (0 != (includedOptions & SMUDGE)) if (0 != (includedOptions & SMUDGE))
if (entry.isSmudged()) if (entry.isSmudged())

19
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java

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

99
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java

@ -0,0 +1,99 @@
/*
* Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.junit.time;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import org.eclipse.jgit.util.FS;
/**
* Utility methods for handling timestamps
*/
public class TimeUtil {
/**
* Set the lastModified time of a given file by adding a given offset to the
* current lastModified time
*
* @param path
* path of a file to set last modified
* @param offsetMillis
* offset in milliseconds, if negative the new lastModified time
* is offset before the original lastModified time, otherwise
* after the original time
* @return the new lastModified time
*/
public static Instant setLastModifiedWithOffset(Path path,
long offsetMillis) {
Instant mTime = FS.DETECTED.lastModifiedInstant(path)
.plusMillis(offsetMillis);
try {
Files.setLastModifiedTime(path, FileTime.from(mTime));
return mTime;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Set the lastModified time of file a to the one from file b
*
* @param a
* file to set lastModified time
* @param b
* file to read lastModified time from
*/
public static void setLastModifiedOf(Path a, Path b) {
Instant mTime = FS.DETECTED.lastModifiedInstant(b);
try {
Files.setLastModifiedTime(a, FileTime.from(mTime));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

22
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java

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

5
org.eclipse.jgit.test/META-INF/MANIFEST.MF

@ -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.reftable;version="[5.1.9,5.2.0)",
org.eclipse.jgit.internal.storage.reftree;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;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.lfs;version="[5.1.9,5.2.0)",
org.eclipse.jgit.lib;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)", 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;version="[5.1.9,5.2.0)",
org.eclipse.jgit.util.io;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.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;version="[4.12,5.0.0)",
org.junit.experimental.theories;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.rules;version="[4.12,5.0.0)",
org.junit.runner;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.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)", Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)",
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" org.hamcrest.library;bundle-version="[1.1.0,2.0.0)"

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java

@ -1266,7 +1266,7 @@ public class AddCommandTest extends RepositoryTestCase {
DirCacheEntry entry = new DirCacheEntry(path, stage); DirCacheEntry entry = new DirCacheEntry(path, stage);
entry.setObjectId(id); entry.setObjectId(id);
entry.setFileMode(FileMode.REGULAR_FILE); entry.setFileMode(FileMode.REGULAR_FILE);
entry.setLastModified(file.lastModified()); entry.setLastModified(FS.DETECTED.lastModifiedInstant(file));
entry.setLength((int) file.length()); entry.setLength((int) file.length());
builder.add(entry); builder.add(entry);

22
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java

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

24
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java

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

4
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DiffCommandTest.java

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

28
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java

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

15
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java vendored

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

7
org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java vendored

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

14
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ConcurrentRepackTest.java

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

23
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java

@ -51,9 +51,11 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.junit.After; import org.junit.After;
@ -80,11 +82,12 @@ public class FileSnapshotTest {
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
} }
private static void waitNextSec(File f) { private static void waitNextTick(File f) throws IOException {
long initialLastModified = f.lastModified(); Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f);
do { do {
f.setLastModified(System.currentTimeMillis()); FS.DETECTED.setLastModified(f.toPath(), Instant.now());
} while (f.lastModified() == initialLastModified); } while (FS.DETECTED.lastModifiedInstant(f)
.equals(initialLastModified));
} }
/** /**
@ -95,10 +98,10 @@ public class FileSnapshotTest {
@Test @Test
public void testActuallyIsModifiedTrivial() throws Exception { public void testActuallyIsModifiedTrivial() throws Exception {
File f1 = createFile("simple"); File f1 = createFile("simple");
waitNextSec(f1); waitNextTick(f1);
FileSnapshot save = FileSnapshot.save(f1); FileSnapshot save = FileSnapshot.save(f1);
append(f1, (byte) 'x'); append(f1, (byte) 'x');
waitNextSec(f1); waitNextTick(f1);
assertTrue(save.isModified(f1)); assertTrue(save.isModified(f1));
} }
@ -113,7 +116,7 @@ public class FileSnapshotTest {
@Test @Test
public void testNewFileWithWait() throws Exception { public void testNewFileWithWait() throws Exception {
File f1 = createFile("newfile"); File f1 = createFile("newfile");
waitNextSec(f1); waitNextTick(f1);
FileSnapshot save = FileSnapshot.save(f1); FileSnapshot save = FileSnapshot.save(f1);
Thread.sleep(1500); Thread.sleep(1500);
assertTrue(save.isModified(f1)); assertTrue(save.isModified(f1));
@ -146,8 +149,8 @@ public class FileSnapshotTest {
File f2 = createFile("fool"); // Guarantees new inode x File f2 = createFile("fool"); // Guarantees new inode x
// wait on f2 since this method resets lastModified of the file // wait on f2 since this method resets lastModified of the file
// and leaves lastModified of f1 untouched // and leaves lastModified of f1 untouched
waitNextSec(f2); waitNextTick(f2);
waitNextSec(f2); waitNextTick(f2);
FileTime timestamp = Files.getLastModifiedTime(f1.toPath()); FileTime timestamp = Files.getLastModifiedTime(f1.toPath());
FileSnapshot save = FileSnapshot.save(f1); FileSnapshot save = FileSnapshot.save(f1);
Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x
@ -185,7 +188,7 @@ public class FileSnapshotTest {
// 0 sized FileSnapshot. // 0 sized FileSnapshot.
FileSnapshot fs1 = FileSnapshot.MISSING_FILE; FileSnapshot fs1 = FileSnapshot.MISSING_FILE;
// UNKNOWN_SIZE FileSnapshot. // UNKNOWN_SIZE FileSnapshot.
FileSnapshot fs2 = FileSnapshot.save(fs1.lastModified()); FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant());
assertTrue(fs1.equals(fs2)); assertTrue(fs1.equals(fs2));
assertTrue(fs2.equals(fs1)); assertTrue(fs2.equals(fs1));

7
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java

@ -147,9 +147,10 @@ public abstract class GcTestCase extends LocalDiskRepositoryTestCase {
return tip; return tip;
} }
protected long lastModified(AnyObjectId objectId) throws IOException { protected long lastModified(AnyObjectId objectId) {
return repo.getFS().lastModified( return repo.getFS()
repo.getObjectDatabase().fileFor(objectId)); .lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId))
.toEpochMilli();
} }
protected static void fsTick() throws InterruptedException, IOException { protected static void fsTick() throws InterruptedException, IOException {

20
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java

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

24
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java

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

8
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java

@ -59,6 +59,7 @@ import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
@ -78,6 +79,7 @@ import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.util.FS;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -1319,10 +1321,8 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
private void writePackedRefs(String content) throws IOException { private void writePackedRefs(String content) throws IOException {
File pr = new File(diskRepo.getDirectory(), "packed-refs"); File pr = new File(diskRepo.getDirectory(), "packed-refs");
write(pr, content); write(pr, content);
FS fs = diskRepo.getFS();
final long now = System.currentTimeMillis(); fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600));
final int oneHourAgo = 3600 * 1000;
pr.setLastModified(now - oneHourAgo);
} }
private void deleteLooseRef(String name) { private void deleteLooseRef(String name) {

12
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java

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

31
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexModificationTimesTest.java

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

14
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RacyGitTests.java

@ -50,12 +50,15 @@ import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.time.TimeUtil;
import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.util.FS;
import org.junit.Test; import org.junit.Test;
public class RacyGitTests extends RepositoryTestCase { public class RacyGitTests extends RepositoryTestCase {
@ -74,8 +77,8 @@ public class RacyGitTests extends RepositoryTestCase {
// create two files // create two files
File a = writeToWorkDir("a", "a"); File a = writeToWorkDir("a", "a");
File b = writeToWorkDir("b", "b"); File b = writeToWorkDir("b", "b");
assertTrue(a.setLastModified(b.lastModified())); TimeUtil.setLastModifiedOf(a.toPath(), b.toPath());
assertTrue(b.setLastModified(b.lastModified())); TimeUtil.setLastModifiedOf(b.toPath(), b.toPath());
// wait to ensure that file-modTimes and therefore index entry modTime // wait to ensure that file-modTimes and therefore index entry modTime
// doesn't match the modtime of index-file after next persistance // doesn't match the modtime of index-file after next persistance
@ -97,10 +100,11 @@ public class RacyGitTests extends RepositoryTestCase {
// filesystem timestamp resolution. By changing the index file // filesystem timestamp resolution. By changing the index file
// artificially, we create a fake racy situation. // artificially, we create a fake racy situation.
File updatedA = writeToWorkDir("a", "a2"); File updatedA = writeToWorkDir("a", "a2");
long newLastModified = updatedA.lastModified() + 100; Instant newLastModified = TimeUtil
assertTrue(updatedA.setLastModified(newLastModified)); .setLastModifiedWithOffset(updatedA.toPath(), 100L);
resetIndex(new FileTreeIterator(db)); resetIndex(new FileTreeIterator(db));
assertTrue(db.getIndexFile().setLastModified(newLastModified)); FS.DETECTED.setLastModified(db.getIndexFile().toPath(),
newLastModified);
DirCache dc = db.readDirCache(); DirCache dc = db.readDirCache();
// check index state: although racily clean a should not be reported as // check index state: although racily clean a should not be reported as

47
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java

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

7
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java

@ -57,10 +57,12 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.time.Instant;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.OpenSshConfig.Host; import org.eclipse.jgit.transport.OpenSshConfig.Host;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.junit.Before; import org.junit.Before;
@ -91,13 +93,14 @@ public class OpenSshConfigTest extends RepositoryTestCase {
} }
private void config(String data) throws IOException { private void config(String data) throws IOException {
long lastMtime = configFile.lastModified(); FS fs = db.getFS();
Instant lastMtime = fs.lastModifiedInstant(configFile);
do { do {
try (final OutputStreamWriter fw = new OutputStreamWriter( try (final OutputStreamWriter fw = new OutputStreamWriter(
new FileOutputStream(configFile), UTF_8)) { new FileOutputStream(configFile), UTF_8)) {
fw.write(data); fw.write(data);
} }
} while (lastMtime == configFile.lastModified()); } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
} }
@Test @Test

22
org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java

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

8
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java

@ -43,6 +43,7 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import static java.time.Instant.EPOCH;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -106,7 +107,7 @@ public class FSTest {
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
String targetName = fs.readSymLink(link); String targetName = fs.readSymLink(link);
assertEquals("å", targetName); assertEquals("å", targetName);
assertTrue(fs.lastModified(link) > 0); assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));
assertEquals(2, fs.length(link)); assertEquals(2, fs.length(link));
@ -119,8 +120,9 @@ public class FSTest {
// Now create the link target // Now create the link target
FileUtils.createNewFile(target); FileUtils.createNewFile(target);
assertTrue(fs.exists(link)); assertTrue(fs.exists(link));
assertTrue(fs.lastModified(link) > 0); assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0);
assertTrue(fs.lastModified(target) > fs.lastModified(link)); assertTrue(fs.lastModifiedInstant(target)
.compareTo(fs.lastModifiedInstant(link)) > 0);
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));
fs.setExecute(target, true); fs.setExecute(target, true);
assertFalse(fs.canExecute(link)); assertFalse(fs.canExecute(link));

62
org.eclipse.jgit/.settings/.api_filters

@ -8,6 +8,20 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="setLastModified(Instant)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException"> <resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
@ -130,6 +144,28 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getEntryLastModifiedInstant()"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry">
<filter id="336695337">
<message_arguments>
<message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="getLastModifiedInstant()"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS"> <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
@ -137,12 +173,30 @@
<message_argument value="fileAttributes(File)"/> <message_argument value="fileAttributes(File)"/>
</message_arguments> </message_arguments>
</filter> </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"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.1.9"/> <message_argument value="5.1.9"/>
<message_argument value="setAsyncfileStoreAttrCache(boolean)"/> <message_argument value="setAsyncfileStoreAttrCache(boolean)"/>
</message_arguments> </message_arguments>
</filter> </filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="setLastModified(Path, Instant)"/>
</message_arguments>
</filter>
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.2.3"/> <message_argument value="5.2.3"/>
@ -150,6 +204,14 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </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"> <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -561,6 +561,7 @@ rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry
readConfigFailed=Reading config file ''{0}'' failed readConfigFailed=Reading config file ''{0}'' failed
readerIsRequired=Reader is required readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
readLastModifiedFailed=Reading lastModified of {0} failed
readTimedOut=Read timed out after {0} ms readTimedOut=Read timed out after {0} ms
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes. receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes.

5
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java

@ -50,6 +50,7 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
@ -228,7 +229,7 @@ public class AddCommand extends GitCommand<DirCache> {
if (GITLINK != mode) { if (GITLINK != mode) {
entry.setLength(f.getEntryLength()); entry.setLength(f.getEntryLength());
entry.setLastModified(f.getEntryLastModified()); entry.setLastModified(f.getEntryLastModifiedInstant());
long len = f.getEntryContentLength(); long len = f.getEntryContentLength();
// We read and filter the content multiple times. // We read and filter the content multiple times.
// f.getEntryContentLength() reads and filters the input and // f.getEntryContentLength() reads and filters the input and
@ -241,7 +242,7 @@ public class AddCommand extends GitCommand<DirCache> {
} }
} else { } else {
entry.setLength(0); entry.setLength(0);
entry.setLastModified(0); entry.setLastModified(Instant.ofEpochSecond(0));
entry.setObjectId(f.getEntryObjectId()); entry.setObjectId(f.getEntryObjectId());
} }
builder.add(entry); builder.add(entry);

2
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java

@ -392,7 +392,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
final DirCacheEntry dcEntry = new DirCacheEntry(path); final DirCacheEntry dcEntry = new DirCacheEntry(path);
long entryLength = fTree.getEntryLength(); long entryLength = fTree.getEntryLength();
dcEntry.setLength(entryLength); dcEntry.setLength(entryLength);
dcEntry.setLastModified(fTree.getEntryLastModified()); dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
boolean objectExists = (dcTree != null boolean objectExists = (dcTree != null

2
org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java

@ -422,7 +422,7 @@ public class ResetCommand extends GitCommand<Ref> {
DirCacheIterator.class); DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) { if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
entry.setLastModified(indexEntry.getLastModified()); entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength()); entry.setLength(indexEntry.getLength());
} }

2
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java

@ -332,7 +332,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
DirCacheIterator.class); DirCacheIterator.class);
if (dcIter != null && dcIter.idEqual(cIter)) { if (dcIter != null && dcIter.idEqual(cIter)) {
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
entry.setLastModified(indexEntry.getLastModified()); entry.setLastModified(indexEntry.getLastModifiedInstant());
entry.setLength(indexEntry.getLength()); entry.setLength(indexEntry.getLength());
} }

3
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java

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

11
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java vendored

@ -497,8 +497,9 @@ public class DirCache {
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
snapshot = FileSnapshot.save(liveFile); snapshot = FileSnapshot.save(liveFile);
int smudge_s = (int) (snapshot.lastModified() / 1000); // TODO (ms) combine smudge_s and smudge_ns into Duration
int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; int smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond());
int smudge_ns = snapshot.lastModifiedInstant().getNano();
// Load the individual file entries. // Load the individual file entries.
// //
@ -679,8 +680,8 @@ public class DirCache {
// so we use the current timestamp as a approximation. // so we use the current timestamp as a approximation.
myLock.createCommitSnapshot(); myLock.createCommitSnapshot();
snapshot = myLock.getCommitSnapshot(); snapshot = myLock.getCommitSnapshot();
smudge_s = (int) (snapshot.lastModified() / 1000); smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond());
smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; smudge_ns = snapshot.lastModifiedInstant().getNano();
} else { } else {
// Used in unit tests only // Used in unit tests only
smudge_ns = 0; smudge_ns = 0;
@ -1025,7 +1026,7 @@ public class DirCache {
DirCacheEntry entry = iIter.getDirCacheEntry(); DirCacheEntry entry = iIter.getDirCacheEntry();
if (entry.isSmudged() && iIter.idEqual(fIter)) { if (entry.isSmudged() && iIter.idEqual(fIter)) {
entry.setLength(fIter.getEntryLength()); entry.setLength(fIter.getEntryLength());
entry.setLastModified(fIter.getEntryLastModified()); entry.setLastModified(fIter.getEntryLastModifiedInstant());
} }
} }
} }

13
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java vendored

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

47
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java vendored

@ -56,6 +56,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
@ -144,6 +145,7 @@ public class DirCacheEntry {
/** Flags which are never stored to disk. */ /** Flags which are never stored to disk. */
private byte inCoreFlags; private byte inCoreFlags;
// TODO (ms): use Instant to combine smudge_s and smudge_ns
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt,
final InputStream in, final MessageDigest md, final int smudge_s, final InputStream in, final MessageDigest md, final int smudge_s,
final int smudge_ns) throws IOException { final int smudge_ns) throws IOException {
@ -563,21 +565,50 @@ public class DirCacheEntry {
* *
* @return last modification time of this file, in milliseconds since the * @return last modification time of this file, in milliseconds since the
* Java epoch (midnight Jan 1, 1970 UTC). * Java epoch (midnight Jan 1, 1970 UTC).
* @deprecated use {@link #getLastModifiedInstant()} instead
*/ */
@Deprecated
public long getLastModified() { public long getLastModified() {
return decodeTS(P_MTIME); return decodeTS(P_MTIME);
} }
/**
* Get the cached last modification date of this file.
* <p>
* One of the indicators that the file has been modified by an application
* changing the working tree is if the last modification time for the file
* differs from the time stored in this entry.
*
* @return last modification time of this file.
* @since 5.1.9
*/
public Instant getLastModifiedInstant() {
return decodeTSInstant(P_MTIME);
}
/** /**
* Set the cached last modification date of this file, using milliseconds. * Set the cached last modification date of this file, using milliseconds.
* *
* @param when * @param when
* new cached modification date of the file, in milliseconds. * new cached modification date of the file, in milliseconds.
* @deprecated use {@link #setLastModified(Instant)} instead
*/ */
@Deprecated
public void setLastModified(long when) { public void setLastModified(long when) {
encodeTS(P_MTIME, when); encodeTS(P_MTIME, when);
} }
/**
* Set the cached last modification date of this file.
*
* @param when
* new cached modification date of the file.
* @since 5.1.9
*/
public void setLastModified(Instant when) {
encodeTS(P_MTIME, when);
}
/** /**
* Get the cached size (mod 4 GB) (in bytes) of this file. * Get the cached size (mod 4 GB) (in bytes) of this file.
* <p> * <p>
@ -692,7 +723,8 @@ public class DirCacheEntry {
@SuppressWarnings("nls") @SuppressWarnings("nls")
@Override @Override
public String toString() { public String toString() {
return getFileMode() + " " + getLength() + " " + getLastModified() return getFileMode() + " " + getLength() + " "
+ getLastModifiedInstant()
+ " " + getObjectId() + " " + getStage() + " " + " " + getObjectId() + " " + getStage() + " "
+ getPathString() + "\n"; + getPathString() + "\n";
} }
@ -750,12 +782,25 @@ public class DirCacheEntry {
return 1000L * sec + ms; return 1000L * sec + ms;
} }
private Instant decodeTSInstant(int pIdx) {
final int base = infoOffset + pIdx;
final int sec = NB.decodeInt32(info, base);
final int nano = NB.decodeInt32(info, base + 4);
return Instant.ofEpochSecond(sec, nano);
}
private void encodeTS(int pIdx, long when) { private void encodeTS(int pIdx, long when) {
final int base = infoOffset + pIdx; final int base = infoOffset + pIdx;
NB.encodeInt32(info, base, (int) (when / 1000)); NB.encodeInt32(info, base, (int) (when / 1000));
NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
} }
private void encodeTS(int pIdx, Instant when) {
final int base = infoOffset + pIdx;
NB.encodeInt32(info, base, (int) when.getEpochSecond());
NB.encodeInt32(info, base + 4, when.getNano());
}
private int getExtendedFlags() { private int getExtendedFlags() {
if (isExtended()) if (isExtended())
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -622,6 +622,7 @@ public class JGitText extends TranslationBundle {
/***/ public String readConfigFailed; /***/ public String readConfigFailed;
/***/ public String readerIsRequired; /***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed; /***/ public String readingObjectsFromLocalRepositoryFailed;
/***/ public String readLastModifiedFailed;
/***/ public String readTimedOut; /***/ public String readTimedOut;
/***/ public String receivePackObjectTooLarge1; /***/ public String receivePackObjectTooLarge1;
/***/ public String receivePackObjectTooLarge2; /***/ public String receivePackObjectTooLarge2;

126
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java

@ -48,10 +48,10 @@ import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration; import java.time.Duration;
import java.util.Date; import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -87,8 +87,14 @@ public class FileSnapshot {
*/ */
public static final long UNKNOWN_SIZE = -1; public static final long UNKNOWN_SIZE = -1;
private static final Instant UNKNOWN_TIME = Instant.ofEpochMilli(-1);
private static final Object MISSING_FILEKEY = new Object(); private static final Object MISSING_FILEKEY = new Object();
private static final DateTimeFormatter dateFmt = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$
.withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
/** /**
* A FileSnapshot that is considered to always be modified. * A FileSnapshot that is considered to always be modified.
* <p> * <p>
@ -96,8 +102,8 @@ public class FileSnapshot {
* file, but only after {@link #isModified(File)} gets invoked. The returned * file, but only after {@link #isModified(File)} gets invoked. The returned
* snapshot contains only invalid status information. * snapshot contains only invalid status information.
*/ */
public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME,
UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
/** /**
* A FileSnapshot that is clean if the file does not exist. * A FileSnapshot that is clean if the file does not exist.
@ -106,8 +112,8 @@ public class FileSnapshot {
* file to be clean. {@link #isModified(File)} will return false if the file * file to be clean. {@link #isModified(File)} will return false if the file
* path does not exist. * path does not exist.
*/ */
public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0, public static final FileSnapshot MISSING_FILE = new FileSnapshot(
Duration.ZERO, MISSING_FILEKEY) { Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) {
@Override @Override
public boolean isModified(File path) { public boolean isModified(File path) {
return FS.DETECTED.exists(path); return FS.DETECTED.exists(path);
@ -163,18 +169,41 @@ public class FileSnapshot {
* @param modified * @param modified
* the last modification time of the file * the last modification time of the file
* @return the snapshot. * @return the snapshot.
* @deprecated use {@link #save(Instant)} instead.
*/ */
@Deprecated
public static FileSnapshot save(long modified) { public static FileSnapshot save(long modified) {
final long read = System.currentTimeMillis(); final Instant read = Instant.now();
return new FileSnapshot(read, 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, return new FileSnapshot(read, modified, UNKNOWN_SIZE, Duration.ZERO,
MISSING_FILEKEY); MISSING_FILEKEY);
} }
/** Last observed modification time of the path. */ /** Last observed modification time of the path. */
private final long lastModified; private final Instant lastModified;
/** Last wall-clock time the path was read. */ /** Last wall-clock time the path was read. */
private volatile long lastRead; private volatile Instant lastRead;
/** True once {@link #lastRead} is far later than {@link #lastModified}. */ /** True once {@link #lastRead} is far later than {@link #lastModified}. */
private boolean cannotBeRacilyClean; private boolean cannotBeRacilyClean;
@ -222,7 +251,7 @@ public class FileSnapshot {
*/ */
protected FileSnapshot(File file, boolean useConfig) { protected FileSnapshot(File file, boolean useConfig) {
this.file = file; this.file = file;
this.lastRead = System.currentTimeMillis(); this.lastRead = Instant.now();
this.fsTimestampResolution = useConfig this.fsTimestampResolution = useConfig
? FS.getFsTimerResolution(file.toPath().getParent()) ? FS.getFsTimerResolution(file.toPath().getParent())
: FALLBACK_TIMESTAMP_RESOLUTION; : FALLBACK_TIMESTAMP_RESOLUTION;
@ -230,18 +259,20 @@ public class FileSnapshot {
try { try {
fileAttributes = FS.DETECTED.fileAttributes(file); fileAttributes = FS.DETECTED.fileAttributes(file);
} catch (IOException e) { } catch (IOException e) {
this.lastModified = file.lastModified(); this.lastModified = Instant.ofEpochMilli(file.lastModified());
this.size = file.length(); this.size = file.length();
this.fileKey = MISSING_FILEKEY; this.fileKey = MISSING_FILEKEY;
return; return;
} }
this.lastModified = fileAttributes.lastModifiedTime().toMillis(); this.lastModified = fileAttributes.lastModifiedTime().toInstant();
this.size = fileAttributes.size(); this.size = fileAttributes.size();
this.fileKey = getFileKey(fileAttributes); this.fileKey = getFileKey(fileAttributes);
LOG.debug("file={}, create new FileSnapshot: lastRead={} ms" //$NON-NLS-1$ if (LOG.isDebugEnabled()) {
+ ", lastModified={} ms, size={}, fileKey={}", //$NON-NLS-1$ LOG.debug("file={}, create new FileSnapshot: lastRead={}, lastModified={}, size={}, fileKey={}", //$NON-NLS-1$
file, Long.valueOf(lastRead), Long.valueOf(lastModified), file, dateFmt.format(lastRead),
Long.valueOf(size), fileKey); dateFmt.format(lastModified), Long.valueOf(size),
fileKey.toString());
}
} }
private boolean sizeChanged; private boolean sizeChanged;
@ -252,7 +283,7 @@ public class FileSnapshot {
private boolean wasRacyClean; 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) { @NonNull Duration fsTimestampResolution, @NonNull Object fileKey) {
this.file = null; this.file = null;
this.lastRead = read; this.lastRead = read;
@ -266,8 +297,19 @@ public class FileSnapshot {
* Get time of last snapshot update * Get time of last snapshot update
* *
* @return time of last snapshot update * @return time of last snapshot update
* @deprecated use {@link #lastModifiedInstant()} instead
*/ */
@Deprecated
public long lastModified() { public long lastModified() {
return lastModified.toEpochMilli();
}
/**
* Get time of last snapshot update
*
* @return time of last snapshot update
*/
public Instant lastModifiedInstant() {
return lastModified; return lastModified;
} }
@ -286,16 +328,16 @@ public class FileSnapshot {
* @return true if the path needs to be read again. * @return true if the path needs to be read again.
*/ */
public boolean isModified(File path) { public boolean isModified(File path) {
long currLastModified; Instant currLastModified;
long currSize; long currSize;
Object currFileKey; Object currFileKey;
try { try {
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
currLastModified = fileAttributes.lastModifiedTime().toMillis(); currLastModified = fileAttributes.lastModifiedTime().toInstant();
currSize = fileAttributes.size(); currSize = fileAttributes.size();
currFileKey = getFileKey(fileAttributes); currFileKey = getFileKey(fileAttributes);
} catch (IOException e) { } catch (IOException e) {
currLastModified = path.lastModified(); currLastModified = Instant.ofEpochMilli(path.lastModified());
currSize = path.length(); currSize = path.length();
currFileKey = MISSING_FILEKEY; currFileKey = MISSING_FILEKEY;
} }
@ -337,7 +379,7 @@ public class FileSnapshot {
* the other snapshot. * the other snapshot.
*/ */
public void setClean(FileSnapshot other) { public void setClean(FileSnapshot other) {
final long now = other.lastRead; final Instant now = other.lastRead;
if (!isRacyClean(now)) { if (!isRacyClean(now)) {
cannotBeRacilyClean = true; cannotBeRacilyClean = true;
} }
@ -351,7 +393,7 @@ public class FileSnapshot {
* if sleep was interrupted * if sleep was interrupted
*/ */
public void waitUntilNotRacy() throws InterruptedException { public void waitUntilNotRacy() throws InterruptedException {
while (isRacyClean(System.currentTimeMillis())) { while (isRacyClean(Instant.now())) {
TimeUnit.NANOSECONDS TimeUnit.NANOSECONDS
.sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10); .sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10);
} }
@ -366,7 +408,7 @@ public class FileSnapshot {
*/ */
public boolean equals(FileSnapshot other) { public boolean equals(FileSnapshot other) {
boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size; 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); && Objects.equals(fileKey, other.fileKey);
} }
@ -389,8 +431,7 @@ public class FileSnapshot {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size), return Objects.hash(lastModified, Long.valueOf(size), fileKey);
fileKey);
} }
/** /**
@ -435,34 +476,37 @@ public class FileSnapshot {
if (this == MISSING_FILE) { if (this == MISSING_FILE) {
return "MISSING_FILE"; return "MISSING_FILE";
} }
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", return "FileSnapshot[modified: " + dateFmt.format(lastModified)
Locale.US); + ", read: " + dateFmt.format(lastRead) + ", size:" + size
return "FileSnapshot[modified: " + f.format(new Date(lastModified))
+ ", read: " + f.format(new Date(lastRead)) + ", size:" + size
+ ", fileKey: " + fileKey + "]"; + ", fileKey: " + fileKey + "]";
} }
private boolean isRacyClean(long read) { private boolean isRacyClean(Instant read) {
// add a 10% safety margin // add a 10% safety margin
long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10; long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10;
long delta = (read - lastModified) * 1_000_000; long delta = Duration.between(lastModified, read).toNanos();
wasRacyClean = delta <= racyNanos; wasRacyClean = delta <= racyNanos;
LOG.debug("file={}, isRacyClean={}, read={} ms, lastModified={} ms," //$NON-NLS-1$ if (LOG.isDebugEnabled()) {
+ " delta={} ns, racy<={} ns", //$NON-NLS-1$ LOG.debug(
file, Boolean.valueOf(wasRacyClean), Long.valueOf(read), "file={}, isRacyClean={}, read={}, lastModified={}, delta={} ns, racy<={} ns", //$NON-NLS-1$
Long.valueOf(lastModified), Long.valueOf(delta), file, Boolean.valueOf(wasRacyClean), dateFmt.format(read),
dateFmt.format(lastModified), Long.valueOf(delta),
Long.valueOf(racyNanos)); Long.valueOf(racyNanos));
}
return wasRacyClean; return wasRacyClean;
} }
private boolean isModified(long currLastModified) { private boolean isModified(Instant currLastModified) {
// Any difference indicates the path was modified. // Any difference indicates the path was modified.
lastModifiedChanged = lastModified != currLastModified; lastModifiedChanged = !lastModified.equals(currLastModified);
if (lastModifiedChanged) { if (lastModifiedChanged) {
LOG.debug("file={}, lastModified changed from {} to {}", //$NON-NLS-1$ if (LOG.isDebugEnabled()) {
file, Long.valueOf(lastModified), LOG.debug(
Long.valueOf(currLastModified)); "file={}, lastModified changed from {} to {}", //$NON-NLS-1$
file, dateFmt.format(lastModified),
dateFmt.format(currLastModified));
}
return true; return true;
} }

9
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java

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

21
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java

@ -56,8 +56,11 @@ import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -424,7 +427,12 @@ public class LockFile {
FileSnapshot n = FileSnapshot.save(lck); FileSnapshot n = FileSnapshot.save(lck);
while (o.equals(n)) { while (o.equals(n)) {
Thread.sleep(25 /* milliseconds */); 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); n = FileSnapshot.save(lck);
} }
} }
@ -474,11 +482,22 @@ public class LockFile {
* Get the modification time of the output file when it was committed. * Get the modification time of the output file when it was committed.
* *
* @return modification time of the lock file right before we committed it. * @return modification time of the lock file right before we committed it.
* @deprecated use {@link #getCommitLastModifiedInstant()} instead
*/ */
@Deprecated
public long getCommitLastModified() { public long getCommitLastModified() {
return commitSnapshot.lastModified(); return commitSnapshot.lastModified();
} }
/**
* Get the modification time of the output file when it was committed.
*
* @return modification time of the lock file right before we committed it.
*/
public Instant getCommitLastModifiedInstant() {
return commitSnapshot.lastModifiedInstant();
}
/** /**
* Get the {@link FileSnapshot} just before commit. * Get the {@link FileSnapshot} just before commit.
* *

7
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java

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

45
org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java

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

9
org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRC.java

@ -46,6 +46,7 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -123,7 +124,7 @@ public class NetRC {
private File netrc; private File netrc;
private long lastModified; private Instant lastModified;
private Map<String, NetRCEntry> hosts = new HashMap<>(); private Map<String, NetRCEntry> hosts = new HashMap<>();
@ -187,8 +188,10 @@ public class NetRC {
if (netrc == null) if (netrc == null)
return null; return null;
if (this.lastModified != this.netrc.lastModified()) if (!this.lastModified
.equals(FS.DETECTED.lastModifiedInstant(this.netrc))) {
parse(); parse();
}
NetRCEntry entry = this.hosts.get(host); NetRCEntry entry = this.hosts.get(host);
@ -209,7 +212,7 @@ public class NetRC {
private void parse() { private void parse() {
this.hosts.clear(); this.hosts.clear();
this.lastModified = this.netrc.lastModified(); this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc);
try (BufferedReader r = new BufferedReader(new FileReader(netrc))) { try (BufferedReader r = new BufferedReader(new FileReader(netrc))) {
String line = null; String line = null;

7
org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java

@ -51,6 +51,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -166,7 +167,7 @@ public class OpenSshConfig implements ConfigRepository {
private final File configFile; private final File configFile;
/** Modification time of {@link #configFile} when it was last loaded. */ /** Modification time of {@link #configFile} when it was last loaded. */
private long lastModified; private Instant lastModified;
/** /**
* Encapsulates entries read out of the configuration file, and * Encapsulates entries read out of the configuration file, and
@ -224,8 +225,8 @@ public class OpenSshConfig implements ConfigRepository {
} }
private synchronized State refresh() { private synchronized State refresh() {
final long mtime = configFile.lastModified(); final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile);
if (mtime != lastModified) { if (!mtime.equals(lastModified)) {
State newState = new State(); State newState = new State();
try (FileInputStream in = new FileInputStream(configFile)) { try (FileInputStream in = new FileInputStream(configFile)) {
newState.entries = parse(in); newState.entries = parse(in);

9
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java

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

67
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java

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

71
org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java

@ -44,6 +44,7 @@
package org.eclipse.jgit.util; package org.eclipse.jgit.util;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.EPOCH;
import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION; import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -66,6 +67,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -625,11 +627,41 @@ public abstract class FS {
* @return last modified time of f * @return last modified time of f
* @throws java.io.IOException * @throws java.io.IOException
* @since 3.0 * @since 3.0
* @deprecated use {@link #lastModifiedInstant(Path)} instead
*/ */
@Deprecated
public long lastModified(File f) throws IOException { public long lastModified(File f) throws IOException {
return FileUtils.lastModified(f); return FileUtils.lastModified(f);
} }
/**
* Get the last modified time of a file system object. If the OS/JRE support
* symbolic links, the modification time of the link is returned, rather
* than that of the link target.
*
* @param p
* a {@link Path} object.
* @return last modified time of p
* @since 5.1.9
*/
public Instant lastModifiedInstant(Path p) {
return FileUtils.lastModifiedInstant(p);
}
/**
* Get the last modified time of a file system object. If the OS/JRE support
* symbolic links, the modification time of the link is returned, rather
* than that of the link target.
*
* @param f
* a {@link File} object.
* @return last modified time of p
* @since 5.1.9
*/
public Instant lastModifiedInstant(File f) {
return FileUtils.lastModifiedInstant(f.toPath());
}
/** /**
* Set the last modified time of a file system object. If the OS/JRE support * Set the last modified time of a file system object. If the OS/JRE support
* symbolic links, the link is modified, not the target, * symbolic links, the link is modified, not the target,
@ -640,11 +672,28 @@ public abstract class FS {
* last modified time * last modified time
* @throws java.io.IOException * @throws java.io.IOException
* @since 3.0 * @since 3.0
* @deprecated use {@link #setLastModified(Path, Instant)} instead
*/ */
@Deprecated
public void setLastModified(File f, long time) throws IOException { public void setLastModified(File f, long time) throws IOException {
FileUtils.setLastModified(f, time); FileUtils.setLastModified(f, time);
} }
/**
* Set the last modified time of a file system object. If the OS/JRE support
* symbolic links, the link is modified, not the target,
*
* @param p
* a {@link Path} object.
* @param time
* last modified time
* @throws java.io.IOException
* @since 5.1.9
*/
public void setLastModified(Path p, Instant time) throws IOException {
FileUtils.setLastModified(p, time);
}
/** /**
* Get the length of a file or link, If the OS/JRE supports symbolic links * Get the length of a file or link, If the OS/JRE supports symbolic links
* it's the length of the link, else the length of the target. * it's the length of the link, else the length of the target.
@ -1712,9 +1761,19 @@ public abstract class FS {
/** /**
* @return the time (milliseconds since 1970-01-01) when this object was * @return the time (milliseconds since 1970-01-01) when this object was
* last modified * last modified
* @deprecated use getLastModifiedInstant instead
*/ */
@Deprecated
public long getLastModifiedTime() { public long getLastModifiedTime() {
return lastModifiedTime; return lastModifiedInstant.toEpochMilli();
}
/**
* @return the time when this object was last modified
* @since 5.1.9
*/
public Instant getLastModifiedInstant() {
return lastModifiedInstant;
} }
private final boolean isDirectory; private final boolean isDirectory;
@ -1725,7 +1784,7 @@ public abstract class FS {
private final long creationTime; private final long creationTime;
private final long lastModifiedTime; private final Instant lastModifiedInstant;
private final boolean isExecutable; private final boolean isExecutable;
@ -1743,7 +1802,7 @@ public abstract class FS {
Attributes(FS fs, File file, boolean exists, boolean isDirectory, Attributes(FS fs, File file, boolean exists, boolean isDirectory,
boolean isExecutable, boolean isSymbolicLink, boolean isExecutable, boolean isSymbolicLink,
boolean isRegularFile, long creationTime, boolean isRegularFile, long creationTime,
long lastModifiedTime, long length) { Instant lastModifiedInstant, long length) {
this.fs = fs; this.fs = fs;
this.file = file; this.file = file;
this.exists = exists; this.exists = exists;
@ -1752,7 +1811,7 @@ public abstract class FS {
this.isSymbolicLink = isSymbolicLink; this.isSymbolicLink = isSymbolicLink;
this.isRegularFile = isRegularFile; this.isRegularFile = isRegularFile;
this.creationTime = creationTime; this.creationTime = creationTime;
this.lastModifiedTime = lastModifiedTime; this.lastModifiedInstant = lastModifiedInstant;
this.length = length; this.length = length;
} }
@ -1764,7 +1823,7 @@ public abstract class FS {
* @param path * @param path
*/ */
public Attributes(File path, FS fs) { public Attributes(File path, FS fs) {
this(fs, path, false, false, false, false, false, 0L, 0L, 0L); this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
} }
/** /**
@ -1810,7 +1869,7 @@ public abstract class FS {
boolean exists = isDirectory || isFile; boolean exists = isDirectory || isFile;
boolean canExecute = exists && !isDirectory && canExecute(path); boolean canExecute = exists && !isDirectory && canExecute(path);
boolean isSymlink = false; boolean isSymlink = false;
long lastModified = exists ? path.lastModified() : 0L; Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH;
long createTime = 0L; long createTime = 0L;
return new Attributes(this, path, exists, isDirectory, canExecute, return new Attributes(this, path, exists, isDirectory, canExecute,
isSymlink, isFile, createTime, lastModified, -1); isSymlink, isFile, createTime, lastModified, -1);

2
org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java

@ -149,7 +149,7 @@ public class FS_Win32 extends FS {
attrs.isSymbolicLink(), attrs.isSymbolicLink(),
attrs.isRegularFile(), attrs.isRegularFile(),
attrs.creationTime().toMillis(), attrs.creationTime().toMillis(),
attrs.lastModifiedTime().toMillis(), attrs.lastModifiedTime().toInstant(),
attrs.size()); attrs.size());
result.add(new FileEntry(f, fs, attributes, result.add(new FileEntry(f, fs, attributes,
fileModeStrategy)); fileModeStrategy));

37
org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java

@ -76,11 +76,14 @@ import java.util.regex.Pattern;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.util.FS.Attributes; import org.eclipse.jgit.util.FS.Attributes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* File Utilities * File Utilities
*/ */
public class FileUtils { public class FileUtils {
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
/** /**
* Option to delete given {@code File} * Option to delete given {@code File}
@ -656,12 +659,31 @@ public class FileUtils {
* @return lastModified attribute for given file, not following symbolic * @return lastModified attribute for given file, not following symbolic
* links * links
* @throws IOException * @throws IOException
* @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
* FileTime
*/ */
@Deprecated
static long lastModified(File file) throws IOException { static long lastModified(File file) throws IOException {
return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
.toMillis(); .toMillis();
} }
/**
* @param path
* @return lastModified attribute for given file, not following symbolic
* links
*/
static Instant lastModifiedInstant(Path path) {
try {
return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS)
.toInstant();
} catch (IOException e) {
LOG.error(MessageFormat
.format(JGitText.get().readLastModifiedFailed, path));
return Instant.ofEpochMilli(path.toFile().lastModified());
}
}
/** /**
* Return all the attributes of a file, without following symbolic links. * Return all the attributes of a file, without following symbolic links.
* *
@ -680,10 +702,21 @@ public class FileUtils {
* @param time * @param time
* @throws IOException * @throws IOException
*/ */
@Deprecated
static void setLastModified(File file, long time) throws IOException { static void setLastModified(File file, long time) throws IOException {
Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
} }
/**
* @param path
* @param time
* @throws IOException
*/
static void setLastModified(Path path, Instant time)
throws IOException {
Files.setLastModifiedTime(path, FileTime.from(time));
}
/** /**
* @param file * @param file
* @return {@code true} if the given file exists, not following symbolic * @return {@code true} if the given file exists, not following symbolic
@ -788,7 +821,7 @@ public class FileUtils {
readAttributes.isSymbolicLink(), readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), // readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), // readAttributes.creationTime().toMillis(), //
readAttributes.lastModifiedTime().toMillis(), readAttributes.lastModifiedTime().toInstant(),
readAttributes.isSymbolicLink() ? Constants readAttributes.isSymbolicLink() ? Constants
.encode(readSymLink(file)).length .encode(readSymLink(file)).length
: readAttributes.size()); : readAttributes.size());
@ -827,7 +860,7 @@ public class FileUtils {
readAttributes.isSymbolicLink(), readAttributes.isSymbolicLink(),
readAttributes.isRegularFile(), // readAttributes.isRegularFile(), //
readAttributes.creationTime().toMillis(), // readAttributes.creationTime().toMillis(), //
readAttributes.lastModifiedTime().toMillis(), readAttributes.lastModifiedTime().toInstant(),
readAttributes.size()); readAttributes.size());
return attributes; return attributes;
} catch (IOException e) { } catch (IOException e) {

Loading…
Cancel
Save