Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Halstrick2019-05-31 13:02:02 +0000
committerMatthias Sohn2019-06-05 12:57:02 +0000
commit5f8d91fded2f767ab609e58dcc22cc721b2e9c9d (patch)
tree200724ae8094f964c7886c31fb3c941fe1dc073f
parentb2ee9cfbc3b757dc7e7896981f08496c9d413602 (diff)
downloadjgit-5f8d91fded2f767ab609e58dcc22cc721b2e9c9d.tar.gz
jgit-5f8d91fded2f767ab609e58dcc22cc721b2e9c9d.tar.xz
jgit-5f8d91fded2f767ab609e58dcc22cc721b2e9c9d.zip
Test detecting modified packfiles
Test that JGit detects that packfiles have changed even if they are repacked multiple times in one tick of the filesystem timer. Test that this detection works also when repacking doesn't change the length or the filekey of the packfile. In this case where a modified file can't be detected by looking at file metadata JGit should still detect too fast modification by racy git checks and trigger rescanning the pack list and consequently rereading of packfile content. Change-Id: I67682cfb807c58afc6de9375224ff7489d6618fb Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java233
1 files changed, 222 insertions, 11 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
index 0e25c494e5..a1433e9fe5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java
@@ -43,28 +43,50 @@
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;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
+//import java.nio.file.attribute.BasicFileAttributes;
import java.text.ParseException;
import java.util.Collection;
+import java.util.Iterator;
import java.util.Random;
import java.util.zip.Deflater;
import org.eclipse.jgit.api.GarbageCollectCommand;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
+import org.eclipse.jgit.api.errors.UnmergedPathsException;
+import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
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.junit.Test;
public class PackFileSnapshotTest extends RepositoryTestCase {
+ private static ObjectId unknownID = ObjectId
+ .fromString("1234567890123456789012345678901234567890");
+
@Test
public void testSamePackDifferentCompressionDetectChecksumChanged()
throws Exception {
@@ -100,24 +122,213 @@ public class PackFileSnapshotTest extends RepositoryTestCase {
assertTrue("expected checksum changed", snapshot.isChecksumChanged(pf));
}
- private void appendRandomLine(File f) throws IOException {
+ private void appendRandomLine(File f, int length, Random r)
+ throws IOException {
try (Writer w = Files.newBufferedWriter(f.toPath(),
StandardOpenOption.APPEND)) {
- w.append(randomLine(20));
+ appendRandomLine(w, length, r);
}
}
- private String randomLine(int len) {
- final int a = 97; // 'a'
- int z = 122; // 'z'
- Random random = new Random();
- StringBuilder buffer = new StringBuilder(len);
+ private void appendRandomLine(File f) throws IOException {
+ appendRandomLine(f, 5, new Random());
+ }
+
+ private void appendRandomLine(Writer w, int len, Random r)
+ throws IOException {
+ final int c1 = 32; // ' '
+ int c2 = 126; // '~'
for (int i = 0; i < len; i++) {
- int rnd = a + (int) (random.nextFloat() * (z - a + 1));
- buffer.append((char) rnd);
+ w.append((char) (c1 + r.nextInt(1 + c2 - c1)));
}
- buffer.append('\n');
- return buffer.toString();
+ }
+
+ private ObjectId createTestRepo(int testDataSeed, int testDataLength)
+ throws IOException, GitAPIException, NoFilepatternException,
+ NoHeadException, NoMessageException, UnmergedPathsException,
+ ConcurrentRefUpdateException, WrongRepositoryStateException,
+ AbortedByHookException {
+ // Create a repo with two commits and one file. Each commit adds
+ // testDataLength number of bytes. Data are random bytes. Since the
+ // seed for the random number generator is specified we will get
+ // the same set of bytes for every run and for every platform
+ Random r = new Random(testDataSeed);
+ Git git = Git.wrap(db);
+ File f = writeTrashFile("file", "foobar ");
+ appendRandomLine(f, testDataLength, r);
+ git.add().addFilepattern("file").call();
+ git.commit().setMessage("message1").call();
+ appendRandomLine(f, testDataLength, r);
+ git.add().addFilepattern("file").call();
+ return git.commit().setMessage("message2").call().getId();
+ }
+
+ // Try repacking so fast that you get two new packs which differ only in
+ // content/chksum but have same name, size and lastmodified.
+ // Since this is done with standard gc (which creates new tmp files and
+ // renames them) the filekeys of the new packfiles differ helping jgit
+ // to detect the fast modification
+ @Test
+ public void testDetectModificationAlthoughSameSizeAndModificationtime()
+ throws Exception {
+ int testDataSeed = 1;
+ int testDataLength = 100;
+ FileBasedConfig config = db.getConfig();
+ // don't use mtime of the parent folder to detect pack file
+ // modification.
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
+ config.save();
+
+ createTestRepo(testDataSeed, testDataLength);
+
+ // repack to create initial packfile
+ PackFile pf = repackAndCheck(5, null, null, null);
+ Path packFilePath = pf.getPackFile().toPath();
+ AnyObjectId chk1 = pf.getPackChecksum();
+ String name = pf.getPackName();
+ Long length = Long.valueOf(pf.getPackFile().length());
+ long m1 = packFilePath.toFile().lastModified();
+
+ // Wait for a filesystem timer tick to enhance probability the rest of
+ // this test is done before the filesystem timer ticks again.
+ fsTick(packFilePath.toFile());
+
+ // Repack to create packfile with same name, length. Lastmodified and
+ // 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);
+
+ // 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();
+
+ // 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
+ // skip searching for new/modified packfiles
+ db.getObjectDatabase().has(unknownID);
+ assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
+ .getPackChecksum());
+ assumeTrue(m3 == m2);
+ }
+
+ // Try repacking so fast that we get two new packs which differ only in
+ // content and checksum but have same name, size and lastmodified.
+ // To avoid that JGit detects modification by checking the filekey create
+ // two new packfiles upfront and create copies of them. Then modify the
+ // packfiles in-place by opening them for write and then copying the
+ // content.
+ @Test
+ public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey()
+ throws Exception {
+ int testDataSeed = 1;
+ int testDataLength = 100;
+ FileBasedConfig config = db.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, false);
+ config.save();
+
+ createTestRepo(testDataSeed, testDataLength);
+
+ // Repack to create initial packfile. Make a copy of it
+ PackFile pf = repackAndCheck(5, null, null, null);
+ Path packFilePath = pf.getPackFile().toPath();
+ Path packFileBasePath = packFilePath.resolveSibling(
+ packFilePath.getFileName().toString().replaceAll(".pack", ""));
+ AnyObjectId chk1 = pf.getPackChecksum();
+ String name = pf.getPackName();
+ Long length = Long.valueOf(pf.getPackFile().length());
+ copyPack(packFileBasePath, "", ".copy1");
+
+ // Repack to create second packfile. Make a copy of it
+ AnyObjectId chk2 = repackAndCheck(6, name, length, chk1)
+ .getPackChecksum();
+ copyPack(packFileBasePath, "", ".copy2");
+
+ // Repack to create third packfile
+ AnyObjectId chk3 = repackAndCheck(7, name, length, chk2)
+ .getPackChecksum();
+ long m3 = packFilePath.toFile().lastModified();
+ db.getObjectDatabase().has(unknownID);
+ assertEquals(chk3, getSinglePack(db.getObjectDatabase().getPacks())
+ .getPackChecksum());
+
+ // Wait for a filesystem timer tick to enhance probability the rest of
+ // this test is done before the filesystem timer ticks.
+ fsTick(packFilePath.toFile());
+
+ // 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);
+
+ db.getObjectDatabase().has(unknownID);
+ assertEquals(chk2, getSinglePack(db.getObjectDatabase().getPacks())
+ .getPackChecksum());
+
+ // 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);
+ db.getObjectDatabase().has(unknownID);
+ assertEquals(chk1, getSinglePack(db.getObjectDatabase().getPacks())
+ .getPackChecksum());
+ }
+
+ // Copy file from src to dst but avoid creating a new File (with new
+ // FileKey) if dst already exists
+ private Path copyFile(Path src, Path dst) throws IOException {
+ if (Files.exists(dst)) {
+ dst.toFile().setWritable(true);
+ try (OutputStream dstOut = Files.newOutputStream(dst)) {
+ Files.copy(src, dstOut);
+ return dst;
+ }
+ } else {
+ return Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private Path copyPack(Path base, String srcSuffix, String dstSuffix)
+ throws IOException {
+ copyFile(Paths.get(base + ".idx" + srcSuffix),
+ Paths.get(base + ".idx" + dstSuffix));
+ copyFile(Paths.get(base + ".bitmap" + srcSuffix),
+ Paths.get(base + ".bitmap" + dstSuffix));
+ return copyFile(Paths.get(base + ".pack" + srcSuffix),
+ Paths.get(base + ".pack" + dstSuffix));
+ }
+
+ private PackFile repackAndCheck(int compressionLevel, String oldName,
+ Long oldLength, AnyObjectId oldChkSum)
+ throws IOException, ParseException {
+ PackFile p = getSinglePack(gc(compressionLevel));
+ File pf = p.getPackFile();
+ // The following two assumptions should not cause the test to fail. If
+ // on a certain platform we get packfiles (containing the same git
+ // objects) where the lengths differ or the checksums don't differ we
+ // just skip this test. A reason for that could be that compression
+ // works differently or random number generator works differently. Then
+ // we have to search for more consistent test data or checkin these
+ // packfiles as test resources
+ assumeTrue(oldLength == null || pf.length() == oldLength.longValue());
+ assumeTrue(oldChkSum == null || !p.getPackChecksum().equals(oldChkSum));
+ assertTrue(oldName == null || p.getPackName().equals(oldName));
+ return p;
+ }
+
+ private PackFile getSinglePack(Collection<PackFile> packs) {
+ Iterator<PackFile> pIt = packs.iterator();
+ PackFile p = pIt.next();
+ assertFalse(pIt.hasNext());
+ return p;
}
private Collection<PackFile> gc(int compressionLevel)

Back to the top