aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileSnapshotTest.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java52
2 files changed, 71 insertions, 9 deletions
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 9ceaa345d9..91273362f7 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
@@ -47,6 +47,9 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
@@ -127,6 +130,31 @@ public class FileSnapshotTest {
assertTrue(save.isModified(f1));
}
+ /**
+ * Simulate packfile replacement in same file which may occur if set of
+ * objects in the pack is the same but pack config was different. On Posix
+ * filesystems this should change the inode (filekey in java.nio
+ * terminology).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testSimulatePackfileReplacement() throws Exception {
+ File f1 = createFile("file"); // inode y
+ 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);
+ FileTime timestamp = Files.getLastModifiedTime(f1.toPath());
+ FileSnapshot save = FileSnapshot.save(f1);
+ Files.move(f2.toPath(), f1.toPath(), // Now "file" is inode x
+ StandardCopyOption.REPLACE_EXISTING,
+ StandardCopyOption.ATOMIC_MOVE);
+ Files.setLastModifiedTime(f1.toPath(), timestamp);
+ assertTrue(save.isModified(f1));
+ }
+
private File createFile(String string) throws IOException {
trash.mkdirs();
File f = File.createTempFile(string, "tdat", trash);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index d6b5fe57e1..09e15938ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -80,6 +80,8 @@ public class FileSnapshot {
*/
public static final long UNKNOWN_SIZE = -1;
+ private static final Object MISSING_FILEKEY = new Object();
+
/**
* A FileSnapshot that is considered to always be modified.
* <p>
@@ -88,7 +90,7 @@ public class FileSnapshot {
* snapshot contains only invalid status information.
*/
public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1,
- UNKNOWN_SIZE, Duration.ZERO);
+ UNKNOWN_SIZE, Duration.ZERO, MISSING_FILEKEY);
/**
* A FileSnapshot that is clean if the file does not exist.
@@ -98,7 +100,7 @@ public class FileSnapshot {
* path does not exist.
*/
public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0,
- Duration.ZERO) {
+ Duration.ZERO, MISSING_FILEKEY) {
@Override
public boolean isModified(File path) {
return FS.DETECTED.exists(path);
@@ -119,17 +121,26 @@ public class FileSnapshot {
long read = System.currentTimeMillis();
long modified;
long size;
+ Object fileKey = null;
Duration fsTimerResolution = FS
.getFsTimerResolution(path.toPath().getParent());
try {
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
modified = fileAttributes.lastModifiedTime().toMillis();
size = fileAttributes.size();
+ fileKey = getFileKey(fileAttributes);
} catch (IOException e) {
modified = path.lastModified();
size = path.length();
+ fileKey = MISSING_FILEKEY;
}
- return new FileSnapshot(read, modified, size, fsTimerResolution);
+ return new FileSnapshot(read, modified, size, fsTimerResolution,
+ fileKey);
+ }
+
+ private static Object getFileKey(BasicFileAttributes fileAttributes) {
+ Object fileKey = fileAttributes.fileKey();
+ return fileKey == null ? MISSING_FILEKEY : fileKey;
}
/**
@@ -149,7 +160,8 @@ public class FileSnapshot {
*/
public static FileSnapshot save(long modified) {
final long read = System.currentTimeMillis();
- return new FileSnapshot(read, modified, -1, Duration.ZERO);
+ return new FileSnapshot(read, modified, -1, Duration.ZERO,
+ MISSING_FILEKEY);
}
/** Last observed modification time of the path. */
@@ -169,13 +181,20 @@ public class FileSnapshot {
/** measured filesystem timestamp resolution */
private Duration fsTimestampResolution;
+ /**
+ * Object that uniquely identifies the given file, or {@code
+ * null} if a file key is not available
+ */
+ private Object fileKey;
+
private FileSnapshot(long read, long modified, long size,
- @NonNull Duration fsTimestampResolution) {
+ @NonNull Duration fsTimestampResolution, @NonNull Object fileKey) {
this.lastRead = read;
this.lastModified = modified;
this.fsTimestampResolution = fsTimestampResolution;
this.size = size;
this.cannotBeRacilyClean = notRacyClean(read);
+ this.fileKey = fileKey;
}
/**
@@ -204,15 +223,20 @@ public class FileSnapshot {
public boolean isModified(File path) {
long currLastModified;
long currSize;
+ Object currFileKey;
try {
BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
currLastModified = fileAttributes.lastModifiedTime().toMillis();
currSize = fileAttributes.size();
+ currFileKey = getFileKey(fileAttributes);
} catch (IOException e) {
currLastModified = path.lastModified();
currSize = path.length();
+ currFileKey = MISSING_FILEKEY;
}
- return (currSize != UNKNOWN_SIZE && currSize != size) || isModified(currLastModified);
+ return isSizeChanged(currSize)
+ || isFileKeyChanged(currFileKey)
+ || isModified(currLastModified);
}
/**
@@ -252,7 +276,8 @@ public class FileSnapshot {
* @return true if the two snapshots share the same information.
*/
public boolean equals(FileSnapshot other) {
- return lastModified == other.lastModified && size == other.size;
+ return lastModified == other.lastModified && size == other.size
+ && Objects.equals(fileKey, other.fileKey);
}
/** {@inheritDoc} */
@@ -274,7 +299,8 @@ public class FileSnapshot {
/** {@inheritDoc} */
@Override
public int hashCode() {
- return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size));
+ return Objects.hash(Long.valueOf(lastModified), Long.valueOf(size),
+ fileKey);
}
/** {@inheritDoc} */
@@ -291,7 +317,7 @@ public class FileSnapshot {
Locale.US);
return "FileSnapshot[modified: " + f.format(new Date(lastModified))
+ ", read: " + f.format(new Date(lastRead)) + ", size:" + size
- + "]";
+ + ", fileKey: " + fileKey + "]";
}
private boolean notRacyClean(long read) {
@@ -327,4 +353,12 @@ public class FileSnapshot {
//
return true;
}
+
+ private boolean isFileKeyChanged(Object currFileKey) {
+ return currFileKey != MISSING_FILEKEY && !currFileKey.equals(fileKey);
+ }
+
+ private boolean isSizeChanged(long currSize) {
+ return currSize != UNKNOWN_SIZE && currSize != size;
+ }
}