diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
index 902416bdf..07b1bc789 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java
@@ -42,6 +42,7 @@
*/
package org.eclipse.jgit.internal.storage.file;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -98,6 +99,27 @@ public class FileSnapshotTest {
assertTrue(save.isModified(f1));
}
+ /**
+ * Check that file is modified by looking at its size.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void tesIsModifiedBySameLastModifiedAndDifferentSize() throws Exception {
+ File f1 = createFile("foo", "lorem".getBytes());
+ File f2 = createFile("bar", "lorem ipsum".getBytes());
+
+ f1.setLastModified(f2.lastModified()); // Make sure f1 and f2 have the same lastModified
+ FileSnapshot save = FileSnapshot.save(f1);
+
+ // Make sure that the modified and read timestamps are far enough, so that
+ // check is done by size
+ Thread.sleep(3000L);
+
+ assertEquals(save.lastModified(), f2.lastModified());
+ assertTrue(save.isModified(f2));
+ }
+
/**
* Create a file, wait long enough and verify that it has not been modified.
* 3.5 seconds mean any difference between file system timestamp and system
@@ -152,10 +174,22 @@ public class FileSnapshotTest {
return f;
}
+ private File createFile(String string, byte[] content) throws IOException {
+ File f = createFile(string);
+ append(f, content);
+ return f;
+ }
+
private static void append(File f, byte b) throws IOException {
+ append(f, new byte[] { b });
+ }
+
+ private static void append(File f, byte[] content) throws IOException {
FileOutputStream os = new FileOutputStream(f, true);
try {
- os.write(b);
+ for (byte b : content) {
+ os.write(b);
+ }
} finally {
os.close();
}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index d259fc85f..fe83211eb 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -22,4 +22,12 @@
+
@@ -76,7 +84,7 @@ 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); + public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, UNKNOWN_SIZE); /** * A FileSnapshot that is clean if the file does not exist. @@ -85,7 +93,7 @@ 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) { + public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0) { @Override public boolean isModified(File path) { return FS.DETECTED.exists(path); @@ -105,12 +113,16 @@ public class FileSnapshot { public static FileSnapshot save(File path) { long read = System.currentTimeMillis(); long modified; + long size; try { - modified = FS.DETECTED.lastModified(path); + BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); + modified = fileAttributes.lastModifiedTime().toMillis(); + size = fileAttributes.size(); } catch (IOException e) { modified = path.lastModified(); + size = path.length(); } - return new FileSnapshot(read, modified); + return new FileSnapshot(read, modified, size); } /** @@ -126,7 +138,7 @@ public class FileSnapshot { */ public static FileSnapshot save(long modified) { final long read = System.currentTimeMillis(); - return new FileSnapshot(read, modified); + return new FileSnapshot(read, modified, -1); } /** Last observed modification time of the path. */ @@ -138,10 +150,16 @@ public class FileSnapshot { /** True once {@link #lastRead} is far later than {@link #lastModified}. */ private boolean cannotBeRacilyClean; - private FileSnapshot(long read, long modified) { + /** Underlying file-system size in bytes. + * + * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ + private final long size; + + private FileSnapshot(long read, long modified, long size) { this.lastRead = read; this.lastModified = modified; this.cannotBeRacilyClean = notRacyClean(read); + this.size = size; } /** @@ -151,6 +169,13 @@ public class FileSnapshot { return lastModified; } + /** + * @return file size in bytes of last snapshot update + */ + public long size() { + return size; + } + /** * Check if the path may have been modified since the snapshot was saved. * @@ -160,12 +185,16 @@ public class FileSnapshot { */ public boolean isModified(File path) { long currLastModified; + long currSize; try { - currLastModified = FS.DETECTED.lastModified(path); + BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); + currLastModified = fileAttributes.lastModifiedTime().toMillis(); + currSize = fileAttributes.size(); } catch (IOException e) { currLastModified = path.lastModified(); + currSize = path.length(); } - return isModified(currLastModified); + return (currSize != UNKNOWN_SIZE && currSize != size) || isModified(currLastModified); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index e1fd1cb88..530b1d4bc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -52,6 +52,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.Charset; +import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.MessageFormat; @@ -417,6 +418,19 @@ public abstract class FS { */ public abstract boolean retryFailedLockFileCommit(); + /** + * Return all the attributes of a file, without following symbolic links. + * + * @param file + * @return {@link BasicFileAttributes} of the file + * @throws IOException in case of any I/O errors accessing the file + * + * @since 4.5.6 + */ + public BasicFileAttributes fileAttributes(File file) throws IOException { + return FileUtils.fileAttributes(file); + } + /** * Determine the user's home directory (location where preferences are). * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index aa101f73f..88b1f9ea0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -564,6 +564,19 @@ public class FileUtils { .toMillis(); } + /** + * Return all the attributes of a file, without following symbolic links. + * + * @param file + * @return {@link BasicFileAttributes} of the file + * @throws IOException in case of any I/O errors accessing the file + * + * @since 4.5.6 + */ + static BasicFileAttributes fileAttributes(File file) throws IOException { + return Files.readAttributes(file.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + } + /** * @param file * @param time