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>tags/v5.1.9.201908210455-r
@@ -58,12 +58,14 @@ import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.io.RandomAccessFile; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.Enumeration; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.util.FS; | |||
/** | |||
* Dumps a file over HTTP GET (or its information via HEAD). | |||
@@ -76,7 +78,7 @@ final class FileSender { | |||
private final RandomAccessFile source; | |||
private final long lastModified; | |||
private final Instant lastModified; | |||
private final long fileLen; | |||
@@ -89,7 +91,7 @@ final class FileSender { | |||
this.source = new RandomAccessFile(path, "r"); | |||
try { | |||
this.lastModified = path.lastModified(); | |||
this.lastModified = FS.DETECTED.lastModifiedInstant(path); | |||
this.fileLen = source.getChannel().size(); | |||
this.end = fileLen; | |||
} catch (IOException e) { | |||
@@ -114,7 +116,7 @@ final class FileSender { | |||
} | |||
} | |||
long getLastModified() { | |||
Instant getLastModified() { | |||
return lastModified; | |||
} | |||
@@ -54,6 +54,7 @@ import static org.eclipse.jgit.util.HttpSupport.HDR_LAST_MODIFIED; | |||
import java.io.File; | |||
import java.io.FileNotFoundException; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.http.HttpServlet; | |||
@@ -76,7 +77,9 @@ abstract class ObjectFileServlet extends HttpServlet { | |||
@Override | |||
String etag(FileSender sender) throws IOException { | |||
return Long.toHexString(sender.getLastModified()); | |||
Instant lastModified = sender.getLastModified(); | |||
return Long.toHexString(lastModified.getEpochSecond()) | |||
+ Long.toHexString(lastModified.getNano()); | |||
} | |||
} | |||
@@ -145,7 +148,9 @@ abstract class ObjectFileServlet extends HttpServlet { | |||
try { | |||
final String etag = etag(sender); | |||
final long lastModified = (sender.getLastModified() / 1000) * 1000; | |||
// HTTP header Last-Modified header has a resolution of 1 sec, see | |||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29 | |||
final long lastModified = sender.getLastModified().getEpochSecond(); | |||
String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH); | |||
if (etag != null && etag.equals(ifNoneMatch)) { |
@@ -51,6 +51,7 @@ import static org.junit.Assert.fail; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
@@ -322,12 +323,13 @@ public abstract class LocalDiskRepositoryTestCase { | |||
throws IllegalStateException, IOException { | |||
DirCache dc = repo.readDirCache(); | |||
StringBuilder sb = new StringBuilder(); | |||
TreeSet<Long> timeStamps = new TreeSet<>(); | |||
TreeSet<Instant> timeStamps = new TreeSet<>(); | |||
// iterate once over the dircache just to collect all time stamps | |||
if (0 != (includedOptions & MOD_TIME)) { | |||
for (int i=0; i<dc.getEntryCount(); ++i) | |||
timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); | |||
for (int i = 0; i < dc.getEntryCount(); ++i) { | |||
timeStamps.add(dc.getEntry(i).getLastModifiedInstant()); | |||
} | |||
} | |||
// iterate again, now produce the result string | |||
@@ -339,7 +341,8 @@ public abstract class LocalDiskRepositoryTestCase { | |||
sb.append(", stage:" + stage); | |||
if (0 != (includedOptions & MOD_TIME)) { | |||
sb.append(", time:t"+ | |||
timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); | |||
timeStamps.headSet(entry.getLastModifiedInstant()) | |||
.size()); | |||
} | |||
if (0 != (includedOptions & SMUDGE)) | |||
if (entry.isSmudged()) |
@@ -57,7 +57,9 @@ import java.io.IOException; | |||
import java.io.InputStreamReader; | |||
import java.io.Reader; | |||
import java.nio.file.Path; | |||
import java.time.Instant; | |||
import java.util.Map; | |||
import java.util.concurrent.TimeUnit; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.errors.GitAPIException; | |||
@@ -284,7 +286,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { | |||
dce = new DirCacheEntry(treeItr.getEntryPathString()); | |||
dce.setFileMode(treeItr.getEntryFileMode()); | |||
dce.setLastModified(treeItr.getEntryLastModified()); | |||
dce.setLastModified(treeItr.getEntryLastModifiedInstant()); | |||
dce.setLength((int) len); | |||
try (FileInputStream in = new FileInputStream( | |||
treeItr.getEntryFile())) { | |||
@@ -361,7 +363,8 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { | |||
* @throws InterruptedException | |||
* @throws IOException | |||
*/ | |||
public static long fsTick(File lastFile) throws InterruptedException, | |||
public static Instant fsTick(File lastFile) | |||
throws InterruptedException, | |||
IOException { | |||
File tmp; | |||
FS fs = FS.DETECTED; | |||
@@ -375,15 +378,15 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { | |||
tmp = File.createTempFile("fsTickTmpFile", null, | |||
lastFile.getParentFile()); | |||
} | |||
long res = FS.getFsTimerResolution(tmp.toPath()).toMillis(); | |||
long res = FS.getFsTimerResolution(tmp.toPath()).toNanos(); | |||
long sleepTime = res / 10; | |||
try { | |||
long startTime = fs.lastModified(lastFile); | |||
long actTime = fs.lastModified(tmp); | |||
while (actTime <= startTime) { | |||
Thread.sleep(sleepTime); | |||
Instant startTime = fs.lastModifiedInstant(lastFile); | |||
Instant actTime = fs.lastModifiedInstant(tmp); | |||
while (actTime.compareTo(startTime) <= 0) { | |||
TimeUnit.NANOSECONDS.sleep(sleepTime); | |||
FileUtils.touch(tmp.toPath()); | |||
actTime = fs.lastModified(tmp); | |||
actTime = fs.lastModifiedInstant(tmp); | |||
} | |||
return actTime; | |||
} finally { |
@@ -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); | |||
} | |||
} | |||
} |
@@ -48,8 +48,10 @@ package org.eclipse.jgit.pgm.debug; | |||
import static java.lang.Integer.valueOf; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import java.time.Instant; | |||
import java.time.ZoneId; | |||
import java.time.format.DateTimeFormatter; | |||
import java.util.Locale; | |||
import org.eclipse.jgit.dircache.DirCache; | |||
import org.eclipse.jgit.dircache.DirCacheEntry; | |||
@@ -67,25 +69,27 @@ class ShowDirCache extends TextBuiltin { | |||
/** {@inheritDoc} */ | |||
@Override | |||
protected void run() throws Exception { | |||
final SimpleDateFormat fmt; | |||
fmt = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss.SSS"); //$NON-NLS-1$ | |||
final DateTimeFormatter fmt = DateTimeFormatter | |||
.ofPattern("yyyy-MM-dd,HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$ | |||
.withLocale(Locale.getDefault()) | |||
.withZone(ZoneId.systemDefault()); | |||
final DirCache cache = db.readDirCache(); | |||
for (int i = 0; i < cache.getEntryCount(); i++) { | |||
final DirCacheEntry ent = cache.getEntry(i); | |||
final FileMode mode = FileMode.fromBits(ent.getRawMode()); | |||
final int len = ent.getLength(); | |||
long lastModified = ent.getLastModified(); | |||
final Date mtime = new Date(lastModified); | |||
Instant mtime = ent.getLastModifiedInstant(); | |||
final int stage = ent.getStage(); | |||
outw.print(mode); | |||
outw.format(" %6d", valueOf(len)); //$NON-NLS-1$ | |||
outw.print(' '); | |||
if (millis) | |||
outw.print(lastModified); | |||
else | |||
if (millis) { | |||
outw.print(mtime.toEpochMilli()); | |||
} else { | |||
outw.print(fmt.format(mtime)); | |||
} | |||
outw.print(' '); | |||
outw.print(ent.getObjectId().name()); | |||
outw.print(' '); |
@@ -40,6 +40,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | |||
org.eclipse.jgit.internal.storage.reftable;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.internal.storage.reftree;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.junit;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.junit.time;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.lfs;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.lib;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.merge;version="[5.1.9,5.2.0)", | |||
@@ -62,12 +63,12 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", | |||
org.eclipse.jgit.util;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.util.io;version="[5.1.9,5.2.0)", | |||
org.eclipse.jgit.util.sha1;version="[5.1.9,5.2.0)", | |||
org.tukaani.xz;version="[1.6.0,2.0)", | |||
org.junit;version="[4.12,5.0.0)", | |||
org.junit.experimental.theories;version="[4.12,5.0.0)", | |||
org.junit.rules;version="[4.12,5.0.0)", | |||
org.junit.runner;version="[4.12,5.0.0)", | |||
org.junit.runners;version="[4.12,5.0.0)", | |||
org.slf4j;version="[1.7.0,2.0.0)" | |||
org.slf4j;version="[1.7.0,2.0.0)", | |||
org.tukaani.xz;version="[1.6.0,2.0)" | |||
Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", | |||
org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" |
@@ -1266,7 +1266,7 @@ public class AddCommandTest extends RepositoryTestCase { | |||
DirCacheEntry entry = new DirCacheEntry(path, stage); | |||
entry.setObjectId(id); | |||
entry.setFileMode(FileMode.REGULAR_FILE); | |||
entry.setLastModified(file.lastModified()); | |||
entry.setLastModified(FS.DETECTED.lastModifiedInstant(file)); | |||
entry.setLength((int) file.length()); | |||
builder.add(entry); |
@@ -43,6 +43,7 @@ | |||
*/ | |||
package org.eclipse.jgit.api; | |||
import static java.time.Instant.EPOCH; | |||
import static org.eclipse.jgit.lib.Constants.MASTER; | |||
import static org.eclipse.jgit.lib.Constants.R_HEADS; | |||
import static org.hamcrest.CoreMatchers.is; | |||
@@ -60,6 +61,9 @@ import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.net.MalformedURLException; | |||
import java.net.URISyntaxException; | |||
import java.nio.file.Files; | |||
import java.nio.file.attribute.FileTime; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.api.CheckoutResult.Status; | |||
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; | |||
@@ -74,6 +78,7 @@ import org.eclipse.jgit.dircache.DirCache; | |||
import org.eclipse.jgit.dircache.DirCacheEntry; | |||
import org.eclipse.jgit.junit.JGitTestUtil; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.junit.time.TimeUtil; | |||
import org.eclipse.jgit.lfs.BuiltinLFS; | |||
import org.eclipse.jgit.lib.ConfigConstants; | |||
import org.eclipse.jgit.lib.Constants; | |||
@@ -86,6 +91,7 @@ import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.transport.RemoteConfig; | |||
import org.eclipse.jgit.transport.URIish; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.FileUtils; | |||
import org.eclipse.jgit.util.SystemReader; | |||
import org.junit.Before; | |||
@@ -360,14 +366,14 @@ public class CheckoutCommandTest extends RepositoryTestCase { | |||
File file = new File(db.getWorkTree(), "Test.txt"); | |||
long size = file.length(); | |||
long mTime = file.lastModified() - 5000L; | |||
assertTrue(file.setLastModified(mTime)); | |||
Instant mTime = TimeUtil.setLastModifiedWithOffset(file.toPath(), | |||
-5000L); | |||
DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS()); | |||
DirCacheEntry entry = cache.getEntry("Test.txt"); | |||
assertNotNull(entry); | |||
entry.setLength(0); | |||
entry.setLastModified(0); | |||
entry.setLastModified(EPOCH); | |||
cache.write(); | |||
assertTrue(cache.commit()); | |||
@@ -375,10 +381,12 @@ public class CheckoutCommandTest extends RepositoryTestCase { | |||
entry = cache.getEntry("Test.txt"); | |||
assertNotNull(entry); | |||
assertEquals(0, entry.getLength()); | |||
assertEquals(0, entry.getLastModified()); | |||
assertEquals(EPOCH, entry.getLastModifiedInstant()); | |||
db.getIndexFile().setLastModified( | |||
db.getIndexFile().lastModified() - 5000); | |||
Files.setLastModifiedTime(db.getIndexFile().toPath(), | |||
FileTime.from(FS.DETECTED | |||
.lastModifiedInstant(db.getIndexFile()) | |||
.minusMillis(5000L))); | |||
assertNotNull(git.checkout().setName("test").call()); | |||
@@ -386,7 +394,7 @@ public class CheckoutCommandTest extends RepositoryTestCase { | |||
entry = cache.getEntry("Test.txt"); | |||
assertNotNull(entry); | |||
assertEquals(size, entry.getLength()); | |||
assertEquals(mTime, entry.getLastModified()); | |||
assertEquals(mTime, entry.getLastModifiedInstant()); | |||
} | |||
@Test |
@@ -61,6 +61,7 @@ import org.eclipse.jgit.dircache.DirCache; | |||
import org.eclipse.jgit.dircache.DirCacheBuilder; | |||
import org.eclipse.jgit.dircache.DirCacheEntry; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.junit.time.TimeUtil; | |||
import org.eclipse.jgit.lib.ConfigConstants; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.FileMode; | |||
@@ -311,11 +312,11 @@ public class CommitCommandTest extends RepositoryTestCase { | |||
public void commitUpdatesSmudgedEntries() throws Exception { | |||
try (Git git = new Git(db)) { | |||
File file1 = writeTrashFile("file1.txt", "content1"); | |||
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L); | |||
File file2 = writeTrashFile("file2.txt", "content2"); | |||
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L); | |||
File file3 = writeTrashFile("file3.txt", "content3"); | |||
assertTrue(file3.setLastModified(file3.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file3.toPath(), -5000L); | |||
assertNotNull(git.add().addFilepattern("file1.txt") | |||
.addFilepattern("file2.txt").addFilepattern("file3.txt").call()); | |||
@@ -346,11 +347,12 @@ public class CommitCommandTest extends RepositoryTestCase { | |||
assertEquals(0, cache.getEntry("file2.txt").getLength()); | |||
assertEquals(0, cache.getEntry("file3.txt").getLength()); | |||
long indexTime = db.getIndexFile().lastModified(); | |||
db.getIndexFile().setLastModified(indexTime - 5000); | |||
TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(), | |||
-5000L); | |||
write(file1, "content4"); | |||
assertTrue(file1.setLastModified(file1.lastModified() + 2500)); | |||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), 2500L); | |||
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | |||
.call()); | |||
@@ -368,9 +370,9 @@ public class CommitCommandTest extends RepositoryTestCase { | |||
public void commitIgnoresSmudgedEntryWithDifferentId() throws Exception { | |||
try (Git git = new Git(db)) { | |||
File file1 = writeTrashFile("file1.txt", "content1"); | |||
assertTrue(file1.setLastModified(file1.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), -5000L); | |||
File file2 = writeTrashFile("file2.txt", "content2"); | |||
assertTrue(file2.setLastModified(file2.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file2.toPath(), -5000L); | |||
assertNotNull(git.add().addFilepattern("file1.txt") | |||
.addFilepattern("file2.txt").call()); | |||
@@ -399,11 +401,11 @@ public class CommitCommandTest extends RepositoryTestCase { | |||
assertEquals(0, cache.getEntry("file1.txt").getLength()); | |||
assertEquals(0, cache.getEntry("file2.txt").getLength()); | |||
long indexTime = db.getIndexFile().lastModified(); | |||
db.getIndexFile().setLastModified(indexTime - 5000); | |||
TimeUtil.setLastModifiedWithOffset(db.getIndexFile().toPath(), | |||
-5000L); | |||
write(file1, "content5"); | |||
assertTrue(file1.setLastModified(file1.lastModified() + 1000)); | |||
TimeUtil.setLastModifiedWithOffset(file1.toPath(), 1000L); | |||
assertNotNull(git.commit().setMessage("edit file").setOnly("file1.txt") | |||
.call()); |
@@ -44,7 +44,6 @@ package org.eclipse.jgit.api; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
@@ -55,6 +54,7 @@ import java.util.List; | |||
import org.eclipse.jgit.diff.DiffEntry; | |||
import org.eclipse.jgit.diff.DiffEntry.ChangeType; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.junit.time.TimeUtil; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
@@ -230,7 +230,7 @@ public class DiffCommandTest extends RepositoryTestCase { | |||
@Test | |||
public void testNoOutputStreamSet() throws Exception { | |||
File file = writeTrashFile("test.txt", "a"); | |||
assertTrue(file.setLastModified(file.lastModified() - 5000)); | |||
TimeUtil.setLastModifiedWithOffset(file.toPath(), -5000L); | |||
try (Git git = new Git(db)) { | |||
git.add().addFilepattern(".").call(); | |||
write(file, "b"); |
@@ -42,6 +42,7 @@ | |||
*/ | |||
package org.eclipse.jgit.api; | |||
import static java.time.Instant.EPOCH; | |||
import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
@@ -52,6 +53,9 @@ import static org.junit.Assert.fail; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.attribute.FileTime; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.api.ResetCommand.ResetType; | |||
import org.eclipse.jgit.api.errors.GitAPIException; | |||
@@ -246,13 +250,13 @@ public class ResetCommandTest extends RepositoryTestCase { | |||
public void testMixedResetRetainsSizeAndModifiedTime() throws Exception { | |||
git = new Git(db); | |||
writeTrashFile("a.txt", "a").setLastModified( | |||
System.currentTimeMillis() - 60 * 1000); | |||
Files.setLastModifiedTime(writeTrashFile("a.txt", "a").toPath(), | |||
FileTime.from(Instant.now().minusSeconds(60))); | |||
assertNotNull(git.add().addFilepattern("a.txt").call()); | |||
assertNotNull(git.commit().setMessage("a commit").call()); | |||
writeTrashFile("b.txt", "b").setLastModified( | |||
System.currentTimeMillis() - 60 * 1000); | |||
Files.setLastModifiedTime(writeTrashFile("b.txt", "b").toPath(), | |||
FileTime.from(Instant.now().minusSeconds(60))); | |||
assertNotNull(git.add().addFilepattern("b.txt").call()); | |||
RevCommit commit2 = git.commit().setMessage("b commit").call(); | |||
assertNotNull(commit2); | |||
@@ -262,12 +266,12 @@ public class ResetCommandTest extends RepositoryTestCase { | |||
DirCacheEntry aEntry = cache.getEntry("a.txt"); | |||
assertNotNull(aEntry); | |||
assertTrue(aEntry.getLength() > 0); | |||
assertTrue(aEntry.getLastModified() > 0); | |||
assertTrue(aEntry.getLastModifiedInstant().compareTo(EPOCH) > 0); | |||
DirCacheEntry bEntry = cache.getEntry("b.txt"); | |||
assertNotNull(bEntry); | |||
assertTrue(bEntry.getLength() > 0); | |||
assertTrue(bEntry.getLastModified() > 0); | |||
assertTrue(bEntry.getLastModifiedInstant().compareTo(EPOCH) > 0); | |||
assertSameAsHead(git.reset().setMode(ResetType.MIXED) | |||
.setRef(commit2.getName()).call()); | |||
@@ -276,13 +280,17 @@ public class ResetCommandTest extends RepositoryTestCase { | |||
DirCacheEntry mixedAEntry = cache.getEntry("a.txt"); | |||
assertNotNull(mixedAEntry); | |||
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); | |||
assertEquals(aEntry.getLastModified(), mixedAEntry.getLastModified()); | |||
assertEquals(aEntry.getLastModifiedInstant(), | |||
mixedAEntry.getLastModifiedInstant()); | |||
assertEquals(aEntry.getLastModifiedInstant(), | |||
mixedAEntry.getLastModifiedInstant()); | |||
DirCacheEntry mixedBEntry = cache.getEntry("b.txt"); | |||
assertNotNull(mixedBEntry); | |||
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); | |||
assertEquals(bEntry.getLastModified(), mixedBEntry.getLastModified()); | |||
assertEquals(bEntry.getLastModifiedInstant(), | |||
mixedBEntry.getLastModifiedInstant()); | |||
assertEquals(bEntry.getLastModifiedInstant(), | |||
mixedBEntry.getLastModifiedInstant()); | |||
} | |||
@Test |
@@ -53,6 +53,7 @@ import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.io.File; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.events.IndexChangedEvent; | |||
import org.eclipse.jgit.events.IndexChangedListener; | |||
@@ -98,7 +99,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
public void testBuildOneFile_FinishWriteCommit() throws Exception { | |||
final String path = "a-file-path"; | |||
final FileMode mode = FileMode.REGULAR_FILE; | |||
final long lastModified = 1218123387057L; | |||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||
final int length = 1342; | |||
final DirCacheEntry entOrig; | |||
{ | |||
@@ -116,7 +117,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | |||
assertEquals(mode.getBits(), entOrig.getRawMode()); | |||
assertEquals(0, entOrig.getStage()); | |||
assertEquals(lastModified, entOrig.getLastModified()); | |||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||
assertEquals(length, entOrig.getLength()); | |||
assertFalse(entOrig.isAssumeValid()); | |||
b.add(entOrig); | |||
@@ -138,7 +139,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | |||
assertEquals(mode.getBits(), entOrig.getRawMode()); | |||
assertEquals(0, entOrig.getStage()); | |||
assertEquals(lastModified, entOrig.getLastModified()); | |||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||
assertEquals(length, entOrig.getLength()); | |||
assertFalse(entOrig.isAssumeValid()); | |||
} | |||
@@ -148,7 +149,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
public void testBuildOneFile_Commit() throws Exception { | |||
final String path = "a-file-path"; | |||
final FileMode mode = FileMode.REGULAR_FILE; | |||
final long lastModified = 1218123387057L; | |||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||
final int length = 1342; | |||
final DirCacheEntry entOrig; | |||
{ | |||
@@ -166,7 +167,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | |||
assertEquals(mode.getBits(), entOrig.getRawMode()); | |||
assertEquals(0, entOrig.getStage()); | |||
assertEquals(lastModified, entOrig.getLastModified()); | |||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||
assertEquals(length, entOrig.getLength()); | |||
assertFalse(entOrig.isAssumeValid()); | |||
b.add(entOrig); | |||
@@ -186,7 +187,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); | |||
assertEquals(mode.getBits(), entOrig.getRawMode()); | |||
assertEquals(0, entOrig.getStage()); | |||
assertEquals(lastModified, entOrig.getLastModified()); | |||
assertEquals(lastModified, entOrig.getLastModifiedInstant()); | |||
assertEquals(length, entOrig.getLength()); | |||
assertFalse(entOrig.isAssumeValid()); | |||
} | |||
@@ -203,7 +204,7 @@ public class DirCacheBuilderTest extends RepositoryTestCase { | |||
final String path = "a-file-path"; | |||
final FileMode mode = FileMode.REGULAR_FILE; | |||
// "old" date in 2008 | |||
final long lastModified = 1218123387057L; | |||
final Instant lastModified = Instant.ofEpochMilli(1218123387057L); | |||
final int length = 1342; | |||
DirCacheEntry entOrig; | |||
boolean receivedEvent = false; |
@@ -43,6 +43,7 @@ | |||
package org.eclipse.jgit.dircache; | |||
import static java.time.Instant.EPOCH; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertSame; | |||
@@ -188,7 +189,7 @@ public class DirCacheEntryTest { | |||
e.setAssumeValid(false); | |||
e.setCreationTime(2L); | |||
e.setFileMode(FileMode.EXECUTABLE_FILE); | |||
e.setLastModified(3L); | |||
e.setLastModified(EPOCH.plusMillis(3L)); | |||
e.setLength(100L); | |||
e.setObjectId(ObjectId | |||
.fromString("0123456789012345678901234567890123456789")); | |||
@@ -199,7 +200,7 @@ public class DirCacheEntryTest { | |||
f.setAssumeValid(true); | |||
f.setCreationTime(10L); | |||
f.setFileMode(FileMode.SYMLINK); | |||
f.setLastModified(20L); | |||
f.setLastModified(EPOCH.plusMillis(20L)); | |||
f.setLength(100000000L); | |||
f.setObjectId(ObjectId | |||
.fromString("1234567890123456789012345678901234567890")); | |||
@@ -212,7 +213,7 @@ public class DirCacheEntryTest { | |||
ObjectId.fromString("1234567890123456789012345678901234567890"), | |||
e.getObjectId()); | |||
assertEquals(FileMode.SYMLINK, e.getFileMode()); | |||
assertEquals(20L, e.getLastModified()); | |||
assertEquals(EPOCH.plusMillis(20L), e.getLastModifiedInstant()); | |||
assertEquals(100000000L, e.getLength()); | |||
if (keepStage) | |||
assertEquals(DirCacheEntry.STAGE_2, e.getStage()); |
@@ -56,6 +56,7 @@ import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
import org.eclipse.jgit.errors.MissingObjectException; | |||
@@ -71,6 +72,7 @@ import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.revwalk.RevObject; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.storage.file.WindowCacheConfig; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.FileUtils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
@@ -235,7 +237,8 @@ public class ConcurrentRepackTest extends RepositoryTestCase { | |||
private static void write(File[] files, PackWriter pw) | |||
throws IOException { | |||
final long begin = files[0].getParentFile().lastModified(); | |||
final Instant begin = FS.DETECTED | |||
.lastModifiedInstant(files[0].getParentFile()); | |||
NullProgressMonitor m = NullProgressMonitor.INSTANCE; | |||
try (OutputStream out = new BufferedOutputStream( | |||
@@ -252,7 +255,8 @@ public class ConcurrentRepackTest extends RepositoryTestCase { | |||
} | |||
private static void delete(File[] list) throws IOException { | |||
final long begin = list[0].getParentFile().lastModified(); | |||
final Instant begin = FS.DETECTED | |||
.lastModifiedInstant(list[0].getParentFile()); | |||
for (File f : list) { | |||
FileUtils.delete(f); | |||
assertFalse(f + " was removed", f.exists()); | |||
@@ -260,14 +264,14 @@ public class ConcurrentRepackTest extends RepositoryTestCase { | |||
touch(begin, list[0].getParentFile()); | |||
} | |||
private static void touch(long begin, File dir) { | |||
while (begin >= dir.lastModified()) { | |||
private static void touch(Instant begin, File dir) throws IOException { | |||
while (begin.compareTo(FS.DETECTED.lastModifiedInstant(dir)) >= 0) { | |||
try { | |||
Thread.sleep(25); | |||
} catch (InterruptedException ie) { | |||
// | |||
} | |||
dir.setLastModified(System.currentTimeMillis()); | |||
FS.DETECTED.setLastModified(dir.toPath(), Instant.now()); | |||
} | |||
} | |||
@@ -51,9 +51,11 @@ import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.StandardCopyOption; | |||
import java.nio.file.attribute.FileTime; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.FileUtils; | |||
import org.eclipse.jgit.util.SystemReader; | |||
import org.junit.After; | |||
@@ -80,11 +82,12 @@ public class FileSnapshotTest { | |||
FileUtils.delete(trash, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); | |||
} | |||
private static void waitNextSec(File f) { | |||
long initialLastModified = f.lastModified(); | |||
private static void waitNextTick(File f) throws IOException { | |||
Instant initialLastModified = FS.DETECTED.lastModifiedInstant(f); | |||
do { | |||
f.setLastModified(System.currentTimeMillis()); | |||
} while (f.lastModified() == initialLastModified); | |||
FS.DETECTED.setLastModified(f.toPath(), Instant.now()); | |||
} while (FS.DETECTED.lastModifiedInstant(f) | |||
.equals(initialLastModified)); | |||
} | |||
/** | |||
@@ -95,10 +98,10 @@ public class FileSnapshotTest { | |||
@Test | |||
public void testActuallyIsModifiedTrivial() throws Exception { | |||
File f1 = createFile("simple"); | |||
waitNextSec(f1); | |||
waitNextTick(f1); | |||
FileSnapshot save = FileSnapshot.save(f1); | |||
append(f1, (byte) 'x'); | |||
waitNextSec(f1); | |||
waitNextTick(f1); | |||
assertTrue(save.isModified(f1)); | |||
} | |||
@@ -113,7 +116,7 @@ public class FileSnapshotTest { | |||
@Test | |||
public void testNewFileWithWait() throws Exception { | |||
File f1 = createFile("newfile"); | |||
waitNextSec(f1); | |||
waitNextTick(f1); | |||
FileSnapshot save = FileSnapshot.save(f1); | |||
Thread.sleep(1500); | |||
assertTrue(save.isModified(f1)); | |||
@@ -146,8 +149,8 @@ public class FileSnapshotTest { | |||
File f2 = createFile("fool"); // Guarantees new inode x | |||
// wait on f2 since this method resets lastModified of the file | |||
// and leaves lastModified of f1 untouched | |||
waitNextSec(f2); | |||
waitNextSec(f2); | |||
waitNextTick(f2); | |||
waitNextTick(f2); | |||
FileTime timestamp = Files.getLastModifiedTime(f1.toPath()); | |||
FileSnapshot save = FileSnapshot.save(f1); | |||
Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x | |||
@@ -185,7 +188,7 @@ public class FileSnapshotTest { | |||
// 0 sized FileSnapshot. | |||
FileSnapshot fs1 = FileSnapshot.MISSING_FILE; | |||
// UNKNOWN_SIZE FileSnapshot. | |||
FileSnapshot fs2 = FileSnapshot.save(fs1.lastModified()); | |||
FileSnapshot fs2 = FileSnapshot.save(fs1.lastModifiedInstant()); | |||
assertTrue(fs1.equals(fs2)); | |||
assertTrue(fs2.equals(fs1)); |
@@ -147,9 +147,10 @@ public abstract class GcTestCase extends LocalDiskRepositoryTestCase { | |||
return tip; | |||
} | |||
protected long lastModified(AnyObjectId objectId) throws IOException { | |||
return repo.getFS().lastModified( | |||
repo.getObjectDatabase().fileFor(objectId)); | |||
protected long lastModified(AnyObjectId objectId) { | |||
return repo.getFS() | |||
.lastModifiedInstant(repo.getObjectDatabase().fileFor(objectId)) | |||
.toEpochMilli(); | |||
} | |||
protected static void fsTick() throws InterruptedException, IOException { |
@@ -65,6 +65,7 @@ import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Assume; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -157,13 +158,14 @@ public class ObjectDirectoryTest extends RepositoryTestCase { | |||
// To deal with racy-git situations JGit's Filesnapshot class will | |||
// report a file/folder potentially dirty if | |||
// cachedLastReadTime-cachedLastModificationTime < 2500ms. This | |||
// causes JGit to always rescan a file after modification. But: | |||
// this was true only if the difference between current system time | |||
// and cachedLastModification time was less than 2500ms. If the | |||
// modification is more than 2500ms ago we may have reported a | |||
// file/folder to be clean although it has not been rescanned. A | |||
// Bug. To show the bug we sleep for more than 2500ms | |||
// cachedLastReadTime-cachedLastModificationTime < filesystem | |||
// timestamp resolution. This causes JGit to always rescan a file | |||
// after modification. But: this was true only if the difference | |||
// between current system time and cachedLastModification time was | |||
// less than 2500ms. If the modification is more than 2500ms ago we | |||
// may have reported a file/folder to be clean although it has not | |||
// been rescanned. A bug. To show the bug we sleep for more than | |||
// 2500ms | |||
Thread.sleep(2600); | |||
File[] ret = packsFolder.listFiles(new FilenameFilter() { | |||
@@ -173,7 +175,9 @@ public class ObjectDirectoryTest extends RepositoryTestCase { | |||
} | |||
}); | |||
assertTrue(ret != null && ret.length == 1); | |||
Assume.assumeTrue(tmpFile.lastModified() == ret[0].lastModified()); | |||
FS fs = db.getFS(); | |||
Assume.assumeTrue(fs.lastModifiedInstant(tmpFile) | |||
.equals(fs.lastModifiedInstant(ret[0]))); | |||
// all objects are in a new packfile but we will not detect it | |||
assertFalse(receivingDB.hasObject(unknownID)); |
@@ -59,6 +59,7 @@ import java.nio.file.StandardCopyOption; | |||
import java.nio.file.StandardOpenOption; | |||
//import java.nio.file.attribute.BasicFileAttributes; | |||
import java.text.ParseException; | |||
import java.time.Instant; | |||
import java.util.Collection; | |||
import java.util.Iterator; | |||
import java.util.Random; | |||
@@ -80,6 +81,7 @@ import org.eclipse.jgit.lib.ConfigConstants; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.storage.pack.PackConfig; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Test; | |||
public class PackFileSnapshotTest extends RepositoryTestCase { | |||
@@ -188,7 +190,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
AnyObjectId chk1 = pf.getPackChecksum(); | |||
String name = pf.getPackName(); | |||
Long length = Long.valueOf(pf.getPackFile().length()); | |||
long m1 = packFilePath.toFile().lastModified(); | |||
FS fs = db.getFS(); | |||
Instant m1 = fs.lastModifiedInstant(packFilePath); | |||
// Wait for a filesystem timer tick to enhance probability the rest of | |||
// this test is done before the filesystem timer ticks again. | |||
@@ -198,15 +201,15 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
// content and checksum are different since compression level differs | |||
AnyObjectId chk2 = repackAndCheck(6, name, length, chk1) | |||
.getPackChecksum(); | |||
long m2 = packFilePath.toFile().lastModified(); | |||
assumeFalse(m2 == m1); | |||
Instant m2 = fs.lastModifiedInstant(packFilePath); | |||
assumeFalse(m2.equals(m1)); | |||
// Repack to create packfile with same name, length. Lastmodified is | |||
// equal to the previous one because we are in the same filesystem timer | |||
// slot. Content and its checksum are different | |||
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | |||
.getPackChecksum(); | |||
long m3 = packFilePath.toFile().lastModified(); | |||
Instant m3 = fs.lastModifiedInstant(packFilePath); | |||
// ask for an unknown git object to force jgit to rescan the list of | |||
// available packs. If we would ask for a known objectid then JGit would | |||
@@ -214,7 +217,7 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
db.getObjectDatabase().has(unknownID); | |||
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | |||
.getPackChecksum()); | |||
assumeTrue(m3 == m2); | |||
assumeTrue(m3.equals(m2)); | |||
} | |||
// Try repacking so fast that we get two new packs which differ only in | |||
@@ -253,7 +256,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
// Repack to create third packfile | |||
AnyObjectId chk3 = repackAndCheck(7, name, length, chk2) | |||
.getPackChecksum(); | |||
long m3 = packFilePath.toFile().lastModified(); | |||
FS fs = db.getFS(); | |||
Instant m3 = fs.lastModifiedInstant(packFilePath); | |||
db.getObjectDatabase().has(unknownID); | |||
assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks()) | |||
.getPackChecksum()); | |||
@@ -265,8 +269,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
// Copy copy2 to packfile data to force modification of packfile without | |||
// changing the packfile's filekey. | |||
copyPack(packFileBasePath, ".copy2", ""); | |||
long m2 = packFilePath.toFile().lastModified(); | |||
assumeFalse(m3 == m2); | |||
Instant m2 = fs.lastModifiedInstant(packFilePath); | |||
assumeFalse(m3.equals(m2)); | |||
db.getObjectDatabase().has(unknownID); | |||
assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks()) | |||
@@ -275,8 +279,8 @@ public class PackFileSnapshotTest extends RepositoryTestCase { | |||
// Copy copy2 to packfile data to force modification of packfile without | |||
// changing the packfile's filekey. | |||
copyPack(packFileBasePath, ".copy1", ""); | |||
long m1 = packFilePath.toFile().lastModified(); | |||
assumeTrue(m2 == m1); | |||
Instant m1 = fs.lastModifiedInstant(packFilePath); | |||
assumeTrue(m2.equals(m1)); | |||
db.getObjectDatabase().has(unknownID); | |||
assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks()) | |||
.getPackChecksum()); |
@@ -59,6 +59,7 @@ import static org.junit.Assert.fail; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Map; | |||
@@ -78,6 +79,7 @@ import org.eclipse.jgit.lib.RefDatabase; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.revwalk.RevCommit; | |||
import org.eclipse.jgit.revwalk.RevTag; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
@@ -1319,10 +1321,8 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { | |||
private void writePackedRefs(String content) throws IOException { | |||
File pr = new File(diskRepo.getDirectory(), "packed-refs"); | |||
write(pr, content); | |||
final long now = System.currentTimeMillis(); | |||
final int oneHourAgo = 3600 * 1000; | |||
pr.setLastModified(now - oneHourAgo); | |||
FS fs = diskRepo.getFS(); | |||
fs.setLastModified(pr.toPath(), Instant.now().minusSeconds(3600)); | |||
} | |||
private void deleteLooseRef(String name) { |
@@ -59,6 +59,7 @@ import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.UnsupportedEncodingException; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.errors.ConfigInvalidException; | |||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
@@ -82,6 +83,7 @@ import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | |||
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.FileUtils; | |||
import org.eclipse.jgit.util.IO; | |||
import org.junit.Rule; | |||
@@ -790,12 +792,14 @@ public class T0003_BasicTest extends SampleDataRepositoryTestCase { | |||
* | |||
* @param name | |||
* the file in the repository to force a time change on. | |||
* @throws IOException | |||
*/ | |||
private void BUG_WorkAroundRacyGitIssues(String name) { | |||
private void BUG_WorkAroundRacyGitIssues(String name) throws IOException { | |||
File path = new File(db.getDirectory(), name); | |||
long old = path.lastModified(); | |||
FS fs = db.getFS(); | |||
Instant old = fs.lastModifiedInstant(path); | |||
long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 | |||
path.setLastModified(set); | |||
assertTrue("time changed", old != path.lastModified()); | |||
fs.setLastModified(path.toPath(), Instant.ofEpochMilli(set)); | |||
assertFalse("time changed", old.equals(fs.lastModifiedInstant(path))); | |||
} | |||
} |
@@ -37,8 +37,12 @@ | |||
*/ | |||
package org.eclipse.jgit.lib; | |||
import static java.time.Instant.EPOCH; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.dircache.DirCache; | |||
import org.eclipse.jgit.dircache.DirCacheEntry; | |||
@@ -63,11 +67,11 @@ public class IndexModificationTimesTest extends RepositoryTestCase { | |||
DirCacheEntry entry = dc.getEntry(path); | |||
DirCacheEntry entry2 = dc.getEntry(path); | |||
assertTrue("last modified shall not be zero!", | |||
entry.getLastModified() != 0); | |||
assertFalse("last modified shall not be the epoch!", | |||
entry.getLastModifiedInstant().equals(EPOCH)); | |||
assertTrue("last modified shall not be zero!", | |||
entry2.getLastModified() != 0); | |||
assertFalse("last modified shall not be the epoch!", | |||
entry2.getLastModifiedInstant().equals(EPOCH)); | |||
writeTrashFile(path, "new content"); | |||
git.add().addFilepattern(path).call(); | |||
@@ -77,11 +81,11 @@ public class IndexModificationTimesTest extends RepositoryTestCase { | |||
entry = dc.getEntry(path); | |||
entry2 = dc.getEntry(path); | |||
assertTrue("last modified shall not be zero!", | |||
entry.getLastModified() != 0); | |||
assertFalse("last modified shall not be the epoch!", | |||
entry.getLastModifiedInstant().equals(EPOCH)); | |||
assertTrue("last modified shall not be zero!", | |||
entry2.getLastModified() != 0); | |||
assertFalse("last modified shall not be the epoch!", | |||
entry2.getLastModifiedInstant().equals(EPOCH)); | |||
} | |||
} | |||
@@ -97,7 +101,7 @@ public class IndexModificationTimesTest extends RepositoryTestCase { | |||
DirCache dc = db.readDirCache(); | |||
DirCacheEntry entry = dc.getEntry(path); | |||
long masterLastMod = entry.getLastModified(); | |||
Instant masterLastMod = entry.getLastModifiedInstant(); | |||
git.checkout().setCreateBranch(true).setName("side").call(); | |||
@@ -110,7 +114,7 @@ public class IndexModificationTimesTest extends RepositoryTestCase { | |||
dc = db.readDirCache(); | |||
entry = dc.getEntry(path); | |||
long sideLastMode = entry.getLastModified(); | |||
Instant sideLastMod = entry.getLastModifiedInstant(); | |||
Thread.sleep(2000); | |||
@@ -120,9 +124,10 @@ public class IndexModificationTimesTest extends RepositoryTestCase { | |||
dc = db.readDirCache(); | |||
entry = dc.getEntry(path); | |||
assertTrue("shall have equal mod time!", masterLastMod == sideLastMode); | |||
assertTrue("shall not equal master timestamp!", | |||
entry.getLastModified() == masterLastMod); | |||
assertTrue("shall have equal mod time!", | |||
masterLastMod.equals(sideLastMod)); | |||
assertTrue("shall have equal master timestamp!", | |||
entry.getLastModifiedInstant().equals(masterLastMod)); | |||
} | |||
} | |||
@@ -50,12 +50,15 @@ import static org.junit.Assert.assertTrue; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.dircache.DirCache; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.junit.time.TimeUtil; | |||
import org.eclipse.jgit.treewalk.FileTreeIterator; | |||
import org.eclipse.jgit.treewalk.WorkingTreeOptions; | |||
import org.eclipse.jgit.util.FS; | |||
import org.junit.Test; | |||
public class RacyGitTests extends RepositoryTestCase { | |||
@@ -74,8 +77,8 @@ public class RacyGitTests extends RepositoryTestCase { | |||
// create two files | |||
File a = writeToWorkDir("a", "a"); | |||
File b = writeToWorkDir("b", "b"); | |||
assertTrue(a.setLastModified(b.lastModified())); | |||
assertTrue(b.setLastModified(b.lastModified())); | |||
TimeUtil.setLastModifiedOf(a.toPath(), b.toPath()); | |||
TimeUtil.setLastModifiedOf(b.toPath(), b.toPath()); | |||
// wait to ensure that file-modTimes and therefore index entry modTime | |||
// doesn't match the modtime of index-file after next persistance | |||
@@ -97,10 +100,11 @@ public class RacyGitTests extends RepositoryTestCase { | |||
// filesystem timestamp resolution. By changing the index file | |||
// artificially, we create a fake racy situation. | |||
File updatedA = writeToWorkDir("a", "a2"); | |||
long newLastModified = updatedA.lastModified() + 100; | |||
assertTrue(updatedA.setLastModified(newLastModified)); | |||
Instant newLastModified = TimeUtil | |||
.setLastModifiedWithOffset(updatedA.toPath(), 100L); | |||
resetIndex(new FileTreeIterator(db)); | |||
assertTrue(db.getIndexFile().setLastModified(newLastModified)); | |||
FS.DETECTED.setLastModified(db.getIndexFile().toPath(), | |||
newLastModified); | |||
DirCache dc = db.readDirCache(); | |||
// check index state: although racily clean a should not be reported as |
@@ -43,6 +43,7 @@ | |||
package org.eclipse.jgit.merge; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static java.time.Instant.EPOCH; | |||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
@@ -54,6 +55,7 @@ import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import java.util.Map; | |||
@@ -1090,13 +1092,13 @@ public class MergerTest extends RepositoryTestCase { | |||
@Theory | |||
public void checkForCorrectIndex(MergeStrategy strategy) throws Exception { | |||
File f; | |||
long lastTs4, lastTsIndex; | |||
Instant lastTs4, lastTsIndex; | |||
Git git = Git.wrap(db); | |||
File indexFile = db.getIndexFile(); | |||
// Create initial content and remember when the last file was written. | |||
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | |||
lastTs4 = FS.DETECTED.lastModified(f); | |||
lastTs4 = FS.DETECTED.lastModifiedInstant(f); | |||
// add all files, commit and check this doesn't update any working tree | |||
// files and that the index is in a new file system timer tick. Make | |||
@@ -1109,8 +1111,9 @@ public class MergerTest extends RepositoryTestCase { | |||
checkConsistentLastModified("0", "1", "2", "3", "4"); | |||
checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); | |||
assertEquals("Commit should not touch working tree file 4", lastTs4, | |||
FS.DETECTED.lastModified(new File(db.getWorkTree(), "4"))); | |||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||
FS.DETECTED | |||
.lastModifiedInstant(new File(db.getWorkTree(), "4"))); | |||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||
// Do modifications on the master branch. Then add and commit. This | |||
// should touch only "0", "2 and "3" | |||
@@ -1124,7 +1127,7 @@ public class MergerTest extends RepositoryTestCase { | |||
checkConsistentLastModified("0", "1", "2", "3", "4"); | |||
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | |||
+ lastTsIndex, "<0", "2", "3", "<.git/index"); | |||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||
// Checkout a side branch. This should touch only "0", "2 and "3" | |||
fsTick(indexFile); | |||
@@ -1133,7 +1136,7 @@ public class MergerTest extends RepositoryTestCase { | |||
checkConsistentLastModified("0", "1", "2", "3", "4"); | |||
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" | |||
+ lastTsIndex, "<0", "2", "3", ".git/index"); | |||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||
// This checkout may have populated worktree and index so fast that we | |||
// may have smudged entries now. Check that we have the right content | |||
@@ -1146,13 +1149,13 @@ public class MergerTest extends RepositoryTestCase { | |||
indexState(CONTENT)); | |||
fsTick(indexFile); | |||
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); | |||
lastTs4 = FS.DETECTED.lastModified(f); | |||
lastTs4 = FS.DETECTED.lastModifiedInstant(f); | |||
fsTick(f); | |||
git.add().addFilepattern(".").call(); | |||
checkConsistentLastModified("0", "1", "2", "3", "4"); | |||
checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", | |||
"4", "<.git/index"); | |||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||
// Do modifications on the side branch. Touch only "1", "2 and "3" | |||
fsTick(indexFile); | |||
@@ -1163,7 +1166,7 @@ public class MergerTest extends RepositoryTestCase { | |||
checkConsistentLastModified("0", "1", "2", "3", "4"); | |||
checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" | |||
+ lastTsIndex, "<1", "2", "3", "<.git/index"); | |||
lastTsIndex = FS.DETECTED.lastModified(indexFile); | |||
lastTsIndex = FS.DETECTED.lastModifiedInstant(indexFile); | |||
// merge master and side. Should only touch "0," "2" and "3" | |||
fsTick(indexFile); | |||
@@ -1330,9 +1333,10 @@ public class MergerTest extends RepositoryTestCase { | |||
assertEquals( | |||
"IndexEntry with path " | |||
+ path | |||
+ " has lastmodified with is different from the worktree file", | |||
FS.DETECTED.lastModified(new File(workTree, path)), dc.getEntry(path) | |||
.getLastModified()); | |||
+ " has lastmodified which is different from the worktree file", | |||
FS.DETECTED.lastModifiedInstant(new File(workTree, path)), | |||
dc.getEntry(path) | |||
.getLastModifiedInstant()); | |||
} | |||
// Assert that modification timestamps of working tree files are as | |||
@@ -1341,21 +1345,22 @@ public class MergerTest extends RepositoryTestCase { | |||
// then this file must be younger then file i. A path "*<modtime>" | |||
// represents a file with a modification time of <modtime> | |||
// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt | |||
private void checkModificationTimeStampOrder(String... pathes) | |||
throws IOException { | |||
long lastMod = Long.MIN_VALUE; | |||
private void checkModificationTimeStampOrder(String... pathes) { | |||
Instant lastMod = EPOCH; | |||
for (String p : pathes) { | |||
boolean strong = p.startsWith("<"); | |||
boolean fixed = p.charAt(strong ? 1 : 0) == '*'; | |||
p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0)); | |||
long curMod = fixed ? Long.valueOf(p).longValue() | |||
: FS.DETECTED.lastModified(new File(db.getWorkTree(), p)); | |||
if (strong) | |||
Instant curMod = fixed ? Instant.parse(p) | |||
: FS.DETECTED | |||
.lastModifiedInstant(new File(db.getWorkTree(), p)); | |||
if (strong) { | |||
assertTrue("path " + p + " is not younger than predecesssor", | |||
curMod > lastMod); | |||
else | |||
curMod.compareTo(lastMod) > 0); | |||
} else { | |||
assertTrue("path " + p + " is older than predecesssor", | |||
curMod >= lastMod); | |||
curMod.compareTo(lastMod) >= 0); | |||
} | |||
} | |||
} | |||
@@ -57,10 +57,12 @@ import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStreamWriter; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.junit.RepositoryTestCase; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.transport.OpenSshConfig.Host; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.FileUtils; | |||
import org.eclipse.jgit.util.SystemReader; | |||
import org.junit.Before; | |||
@@ -91,13 +93,14 @@ public class OpenSshConfigTest extends RepositoryTestCase { | |||
} | |||
private void config(String data) throws IOException { | |||
long lastMtime = configFile.lastModified(); | |||
FS fs = db.getFS(); | |||
Instant lastMtime = fs.lastModifiedInstant(configFile); | |||
do { | |||
try (final OutputStreamWriter fw = new OutputStreamWriter( | |||
new FileOutputStream(configFile), UTF_8)) { | |||
fw.write(data); | |||
} | |||
} while (lastMtime == configFile.lastModified()); | |||
} while (lastMtime.equals(fs.lastModifiedInstant(configFile))); | |||
} | |||
@Test |
@@ -52,6 +52,7 @@ import static org.junit.Assert.assertTrue; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.security.MessageDigest; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.ResetCommand.ResetType; | |||
@@ -86,7 +87,7 @@ import org.junit.Test; | |||
public class FileTreeIteratorTest extends RepositoryTestCase { | |||
private final String[] paths = { "a,", "a,b", "a/b", "a0b" }; | |||
private long[] mtime; | |||
private Instant[] mtime; | |||
@Override | |||
@Before | |||
@@ -99,11 +100,11 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
// This should stress the sorting code better than doing it in | |||
// the correct order. | |||
// | |||
mtime = new long[paths.length]; | |||
mtime = new Instant[paths.length]; | |||
for (int i = paths.length - 1; i >= 0; i--) { | |||
final String s = paths[i]; | |||
writeTrashFile(s, s); | |||
mtime[i] = FS.DETECTED.lastModified(new File(trash, s)); | |||
mtime[i] = db.getFS().lastModifiedInstant(new File(trash, s)); | |||
} | |||
} | |||
@@ -199,7 +200,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | |||
assertEquals(paths[0], nameOf(top)); | |||
assertEquals(paths[0].length(), top.getEntryLength()); | |||
assertEquals(mtime[0], top.getEntryLastModified()); | |||
assertEquals(mtime[0], top.getEntryLastModifiedInstant()); | |||
top.next(1); | |||
assertFalse(top.first()); | |||
@@ -207,7 +208,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | |||
assertEquals(paths[1], nameOf(top)); | |||
assertEquals(paths[1].length(), top.getEntryLength()); | |||
assertEquals(mtime[1], top.getEntryLastModified()); | |||
assertEquals(mtime[1], top.getEntryLastModifiedInstant()); | |||
top.next(1); | |||
assertFalse(top.first()); | |||
@@ -222,7 +223,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
assertFalse(sub.eof()); | |||
assertEquals(paths[2], nameOf(sub)); | |||
assertEquals(paths[2].length(), subfti.getEntryLength()); | |||
assertEquals(mtime[2], subfti.getEntryLastModified()); | |||
assertEquals(mtime[2], subfti.getEntryLastModifiedInstant()); | |||
sub.next(1); | |||
assertTrue(sub.eof()); | |||
@@ -233,7 +234,7 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); | |||
assertEquals(paths[3], nameOf(top)); | |||
assertEquals(paths[3].length(), top.getEntryLength()); | |||
assertEquals(mtime[3], top.getEntryLastModified()); | |||
assertEquals(mtime[3], top.getEntryLastModifiedInstant()); | |||
top.next(1); | |||
assertTrue(top.eof()); | |||
@@ -345,20 +346,21 @@ public class FileTreeIteratorTest extends RepositoryTestCase { | |||
@Test | |||
public void testIsModifiedFileSmudged() throws Exception { | |||
File f = writeTrashFile("file", "content"); | |||
FS fs = db.getFS(); | |||
try (Git git = new Git(db)) { | |||
// The idea of this test is to check the smudged handling | |||
// Hopefully fsTick will make sure our entry gets smudged | |||
fsTick(f); | |||
writeTrashFile("file", "content"); | |||
long lastModified = f.lastModified(); | |||
Instant lastModified = fs.lastModifiedInstant(f); | |||
git.add().addFilepattern("file").call(); | |||
writeTrashFile("file", "conten2"); | |||
f.setLastModified(lastModified); | |||
fs.setLastModified(f.toPath(), lastModified); | |||
// We cannot trust this to go fast enough on | |||
// a system with less than one-second lastModified | |||
// resolution, so we force the index to have the | |||
// same timestamp as the file we look at. | |||
db.getIndexFile().setLastModified(lastModified); | |||
fs.setLastModified(db.getIndexFile().toPath(), lastModified); | |||
} | |||
DirCacheEntry dce = db.readDirCache().getEntry("file"); | |||
FileTreeIterator fti = new FileTreeIterator(trash, db.getFS(), db |
@@ -43,6 +43,7 @@ | |||
package org.eclipse.jgit.util; | |||
import static java.time.Instant.EPOCH; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertFalse; | |||
import static org.junit.Assert.assertTrue; | |||
@@ -106,7 +107,7 @@ public class FSTest { | |||
assertTrue(fs.exists(link)); | |||
String targetName = fs.readSymLink(link); | |||
assertEquals("å", targetName); | |||
assertTrue(fs.lastModified(link) > 0); | |||
assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0); | |||
assertTrue(fs.exists(link)); | |||
assertFalse(fs.canExecute(link)); | |||
assertEquals(2, fs.length(link)); | |||
@@ -119,8 +120,9 @@ public class FSTest { | |||
// Now create the link target | |||
FileUtils.createNewFile(target); | |||
assertTrue(fs.exists(link)); | |||
assertTrue(fs.lastModified(link) > 0); | |||
assertTrue(fs.lastModified(target) > fs.lastModified(link)); | |||
assertTrue(fs.lastModifiedInstant(link).compareTo(EPOCH) > 0); | |||
assertTrue(fs.lastModifiedInstant(target) | |||
.compareTo(fs.lastModifiedInstant(link)) > 0); | |||
assertFalse(fs.canExecute(link)); | |||
fs.setExecute(target, true); | |||
assertFalse(fs.canExecute(link)); |
@@ -8,6 +8,20 @@ | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/dircache/DirCacheEntry.java" type="org.eclipse.jgit.dircache.DirCacheEntry"> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="getLastModifiedInstant()"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="setLastModified(Instant)"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException"> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
@@ -130,6 +144,28 @@ | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator"> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="getEntryLastModifiedInstant()"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator$Entry"> | |||
<filter id="336695337"> | |||
<message_arguments> | |||
<message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator.Entry"/> | |||
<message_argument value="getLastModifiedInstant()"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="getLastModifiedInstant()"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS"> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
@@ -137,12 +173,30 @@ | |||
<message_argument value="fileAttributes(File)"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="lastModifiedInstant(File)"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="lastModifiedInstant(Path)"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="setAsyncfileStoreAttrCache(boolean)"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="setLastModified(Path, Instant)"/> | |||
</message_arguments> | |||
</filter> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.2.3"/> | |||
@@ -150,6 +204,14 @@ | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$Attributes"> | |||
<filter id="1142947843"> | |||
<message_arguments> | |||
<message_argument value="5.1.9"/> | |||
<message_argument value="getLastModifiedInstant()"/> | |||
</message_arguments> | |||
</filter> | |||
</resource> | |||
<resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils"> | |||
<filter id="1142947843"> | |||
<message_arguments> |
@@ -561,6 +561,7 @@ rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry | |||
readConfigFailed=Reading config file ''{0}'' failed | |||
readerIsRequired=Reader is required | |||
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} | |||
readLastModifiedFailed=Reading lastModified of {0} failed | |||
readTimedOut=Read timed out after {0} ms | |||
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes. | |||
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes. |
@@ -50,6 +50,7 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.time.Instant; | |||
import java.util.Collection; | |||
import java.util.LinkedList; | |||
@@ -228,7 +229,7 @@ public class AddCommand extends GitCommand<DirCache> { | |||
if (GITLINK != mode) { | |||
entry.setLength(f.getEntryLength()); | |||
entry.setLastModified(f.getEntryLastModified()); | |||
entry.setLastModified(f.getEntryLastModifiedInstant()); | |||
long len = f.getEntryContentLength(); | |||
// We read and filter the content multiple times. | |||
// f.getEntryContentLength() reads and filters the input and | |||
@@ -241,7 +242,7 @@ public class AddCommand extends GitCommand<DirCache> { | |||
} | |||
} else { | |||
entry.setLength(0); | |||
entry.setLastModified(0); | |||
entry.setLastModified(Instant.ofEpochSecond(0)); | |||
entry.setObjectId(f.getEntryObjectId()); | |||
} | |||
builder.add(entry); |
@@ -392,7 +392,7 @@ public class CommitCommand extends GitCommand<RevCommit> { | |||
final DirCacheEntry dcEntry = new DirCacheEntry(path); | |||
long entryLength = fTree.getEntryLength(); | |||
dcEntry.setLength(entryLength); | |||
dcEntry.setLastModified(fTree.getEntryLastModified()); | |||
dcEntry.setLastModified(fTree.getEntryLastModifiedInstant()); | |||
dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); | |||
boolean objectExists = (dcTree != null |
@@ -422,7 +422,7 @@ public class ResetCommand extends GitCommand<Ref> { | |||
DirCacheIterator.class); | |||
if (dcIter != null && dcIter.idEqual(cIter)) { | |||
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | |||
entry.setLastModified(indexEntry.getLastModified()); | |||
entry.setLastModified(indexEntry.getLastModifiedInstant()); | |||
entry.setLength(indexEntry.getLength()); | |||
} | |||
@@ -332,7 +332,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { | |||
DirCacheIterator.class); | |||
if (dcIter != null && dcIter.idEqual(cIter)) { | |||
DirCacheEntry indexEntry = dcIter.getDirCacheEntry(); | |||
entry.setLastModified(indexEntry.getLastModified()); | |||
entry.setLastModified(indexEntry.getLastModifiedInstant()); | |||
entry.setLength(indexEntry.getLength()); | |||
} | |||
@@ -300,7 +300,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> { | |||
final DirCacheEntry entry = new DirCacheEntry( | |||
treeWalk.getRawPath()); | |||
entry.setLength(wtIter.getEntryLength()); | |||
entry.setLastModified(wtIter.getEntryLastModified()); | |||
entry.setLastModified( | |||
wtIter.getEntryLastModifiedInstant()); | |||
entry.setFileMode(wtIter.getEntryFileMode()); | |||
long contentLength = wtIter.getEntryContentLength(); | |||
try (InputStream in = wtIter.openEntryStream()) { |
@@ -497,8 +497,9 @@ public class DirCache { | |||
throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries); | |||
snapshot = FileSnapshot.save(liveFile); | |||
int smudge_s = (int) (snapshot.lastModified() / 1000); | |||
int smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; | |||
// TODO (ms) combine smudge_s and smudge_ns into Duration | |||
int smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond()); | |||
int smudge_ns = snapshot.lastModifiedInstant().getNano(); | |||
// Load the individual file entries. | |||
// | |||
@@ -679,8 +680,8 @@ public class DirCache { | |||
// so we use the current timestamp as a approximation. | |||
myLock.createCommitSnapshot(); | |||
snapshot = myLock.getCommitSnapshot(); | |||
smudge_s = (int) (snapshot.lastModified() / 1000); | |||
smudge_ns = ((int) (snapshot.lastModified() % 1000)) * 1000000; | |||
smudge_s = (int) (snapshot.lastModifiedInstant().getEpochSecond()); | |||
smudge_ns = snapshot.lastModifiedInstant().getNano(); | |||
} else { | |||
// Used in unit tests only | |||
smudge_ns = 0; | |||
@@ -1025,7 +1026,7 @@ public class DirCache { | |||
DirCacheEntry entry = iIter.getDirCacheEntry(); | |||
if (entry.isSmudged() && iIter.idEqual(fIter)) { | |||
entry.setLength(fIter.getEntryLength()); | |||
entry.setLastModified(fIter.getEntryLastModified()); | |||
entry.setLastModified(fIter.getEntryLastModifiedInstant()); | |||
} | |||
} | |||
} |
@@ -50,6 +50,7 @@ import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.nio.file.StandardCopyOption; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
@@ -425,8 +426,10 @@ public class DirCacheCheckout { | |||
// update the timestamp of the index with the one from the | |||
// file if not set, as we are sure to be in sync here. | |||
DirCacheEntry entry = i.getDirCacheEntry(); | |||
if (entry.getLastModified() == 0) | |||
entry.setLastModified(f.getEntryLastModified()); | |||
Instant mtime = entry.getLastModifiedInstant(); | |||
if (mtime == null || mtime.equals(Instant.EPOCH)) { | |||
entry.setLastModified(f.getEntryLastModifiedInstant()); | |||
} | |||
keep(entry); | |||
} | |||
} else | |||
@@ -611,7 +614,7 @@ public class DirCacheCheckout { | |||
File gitlinkDir = new File(repo.getWorkTree(), path); | |||
FileUtils.mkdirs(gitlinkDir, true); | |||
FS fs = repo.getFS(); | |||
entry.setLastModified(fs.lastModified(gitlinkDir)); | |||
entry.setLastModified(fs.lastModifiedInstant(gitlinkDir)); | |||
} | |||
private static ArrayList<String> filterOut(ArrayList<String> strings, | |||
@@ -1433,7 +1436,7 @@ public class DirCacheCheckout { | |||
} | |||
fs.createSymLink(f, target); | |||
entry.setLength(bytes.length); | |||
entry.setLastModified(fs.lastModified(f)); | |||
entry.setLastModified(fs.lastModifiedInstant(f)); | |||
return; | |||
} | |||
@@ -1502,7 +1505,7 @@ public class DirCacheCheckout { | |||
FileUtils.delete(tmpFile); | |||
} | |||
} | |||
entry.setLastModified(fs.lastModified(f)); | |||
entry.setLastModified(fs.lastModifiedInstant(f)); | |||
} | |||
// Run an external filter command |
@@ -56,6 +56,7 @@ import java.io.OutputStream; | |||
import java.nio.ByteBuffer; | |||
import java.security.MessageDigest; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import org.eclipse.jgit.errors.CorruptObjectException; | |||
@@ -144,6 +145,7 @@ public class DirCacheEntry { | |||
/** Flags which are never stored to disk. */ | |||
private byte inCoreFlags; | |||
// TODO (ms): use Instant to combine smudge_s and smudge_ns | |||
DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, | |||
final InputStream in, final MessageDigest md, final int smudge_s, | |||
final int smudge_ns) throws IOException { | |||
@@ -563,21 +565,50 @@ public class DirCacheEntry { | |||
* | |||
* @return last modification time of this file, in milliseconds since the | |||
* Java epoch (midnight Jan 1, 1970 UTC). | |||
* @deprecated use {@link #getLastModifiedInstant()} instead | |||
*/ | |||
@Deprecated | |||
public long getLastModified() { | |||
return decodeTS(P_MTIME); | |||
} | |||
/** | |||
* Get the cached last modification date of this file. | |||
* <p> | |||
* One of the indicators that the file has been modified by an application | |||
* changing the working tree is if the last modification time for the file | |||
* differs from the time stored in this entry. | |||
* | |||
* @return last modification time of this file. | |||
* @since 5.1.9 | |||
*/ | |||
public Instant getLastModifiedInstant() { | |||
return decodeTSInstant(P_MTIME); | |||
} | |||
/** | |||
* Set the cached last modification date of this file, using milliseconds. | |||
* | |||
* @param when | |||
* new cached modification date of the file, in milliseconds. | |||
* @deprecated use {@link #setLastModified(Instant)} instead | |||
*/ | |||
@Deprecated | |||
public void setLastModified(long when) { | |||
encodeTS(P_MTIME, when); | |||
} | |||
/** | |||
* Set the cached last modification date of this file. | |||
* | |||
* @param when | |||
* new cached modification date of the file. | |||
* @since 5.1.9 | |||
*/ | |||
public void setLastModified(Instant when) { | |||
encodeTS(P_MTIME, when); | |||
} | |||
/** | |||
* Get the cached size (mod 4 GB) (in bytes) of this file. | |||
* <p> | |||
@@ -692,7 +723,8 @@ public class DirCacheEntry { | |||
@SuppressWarnings("nls") | |||
@Override | |||
public String toString() { | |||
return getFileMode() + " " + getLength() + " " + getLastModified() | |||
return getFileMode() + " " + getLength() + " " | |||
+ getLastModifiedInstant() | |||
+ " " + getObjectId() + " " + getStage() + " " | |||
+ getPathString() + "\n"; | |||
} | |||
@@ -750,12 +782,25 @@ public class DirCacheEntry { | |||
return 1000L * sec + ms; | |||
} | |||
private Instant decodeTSInstant(int pIdx) { | |||
final int base = infoOffset + pIdx; | |||
final int sec = NB.decodeInt32(info, base); | |||
final int nano = NB.decodeInt32(info, base + 4); | |||
return Instant.ofEpochSecond(sec, nano); | |||
} | |||
private void encodeTS(int pIdx, long when) { | |||
final int base = infoOffset + pIdx; | |||
NB.encodeInt32(info, base, (int) (when / 1000)); | |||
NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); | |||
} | |||
private void encodeTS(int pIdx, Instant when) { | |||
final int base = infoOffset + pIdx; | |||
NB.encodeInt32(info, base, (int) when.getEpochSecond()); | |||
NB.encodeInt32(info, base + 4, when.getNano()); | |||
} | |||
private int getExtendedFlags() { | |||
if (isExtended()) | |||
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; |
@@ -622,6 +622,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String readConfigFailed; | |||
/***/ public String readerIsRequired; | |||
/***/ public String readingObjectsFromLocalRepositoryFailed; | |||
/***/ public String readLastModifiedFailed; | |||
/***/ public String readTimedOut; | |||
/***/ public String receivePackObjectTooLarge1; | |||
/***/ public String receivePackObjectTooLarge2; |
@@ -48,10 +48,10 @@ import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.attribute.BasicFileAttributes; | |||
import java.text.DateFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.time.Duration; | |||
import java.util.Date; | |||
import java.time.Instant; | |||
import java.time.ZoneId; | |||
import java.time.format.DateTimeFormatter; | |||
import java.util.Locale; | |||
import java.util.Objects; | |||
import java.util.concurrent.TimeUnit; | |||
@@ -87,8 +87,14 @@ public class FileSnapshot { | |||
*/ | |||
public static final long UNKNOWN_SIZE = -1; | |||
private static final Instant UNKNOWN_TIME = Instant.ofEpochMilli(-1); | |||
private static final Object MISSING_FILEKEY = new Object(); | |||
private static final DateTimeFormatter dateFmt = DateTimeFormatter | |||
.ofPattern("yyyy-MM-dd HH:mm:ss.nnnnnnnnn") //$NON-NLS-1$ | |||
.withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault()); | |||
/** | |||
* A FileSnapshot that is considered to always be modified. | |||
* <p> | |||
@@ -96,8 +102,8 @@ public class FileSnapshot { | |||
* file, but only after {@link #isModified(File)} gets invoked. The returned | |||
* snapshot contains only invalid status information. | |||
*/ | |||
public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, | |||
UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); | |||
public static final FileSnapshot DIRTY = new FileSnapshot(UNKNOWN_TIME, | |||
UNKNOWN_TIME, UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); | |||
/** | |||
* A FileSnapshot that is clean if the file does not exist. | |||
@@ -106,8 +112,8 @@ public class FileSnapshot { | |||
* file to be clean. {@link #isModified(File)} will return false if the file | |||
* path does not exist. | |||
*/ | |||
public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0, | |||
Duration.ZERO, MISSING_FILEKEY) { | |||
public static final FileSnapshot MISSING_FILE = new FileSnapshot( | |||
Instant.EPOCH, Instant.EPOCH, 0, Duration.ZERO, MISSING_FILEKEY) { | |||
@Override | |||
public boolean isModified(File path) { | |||
return FS.DETECTED.exists(path); | |||
@@ -163,18 +169,41 @@ public class FileSnapshot { | |||
* @param modified | |||
* the last modification time of the file | |||
* @return the snapshot. | |||
* @deprecated use {@link #save(Instant)} instead. | |||
*/ | |||
@Deprecated | |||
public static FileSnapshot save(long modified) { | |||
final long read = System.currentTimeMillis(); | |||
final Instant read = Instant.now(); | |||
return new FileSnapshot(read, Instant.ofEpochMilli(modified), | |||
UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY); | |||
} | |||
/** | |||
* Record a snapshot for a file for which the last modification time is | |||
* already known. | |||
* <p> | |||
* This method should be invoked before the file is accessed. | |||
* <p> | |||
* Note that this method cannot rely on measuring file timestamp resolution | |||
* to avoid racy git issues caused by finite file timestamp resolution since | |||
* it's unknown in which filesystem the file is located. Hence the worst | |||
* case fallback for timestamp resolution is used. | |||
* | |||
* @param modified | |||
* the last modification time of the file | |||
* @return the snapshot. | |||
*/ | |||
public static FileSnapshot save(Instant modified) { | |||
final Instant read = Instant.now(); | |||
return new FileSnapshot(read, modified, UNKNOWN_SIZE, Duration.ZERO, | |||
MISSING_FILEKEY); | |||
} | |||
/** Last observed modification time of the path. */ | |||
private final long lastModified; | |||
private final Instant lastModified; | |||
/** Last wall-clock time the path was read. */ | |||
private volatile long lastRead; | |||
private volatile Instant lastRead; | |||
/** True once {@link #lastRead} is far later than {@link #lastModified}. */ | |||
private boolean cannotBeRacilyClean; | |||
@@ -222,7 +251,7 @@ public class FileSnapshot { | |||
*/ | |||
protected FileSnapshot(File file, boolean useConfig) { | |||
this.file = file; | |||
this.lastRead = System.currentTimeMillis(); | |||
this.lastRead = Instant.now(); | |||
this.fsTimestampResolution = useConfig | |||
? FS.getFsTimerResolution(file.toPath().getParent()) | |||
: FALLBACK_TIMESTAMP_RESOLUTION; | |||
@@ -230,18 +259,20 @@ public class FileSnapshot { | |||
try { | |||
fileAttributes = FS.DETECTED.fileAttributes(file); | |||
} catch (IOException e) { | |||
this.lastModified = file.lastModified(); | |||
this.lastModified = Instant.ofEpochMilli(file.lastModified()); | |||
this.size = file.length(); | |||
this.fileKey = MISSING_FILEKEY; | |||
return; | |||
} | |||
this.lastModified = fileAttributes.lastModifiedTime().toMillis(); | |||
this.lastModified = fileAttributes.lastModifiedTime().toInstant(); | |||
this.size = fileAttributes.size(); | |||
this.fileKey = getFileKey(fileAttributes); | |||
LOG.debug("file={}, create new FileSnapshot: lastRead={} ms" //$NON-NLS-1$ | |||
+ ", lastModified={} ms, size={}, fileKey={}", //$NON-NLS-1$ | |||
file, Long.valueOf(lastRead), Long.valueOf(lastModified), | |||
Long.valueOf(size), fileKey); | |||
if (LOG.isDebugEnabled()) { | |||
LOG.debug("file={}, create new FileSnapshot: lastRead={}, lastModified={}, size={}, fileKey={}", //$NON-NLS-1$ | |||
file, dateFmt.format(lastRead), | |||
dateFmt.format(lastModified), Long.valueOf(size), | |||
fileKey.toString()); | |||
} | |||
} | |||
private boolean sizeChanged; | |||
@@ -252,7 +283,7 @@ public class FileSnapshot { | |||
private boolean wasRacyClean; | |||
private FileSnapshot(long read, long modified, long size, | |||
private FileSnapshot(Instant read, Instant modified, long size, | |||
@NonNull Duration fsTimestampResolution, @NonNull Object fileKey) { | |||
this.file = null; | |||
this.lastRead = read; | |||
@@ -266,8 +297,19 @@ public class FileSnapshot { | |||
* Get time of last snapshot update | |||
* | |||
* @return time of last snapshot update | |||
* @deprecated use {@link #lastModifiedInstant()} instead | |||
*/ | |||
@Deprecated | |||
public long lastModified() { | |||
return lastModified.toEpochMilli(); | |||
} | |||
/** | |||
* Get time of last snapshot update | |||
* | |||
* @return time of last snapshot update | |||
*/ | |||
public Instant lastModifiedInstant() { | |||
return lastModified; | |||
} | |||
@@ -286,16 +328,16 @@ public class FileSnapshot { | |||
* @return true if the path needs to be read again. | |||
*/ | |||
public boolean isModified(File path) { | |||
long currLastModified; | |||
Instant currLastModified; | |||
long currSize; | |||
Object currFileKey; | |||
try { | |||
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); | |||
currLastModified = fileAttributes.lastModifiedTime().toMillis(); | |||
currLastModified = fileAttributes.lastModifiedTime().toInstant(); | |||
currSize = fileAttributes.size(); | |||
currFileKey = getFileKey(fileAttributes); | |||
} catch (IOException e) { | |||
currLastModified = path.lastModified(); | |||
currLastModified = Instant.ofEpochMilli(path.lastModified()); | |||
currSize = path.length(); | |||
currFileKey = MISSING_FILEKEY; | |||
} | |||
@@ -337,7 +379,7 @@ public class FileSnapshot { | |||
* the other snapshot. | |||
*/ | |||
public void setClean(FileSnapshot other) { | |||
final long now = other.lastRead; | |||
final Instant now = other.lastRead; | |||
if (!isRacyClean(now)) { | |||
cannotBeRacilyClean = true; | |||
} | |||
@@ -351,7 +393,7 @@ public class FileSnapshot { | |||
* if sleep was interrupted | |||
*/ | |||
public void waitUntilNotRacy() throws InterruptedException { | |||
while (isRacyClean(System.currentTimeMillis())) { | |||
while (isRacyClean(Instant.now())) { | |||
TimeUnit.NANOSECONDS | |||
.sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10); | |||
} | |||
@@ -366,7 +408,7 @@ public class FileSnapshot { | |||
*/ | |||
public boolean equals(FileSnapshot other) { | |||
boolean sizeEq = size == UNKNOWN_SIZE || other.size == UNKNOWN_SIZE || size == other.size; | |||
return lastModified == other.lastModified && sizeEq | |||
return lastModified.equals(other.lastModified) && sizeEq | |||
&& Objects.equals(fileKey, other.fileKey); | |||
} | |||
@@ -389,8 +431,7 @@ public class FileSnapshot { | |||
/** {@inheritDoc} */ | |||
@Override | |||
public int hashCode() { | |||
return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size), | |||
fileKey); | |||
return Objects.hash(lastModified, Long.valueOf(size), fileKey); | |||
} | |||
/** | |||
@@ -435,34 +476,37 @@ public class FileSnapshot { | |||
if (this == MISSING_FILE) { | |||
return "MISSING_FILE"; | |||
} | |||
DateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", | |||
Locale.US); | |||
return "FileSnapshot[modified: " + f.format(new Date(lastModified)) | |||
+ ", read: " + f.format(new Date(lastRead)) + ", size:" + size | |||
return "FileSnapshot[modified: " + dateFmt.format(lastModified) | |||
+ ", read: " + dateFmt.format(lastRead) + ", size:" + size | |||
+ ", fileKey: " + fileKey + "]"; | |||
} | |||
private boolean isRacyClean(long read) { | |||
private boolean isRacyClean(Instant read) { | |||
// add a 10% safety margin | |||
long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10; | |||
long delta = (read - lastModified) * 1_000_000; | |||
long delta = Duration.between(lastModified, read).toNanos(); | |||
wasRacyClean = delta <= racyNanos; | |||
LOG.debug("file={}, isRacyClean={}, read={} ms, lastModified={} ms," //$NON-NLS-1$ | |||
+ " delta={} ns, racy<={} ns", //$NON-NLS-1$ | |||
file, Boolean.valueOf(wasRacyClean), Long.valueOf(read), | |||
Long.valueOf(lastModified), Long.valueOf(delta), | |||
Long.valueOf(racyNanos)); | |||
if (LOG.isDebugEnabled()) { | |||
LOG.debug( | |||
"file={}, isRacyClean={}, read={}, lastModified={}, delta={} ns, racy<={} ns", //$NON-NLS-1$ | |||
file, Boolean.valueOf(wasRacyClean), dateFmt.format(read), | |||
dateFmt.format(lastModified), Long.valueOf(delta), | |||
Long.valueOf(racyNanos)); | |||
} | |||
return wasRacyClean; | |||
} | |||
private boolean isModified(long currLastModified) { | |||
private boolean isModified(Instant currLastModified) { | |||
// Any difference indicates the path was modified. | |||
lastModifiedChanged = lastModified != currLastModified; | |||
lastModifiedChanged = !lastModified.equals(currLastModified); | |||
if (lastModifiedChanged) { | |||
LOG.debug("file={}, lastModified changed from {} to {}", //$NON-NLS-1$ | |||
file, Long.valueOf(lastModified), | |||
Long.valueOf(currLastModified)); | |||
if (LOG.isDebugEnabled()) { | |||
LOG.debug( | |||
"file={}, lastModified changed from {} to {}", //$NON-NLS-1$ | |||
file, dateFmt.format(lastModified), | |||
dateFmt.format(currLastModified)); | |||
} | |||
return true; | |||
} | |||
@@ -372,8 +372,9 @@ public class GC { | |||
continue oldPackLoop; | |||
if (!oldPack.shouldBeKept() | |||
&& repo.getFS().lastModified( | |||
oldPack.getPackFile()) < packExpireDate) { | |||
&& repo.getFS() | |||
.lastModifiedInstant(oldPack.getPackFile()) | |||
.toEpochMilli() < packExpireDate) { | |||
oldPack.close(); | |||
if (shouldLoosen) { | |||
loosen(inserter, reader, oldPack, ids); | |||
@@ -561,8 +562,10 @@ public class GC { | |||
String fName = f.getName(); | |||
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) | |||
continue; | |||
if (repo.getFS().lastModified(f) >= expireDate) | |||
if (repo.getFS().lastModifiedInstant(f) | |||
.toEpochMilli() >= expireDate) { | |||
continue; | |||
} | |||
try { | |||
ObjectId id = ObjectId.fromString(d + fName); | |||
if (objectsToKeep.contains(id)) |
@@ -56,8 +56,11 @@ import java.io.OutputStream; | |||
import java.nio.ByteBuffer; | |||
import java.nio.channels.Channels; | |||
import java.nio.channels.FileChannel; | |||
import java.nio.file.Files; | |||
import java.nio.file.StandardCopyOption; | |||
import java.nio.file.attribute.FileTime; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.Constants; | |||
@@ -424,7 +427,12 @@ public class LockFile { | |||
FileSnapshot n = FileSnapshot.save(lck); | |||
while (o.equals(n)) { | |||
Thread.sleep(25 /* milliseconds */); | |||
lck.setLastModified(System.currentTimeMillis()); | |||
try { | |||
Files.setLastModifiedTime(lck.toPath(), | |||
FileTime.from(Instant.now())); | |||
} catch (IOException e) { | |||
n.waitUntilNotRacy(); | |||
} | |||
n = FileSnapshot.save(lck); | |||
} | |||
} | |||
@@ -474,11 +482,22 @@ public class LockFile { | |||
* Get the modification time of the output file when it was committed. | |||
* | |||
* @return modification time of the lock file right before we committed it. | |||
* @deprecated use {@link #getCommitLastModifiedInstant()} instead | |||
*/ | |||
@Deprecated | |||
public long getCommitLastModified() { | |||
return commitSnapshot.lastModified(); | |||
} | |||
/** | |||
* Get the modification time of the output file when it was committed. | |||
* | |||
* @return modification time of the lock file right before we committed it. | |||
*/ | |||
public Instant getCommitLastModifiedInstant() { | |||
return commitSnapshot.lastModifiedInstant(); | |||
} | |||
/** | |||
* Get the {@link FileSnapshot} just before commit. | |||
* |
@@ -60,6 +60,7 @@ import java.nio.channels.FileChannel.MapMode; | |||
import java.nio.file.AccessDeniedException; | |||
import java.nio.file.NoSuchFileException; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
@@ -107,7 +108,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { | |||
public static final Comparator<PackFile> SORT = new Comparator<PackFile>() { | |||
@Override | |||
public int compare(PackFile a, PackFile b) { | |||
return b.packLastModified - a.packLastModified; | |||
return b.packLastModified.compareTo(a.packLastModified); | |||
} | |||
}; | |||
@@ -132,7 +133,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { | |||
private int activeCopyRawData; | |||
int packLastModified; | |||
Instant packLastModified; | |||
private PackFileSnapshot fileSnapshot; | |||
@@ -172,7 +173,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { | |||
public PackFile(File packFile, int extensions) { | |||
this.packFile = packFile; | |||
this.fileSnapshot = PackFileSnapshot.save(packFile); | |||
this.packLastModified = (int) (fileSnapshot.lastModified() >> 10); | |||
this.packLastModified = fileSnapshot.lastModifiedInstant(); | |||
this.extensions = extensions; | |||
// Multiply by 31 here so we can more directly combine with another |
@@ -46,6 +46,7 @@ | |||
*/ | |||
package org.eclipse.jgit.merge; | |||
import static java.time.Instant.EPOCH; | |||
import static org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm.HISTOGRAM; | |||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DIFF_SECTION; | |||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_ALGORITHM; | |||
@@ -59,6 +60,7 @@ import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
@@ -464,7 +466,7 @@ public class ResolveMerger extends ThreeWayMerger { | |||
* @return the entry which was added to the index | |||
*/ | |||
private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage, | |||
long lastMod, long len) { | |||
Instant lastMod, long len) { | |||
if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) { | |||
DirCacheEntry e = new DirCacheEntry(path, stage); | |||
e.setFileMode(p.getEntryFileMode()); | |||
@@ -491,7 +493,7 @@ public class ResolveMerger extends ThreeWayMerger { | |||
e.getStage()); | |||
newEntry.setFileMode(e.getFileMode()); | |||
newEntry.setObjectId(e.getObjectId()); | |||
newEntry.setLastModified(e.getLastModified()); | |||
newEntry.setLastModified(e.getLastModifiedInstant()); | |||
newEntry.setLength(e.getLength()); | |||
builder.add(newEntry); | |||
return newEntry; | |||
@@ -667,16 +669,17 @@ public class ResolveMerger extends ThreeWayMerger { | |||
// we know about length and lastMod only after we have written the new content. | |||
// This will happen later. Set these values to 0 for know. | |||
DirCacheEntry e = add(tw.getRawPath(), theirs, | |||
DirCacheEntry.STAGE_0, 0, 0); | |||
DirCacheEntry.STAGE_0, EPOCH, 0); | |||
addToCheckout(tw.getPathString(), e, attributes); | |||
} | |||
return true; | |||
} else { | |||
// FileModes are not mergeable. We found a conflict on modes. | |||
// For conflicting entries we don't know lastModified and length. | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, | |||
0); | |||
unmergedPaths.add(tw.getPathString()); | |||
mergeResults.put( | |||
tw.getPathString(), | |||
@@ -708,7 +711,7 @@ public class ResolveMerger extends ThreeWayMerger { | |||
// the new content. | |||
// This will happen later. Set these values to 0 for know. | |||
DirCacheEntry e = add(tw.getRawPath(), theirs, | |||
DirCacheEntry.STAGE_0, 0, 0); | |||
DirCacheEntry.STAGE_0, EPOCH, 0); | |||
if (e != null) { | |||
addToCheckout(tw.getPathString(), e, attributes); | |||
} | |||
@@ -737,16 +740,16 @@ public class ResolveMerger extends ThreeWayMerger { | |||
// detected later | |||
if (nonTree(modeO) && !nonTree(modeT)) { | |||
if (nonTree(modeB)) | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); | |||
unmergedPaths.add(tw.getPathString()); | |||
enterSubtree = false; | |||
return true; | |||
} | |||
if (nonTree(modeT) && !nonTree(modeO)) { | |||
if (nonTree(modeB)) | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); | |||
unmergedPaths.add(tw.getPathString()); | |||
enterSubtree = false; | |||
return true; | |||
@@ -773,9 +776,9 @@ public class ResolveMerger extends ThreeWayMerger { | |||
boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT); | |||
// Don't attempt to resolve submodule link conflicts | |||
if (gitlinkConflict || !attributes.canBeContentMerged()) { | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); | |||
if (gitlinkConflict) { | |||
MergeResult<SubmoduleConflict> result = new MergeResult<>( | |||
@@ -822,10 +825,10 @@ public class ResolveMerger extends ThreeWayMerger { | |||
MergeResult<RawText> result = contentMerge(base, ours, theirs, | |||
attributes); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); | |||
DirCacheEntry e = add(tw.getRawPath(), theirs, | |||
DirCacheEntry.STAGE_3, 0, 0); | |||
DirCacheEntry.STAGE_3, EPOCH, 0); | |||
// OURS was deleted checkout THEIRS | |||
if (modeO == 0) { | |||
@@ -957,9 +960,9 @@ public class ResolveMerger extends ThreeWayMerger { | |||
// A conflict occurred, the file will contain conflict markers | |||
// the index will be populated with the three stages and the | |||
// workdir (if used) contains the halfway merged content. | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, 0, 0); | |||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); | |||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); | |||
add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); | |||
mergeResults.put(tw.getPathString(), result); | |||
return; | |||
} | |||
@@ -976,7 +979,7 @@ public class ResolveMerger extends ThreeWayMerger { | |||
? FileMode.REGULAR_FILE : FileMode.fromBits(newMode)); | |||
if (mergedFile != null) { | |||
dce.setLastModified( | |||
nonNullRepo().getFS().lastModified(mergedFile)); | |||
nonNullRepo().getFS().lastModifiedInstant(mergedFile)); | |||
dce.setLength((int) mergedFile.length()); | |||
} | |||
dce.setObjectId(insertMergeResult(rawMerged, attributes)); |
@@ -46,6 +46,7 @@ import java.io.BufferedReader; | |||
import java.io.File; | |||
import java.io.FileReader; | |||
import java.io.IOException; | |||
import java.time.Instant; | |||
import java.util.Collection; | |||
import java.util.HashMap; | |||
import java.util.Locale; | |||
@@ -123,7 +124,7 @@ public class NetRC { | |||
private File netrc; | |||
private long lastModified; | |||
private Instant lastModified; | |||
private Map<String, NetRCEntry> hosts = new HashMap<>(); | |||
@@ -187,8 +188,10 @@ public class NetRC { | |||
if (netrc == null) | |||
return null; | |||
if (this.lastModified != this.netrc.lastModified()) | |||
if (!this.lastModified | |||
.equals(FS.DETECTED.lastModifiedInstant(this.netrc))) { | |||
parse(); | |||
} | |||
NetRCEntry entry = this.hosts.get(host); | |||
@@ -209,7 +212,7 @@ public class NetRC { | |||
private void parse() { | |||
this.hosts.clear(); | |||
this.lastModified = this.netrc.lastModified(); | |||
this.lastModified = FS.DETECTED.lastModifiedInstant(this.netrc); | |||
try (BufferedReader r = new BufferedReader(new FileReader(netrc))) { | |||
String line = null; |
@@ -51,6 +51,7 @@ import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.security.AccessController; | |||
import java.security.PrivilegedAction; | |||
import java.time.Instant; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
@@ -166,7 +167,7 @@ public class OpenSshConfig implements ConfigRepository { | |||
private final File configFile; | |||
/** Modification time of {@link #configFile} when it was last loaded. */ | |||
private long lastModified; | |||
private Instant lastModified; | |||
/** | |||
* Encapsulates entries read out of the configuration file, and | |||
@@ -224,8 +225,8 @@ public class OpenSshConfig implements ConfigRepository { | |||
} | |||
private synchronized State refresh() { | |||
final long mtime = configFile.lastModified(); | |||
if (mtime != lastModified) { | |||
final Instant mtime = FS.DETECTED.lastModifiedInstant(configFile); | |||
if (!mtime.equals(lastModified)) { | |||
State newState = new State(); | |||
try (FileInputStream in = new FileInputStream(configFile)) { | |||
newState.entries = parse(in); |
@@ -53,6 +53,7 @@ import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.time.Instant; | |||
import org.eclipse.jgit.dircache.DirCacheIterator; | |||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
@@ -406,8 +407,14 @@ public class FileTreeIterator extends WorkingTreeIterator { | |||
} | |||
@Override | |||
@Deprecated | |||
public long getLastModified() { | |||
return attributes.getLastModifiedTime(); | |||
return attributes.getLastModifiedInstant().toEpochMilli(); | |||
} | |||
@Override | |||
public Instant getLastModifiedInstant() { | |||
return attributes.getLastModifiedInstant(); | |||
} | |||
@Override |
@@ -59,6 +59,7 @@ import java.nio.CharBuffer; | |||
import java.nio.charset.CharacterCodingException; | |||
import java.nio.charset.CharsetEncoder; | |||
import java.text.MessageFormat; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
@@ -645,11 +646,23 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* | |||
* @return last modified time of this file, in milliseconds since the epoch | |||
* (Jan 1, 1970 UTC). | |||
* @deprecated use {@link #getEntryLastModifiedInstant()} instead | |||
*/ | |||
@Deprecated | |||
public long getEntryLastModified() { | |||
return current().getLastModified(); | |||
} | |||
/** | |||
* Get the last modified time of this entry. | |||
* | |||
* @return last modified time of this file | |||
* @since 5.1.9 | |||
*/ | |||
public Instant getEntryLastModifiedInstant() { | |||
return current().getLastModifiedInstant(); | |||
} | |||
/** | |||
* Obtain an input stream to read the file content. | |||
* <p> | |||
@@ -924,30 +937,28 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
// Git under windows only stores seconds so we round the timestamp | |||
// Java gives us if it looks like the timestamp in index is seconds | |||
// only. Otherwise we compare the timestamp at millisecond precision, | |||
// only. Otherwise we compare the timestamp at nanosecond precision, | |||
// unless core.checkstat is set to "minimal", in which case we only | |||
// compare the whole second part. | |||
long cacheLastModified = entry.getLastModified(); | |||
long fileLastModified = getEntryLastModified(); | |||
long lastModifiedMillis = fileLastModified % 1000; | |||
long cacheMillis = cacheLastModified % 1000; | |||
if (getOptions().getCheckStat() == CheckStat.MINIMAL) { | |||
fileLastModified = fileLastModified - lastModifiedMillis; | |||
cacheLastModified = cacheLastModified - cacheMillis; | |||
} else if (cacheMillis == 0) | |||
fileLastModified = fileLastModified - lastModifiedMillis; | |||
// Some Java version on Linux return whole seconds only even when | |||
// the file systems supports more precision. | |||
else if (lastModifiedMillis == 0) | |||
cacheLastModified = cacheLastModified - cacheMillis; | |||
if (fileLastModified != cacheLastModified) | |||
Instant cacheLastModified = entry.getLastModifiedInstant(); | |||
Instant fileLastModified = getEntryLastModifiedInstant(); | |||
if ((getOptions().getCheckStat() == CheckStat.MINIMAL) | |||
|| (cacheLastModified.getNano() == 0) | |||
// Some Java version on Linux return whole seconds only even | |||
// when the file systems supports more precision. | |||
|| (fileLastModified.getNano() == 0)) { | |||
if (fileLastModified.getEpochSecond() != cacheLastModified | |||
.getEpochSecond()) { | |||
return MetadataDiff.DIFFER_BY_TIMESTAMP; | |||
} | |||
} | |||
if (!fileLastModified.equals(cacheLastModified)) { | |||
return MetadataDiff.DIFFER_BY_TIMESTAMP; | |||
else if (!entry.isSmudged()) | |||
// The file is clean when you look at timestamps. | |||
return MetadataDiff.EQUAL; | |||
else | |||
} else if (entry.isSmudged()) { | |||
return MetadataDiff.SMUDGED; | |||
} | |||
// The file is clean when when comparing timestamps | |||
return MetadataDiff.EQUAL; | |||
} | |||
/** | |||
@@ -1274,9 +1285,25 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
* instance member instead. | |||
* | |||
* @return time since the epoch (in ms) of the last change. | |||
* @deprecated use {@link #getLastModifiedInstant()} instead | |||
*/ | |||
@Deprecated | |||
public abstract long getLastModified(); | |||
/** | |||
* Get the last modified time of this entry. | |||
* <p> | |||
* <b>Note: Efficient implementation required.</b> | |||
* <p> | |||
* The implementation of this method must be efficient. If a subclass | |||
* needs to compute the value they should cache the reference within an | |||
* instance member instead. | |||
* | |||
* @return time of the last change. | |||
* @since 5.1.9 | |||
*/ | |||
public abstract Instant getLastModifiedInstant(); | |||
/** | |||
* Get the name of this entry within its directory. | |||
* <p> |
@@ -44,6 +44,7 @@ | |||
package org.eclipse.jgit.util; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static java.time.Instant.EPOCH; | |||
import static org.eclipse.jgit.lib.Constants.FALLBACK_TIMESTAMP_RESOLUTION; | |||
import java.io.BufferedReader; | |||
@@ -66,6 +67,7 @@ import java.security.AccessController; | |||
import java.security.PrivilegedAction; | |||
import java.text.MessageFormat; | |||
import java.time.Duration; | |||
import java.time.Instant; | |||
import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@@ -625,11 +627,41 @@ public abstract class FS { | |||
* @return last modified time of f | |||
* @throws java.io.IOException | |||
* @since 3.0 | |||
* @deprecated use {@link #lastModifiedInstant(Path)} instead | |||
*/ | |||
@Deprecated | |||
public long lastModified(File f) throws IOException { | |||
return FileUtils.lastModified(f); | |||
} | |||
/** | |||
* Get the last modified time of a file system object. If the OS/JRE support | |||
* symbolic links, the modification time of the link is returned, rather | |||
* than that of the link target. | |||
* | |||
* @param p | |||
* a {@link Path} object. | |||
* @return last modified time of p | |||
* @since 5.1.9 | |||
*/ | |||
public Instant lastModifiedInstant(Path p) { | |||
return FileUtils.lastModifiedInstant(p); | |||
} | |||
/** | |||
* Get the last modified time of a file system object. If the OS/JRE support | |||
* symbolic links, the modification time of the link is returned, rather | |||
* than that of the link target. | |||
* | |||
* @param f | |||
* a {@link File} object. | |||
* @return last modified time of p | |||
* @since 5.1.9 | |||
*/ | |||
public Instant lastModifiedInstant(File f) { | |||
return FileUtils.lastModifiedInstant(f.toPath()); | |||
} | |||
/** | |||
* Set the last modified time of a file system object. If the OS/JRE support | |||
* symbolic links, the link is modified, not the target, | |||
@@ -640,11 +672,28 @@ public abstract class FS { | |||
* last modified time | |||
* @throws java.io.IOException | |||
* @since 3.0 | |||
* @deprecated use {@link #setLastModified(Path, Instant)} instead | |||
*/ | |||
@Deprecated | |||
public void setLastModified(File f, long time) throws IOException { | |||
FileUtils.setLastModified(f, time); | |||
} | |||
/** | |||
* Set the last modified time of a file system object. If the OS/JRE support | |||
* symbolic links, the link is modified, not the target, | |||
* | |||
* @param p | |||
* a {@link Path} object. | |||
* @param time | |||
* last modified time | |||
* @throws java.io.IOException | |||
* @since 5.1.9 | |||
*/ | |||
public void setLastModified(Path p, Instant time) throws IOException { | |||
FileUtils.setLastModified(p, time); | |||
} | |||
/** | |||
* Get the length of a file or link, If the OS/JRE supports symbolic links | |||
* it's the length of the link, else the length of the target. | |||
@@ -1712,9 +1761,19 @@ public abstract class FS { | |||
/** | |||
* @return the time (milliseconds since 1970-01-01) when this object was | |||
* last modified | |||
* @deprecated use getLastModifiedInstant instead | |||
*/ | |||
@Deprecated | |||
public long getLastModifiedTime() { | |||
return lastModifiedTime; | |||
return lastModifiedInstant.toEpochMilli(); | |||
} | |||
/** | |||
* @return the time when this object was last modified | |||
* @since 5.1.9 | |||
*/ | |||
public Instant getLastModifiedInstant() { | |||
return lastModifiedInstant; | |||
} | |||
private final boolean isDirectory; | |||
@@ -1725,7 +1784,7 @@ public abstract class FS { | |||
private final long creationTime; | |||
private final long lastModifiedTime; | |||
private final Instant lastModifiedInstant; | |||
private final boolean isExecutable; | |||
@@ -1743,7 +1802,7 @@ public abstract class FS { | |||
Attributes(FS fs, File file, boolean exists, boolean isDirectory, | |||
boolean isExecutable, boolean isSymbolicLink, | |||
boolean isRegularFile, long creationTime, | |||
long lastModifiedTime, long length) { | |||
Instant lastModifiedInstant, long length) { | |||
this.fs = fs; | |||
this.file = file; | |||
this.exists = exists; | |||
@@ -1752,7 +1811,7 @@ public abstract class FS { | |||
this.isSymbolicLink = isSymbolicLink; | |||
this.isRegularFile = isRegularFile; | |||
this.creationTime = creationTime; | |||
this.lastModifiedTime = lastModifiedTime; | |||
this.lastModifiedInstant = lastModifiedInstant; | |||
this.length = length; | |||
} | |||
@@ -1764,7 +1823,7 @@ public abstract class FS { | |||
* @param path | |||
*/ | |||
public Attributes(File path, FS fs) { | |||
this(fs, path, false, false, false, false, false, 0L, 0L, 0L); | |||
this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L); | |||
} | |||
/** | |||
@@ -1810,7 +1869,7 @@ public abstract class FS { | |||
boolean exists = isDirectory || isFile; | |||
boolean canExecute = exists && !isDirectory && canExecute(path); | |||
boolean isSymlink = false; | |||
long lastModified = exists ? path.lastModified() : 0L; | |||
Instant lastModified = exists ? lastModifiedInstant(path) : EPOCH; | |||
long createTime = 0L; | |||
return new Attributes(this, path, exists, isDirectory, canExecute, | |||
isSymlink, isFile, createTime, lastModified, -1); |
@@ -149,7 +149,7 @@ public class FS_Win32 extends FS { | |||
attrs.isSymbolicLink(), | |||
attrs.isRegularFile(), | |||
attrs.creationTime().toMillis(), | |||
attrs.lastModifiedTime().toMillis(), | |||
attrs.lastModifiedTime().toInstant(), | |||
attrs.size()); | |||
result.add(new FileEntry(f, fs, attributes, | |||
fileModeStrategy)); |
@@ -76,11 +76,14 @@ import java.util.regex.Pattern; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.util.FS.Attributes; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
/** | |||
* File Utilities | |||
*/ | |||
public class FileUtils { | |||
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class); | |||
/** | |||
* Option to delete given {@code File} | |||
@@ -656,12 +659,31 @@ public class FileUtils { | |||
* @return lastModified attribute for given file, not following symbolic | |||
* links | |||
* @throws IOException | |||
* @deprecated use {@link #lastModifiedInstant(Path)} instead which returns | |||
* FileTime | |||
*/ | |||
@Deprecated | |||
static long lastModified(File file) throws IOException { | |||
return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) | |||
.toMillis(); | |||
} | |||
/** | |||
* @param path | |||
* @return lastModified attribute for given file, not following symbolic | |||
* links | |||
*/ | |||
static Instant lastModifiedInstant(Path path) { | |||
try { | |||
return Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS) | |||
.toInstant(); | |||
} catch (IOException e) { | |||
LOG.error(MessageFormat | |||
.format(JGitText.get().readLastModifiedFailed, path)); | |||
return Instant.ofEpochMilli(path.toFile().lastModified()); | |||
} | |||
} | |||
/** | |||
* Return all the attributes of a file, without following symbolic links. | |||
* | |||
@@ -680,10 +702,21 @@ public class FileUtils { | |||
* @param time | |||
* @throws IOException | |||
*/ | |||
@Deprecated | |||
static void setLastModified(File file, long time) throws IOException { | |||
Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); | |||
} | |||
/** | |||
* @param path | |||
* @param time | |||
* @throws IOException | |||
*/ | |||
static void setLastModified(Path path, Instant time) | |||
throws IOException { | |||
Files.setLastModifiedTime(path, FileTime.from(time)); | |||
} | |||
/** | |||
* @param file | |||
* @return {@code true} if the given file exists, not following symbolic | |||
@@ -788,7 +821,7 @@ public class FileUtils { | |||
readAttributes.isSymbolicLink(), | |||
readAttributes.isRegularFile(), // | |||
readAttributes.creationTime().toMillis(), // | |||
readAttributes.lastModifiedTime().toMillis(), | |||
readAttributes.lastModifiedTime().toInstant(), | |||
readAttributes.isSymbolicLink() ? Constants | |||
.encode(readSymLink(file)).length | |||
: readAttributes.size()); | |||
@@ -827,7 +860,7 @@ public class FileUtils { | |||
readAttributes.isSymbolicLink(), | |||
readAttributes.isRegularFile(), // | |||
readAttributes.creationTime().toMillis(), // | |||
readAttributes.lastModifiedTime().toMillis(), | |||
readAttributes.lastModifiedTime().toInstant(), | |||
readAttributes.size()); | |||
return attributes; | |||
} catch (IOException e) { |