aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test/tst/org/eclipse
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.test/tst/org/eclipse')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java18
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java7
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java11
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java19
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java22
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java161
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java49
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java24
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java53
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java29
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java20
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java36
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java552
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java320
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java33
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java12
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackParserTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java72
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java10
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java165
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java279
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java31
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java32
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java83
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java38
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java27
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java166
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java30
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexBuilderTest.java107
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java90
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java345
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java179
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java239
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java71
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java96
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java41
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java16
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java19
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java376
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java3
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java210
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java227
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java37
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilterTest.java122
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java34
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java2
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java19
59 files changed, 4190 insertions, 390 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 1c2e995bbb..226677229c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
- * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2025 Christian Halstrick <christian.halstrick@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -665,11 +665,13 @@ public class AddCommandTest extends RepositoryTestCase {
FileUtils.delete(file);
// is supposed to do nothing
- dc = git.add().addFilepattern("a.txt").call();
+ dc = git.add().addFilepattern("a.txt").setAll(false).call();
assertEquals(oid, dc.getEntry(0).getObjectId());
assertEquals(
"[a.txt, mode:100644, content:content]",
indexState(CONTENT));
+ git.add().addFilepattern("a.txt").call();
+ assertEquals("", indexState(CONTENT));
}
}
@@ -690,11 +692,13 @@ public class AddCommandTest extends RepositoryTestCase {
FileUtils.delete(file);
// is supposed to do nothing
- dc = git.add().addFilepattern("a.txt").call();
+ dc = git.add().addFilepattern("a.txt").setAll(false).call();
assertEquals(oid, dc.getEntry(0).getObjectId());
assertEquals(
"[a.txt, mode:100644, content:content]",
indexState(CONTENT));
+ git.add().addFilepattern("a.txt").call();
+ assertEquals("", indexState(CONTENT));
}
}
@@ -964,7 +968,7 @@ public class AddCommandTest extends RepositoryTestCase {
// file sub/b.txt is deleted
FileUtils.delete(file2);
- git.add().addFilepattern("sub").call();
+ git.add().addFilepattern("sub").setAll(false).call();
// change in sub/a.txt is staged
// deletion of sub/b.txt is not staged
// sub/c.txt is staged
@@ -973,6 +977,12 @@ public class AddCommandTest extends RepositoryTestCase {
"[sub/b.txt, mode:100644, content:content b]" +
"[sub/c.txt, mode:100644, content:content c]",
indexState(CONTENT));
+ git.add().addFilepattern("sub").call();
+ // deletion of sub/b.txt is staged
+ assertEquals(
+ "[sub/a.txt, mode:100644, content:modified content]"
+ + "[sub/c.txt, mode:100644, content:content c]",
+ indexState(CONTENT));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index be3b33a9c5..3f5c5da55a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -34,6 +34,7 @@ import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.ContentMergeStrategy;
@@ -529,10 +530,11 @@ public class CherryPickCommandTest extends RepositoryTestCase {
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
if (reason == null) {
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(reader.getLastEntry().getComment()
.startsWith("cherry-pick: "));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(reader.getLastEntry().getComment()
.startsWith("cherry-pick: "));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index db5b27c2ab..661878fa07 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
@@ -182,7 +182,8 @@ public class CloneCommandTest extends RepositoryTestCase {
private static boolean hasRefLog(Repository repo, Ref ref) {
try {
- return repo.getReflogReader(ref.getName()).getLastEntry() != null;
+ return repo.getRefDatabase().getReflogReader(ref)
+ .getLastEntry() != null;
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
@@ -796,7 +797,7 @@ public class CloneCommandTest extends RepositoryTestCase {
assertNull(git2.getRepository().getConfig().getEnum(
BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, "test",
- ConfigConstants.CONFIG_KEY_REBASE, null));
+ ConfigConstants.CONFIG_KEY_REBASE));
StoredConfig userConfig = SystemReader.getInstance()
.getUserConfig();
@@ -812,7 +813,6 @@ public class CloneCommandTest extends RepositoryTestCase {
addRepoToClose(git2.getRepository());
assertEquals(BranchRebaseMode.REBASE,
git2.getRepository().getConfig().getEnum(
- BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, "test",
ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
@@ -829,7 +829,6 @@ public class CloneCommandTest extends RepositoryTestCase {
addRepoToClose(git2.getRepository());
assertEquals(BranchRebaseMode.REBASE,
git2.getRepository().getConfig().getEnum(
- BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, "test",
ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
index 57e5d4958f..4e5f44e5a6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
@@ -26,6 +26,7 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -69,10 +70,11 @@ public class CommitAndLogCommandTest extends RepositoryTestCase {
l--;
}
assertEquals(l, -1);
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(
reader.getLastEntry().getComment().startsWith("commit:"));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(
reader.getLastEntry().getComment().startsWith("commit:"));
}
@@ -248,10 +250,11 @@ public class CommitAndLogCommandTest extends RepositoryTestCase {
c++;
}
assertEquals(1, c);
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(reader.getLastEntry().getComment()
.startsWith("commit (amend):"));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(reader.getLastEntry().getComment()
.startsWith("commit (amend):"));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index 2cbd223ee8..21cfcc4e34 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -435,10 +435,12 @@ public class CommitCommandTest extends RepositoryTestCase {
assertEquals(1, squashedCommit.getParentCount());
assertNull(db.readSquashCommitMsg());
- assertEquals("commit: Squashed commit of the following:", db
- .getReflogReader(Constants.HEAD).getLastEntry().getComment());
- assertEquals("commit: Squashed commit of the following:", db
- .getReflogReader(db.getBranch()).getLastEntry().getComment());
+ assertEquals("commit: Squashed commit of the following:",
+ db.getRefDatabase().getReflogReader(Constants.HEAD)
+ .getLastEntry().getComment());
+ assertEquals("commit: Squashed commit of the following:",
+ db.getRefDatabase().getReflogReader(db.getFullBranch())
+ .getLastEntry().getComment());
}
}
@@ -455,12 +457,15 @@ public class CommitCommandTest extends RepositoryTestCase {
git.commit().setMessage("c3").setAll(true)
.setReflogComment("testRl").call();
- db.getReflogReader(Constants.HEAD).getReverseEntries();
+ db.getRefDatabase().getReflogReader(Constants.HEAD)
+ .getReverseEntries();
assertEquals("testRl;commit (initial): c1;", reflogComments(
- db.getReflogReader(Constants.HEAD).getReverseEntries()));
+ db.getRefDatabase().getReflogReader(Constants.HEAD)
+ .getReverseEntries()));
assertEquals("testRl;commit (initial): c1;", reflogComments(
- db.getReflogReader(db.getBranch()).getReverseEntries()));
+ db.getRefDatabase().getReflogReader(db.getFullBranch())
+ .getReverseEntries()));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index ab87fa9662..060e6d3e84 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -12,6 +12,7 @@ package org.eclipse.jgit.api;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -87,6 +88,9 @@ public class DescribeCommandTest extends RepositoryTestCase {
assertEquals("alice-t1", describe(c2, "alice*"));
assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
+ assertNotEquals("alice-t1", describeExcluding(c2, "alice*"));
+ assertNotEquals("alice-t1", describeCommand(c2).setMatch("*").setExclude("alice*").call());
+
assertEquals("bob-t2", describe(c3));
assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
@@ -95,6 +99,15 @@ public class DescribeCommandTest extends RepositoryTestCase {
assertEquals("bob-t2", describe(c3, "?ob*"));
assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
+ assertNotEquals("alice-t1-1-g44579eb", describeExcluding(c3, "alice*"));
+ assertNotEquals("alice-t1-1-g44579eb", describeCommand(c3).setMatch("*").setExclude("alice*").call());
+ assertNotEquals("alice-t1-1-g44579eb", describeExcluding(c3, "a??c?-t*"));
+ assertNotEquals("alice-t1-1-g44579eb", describeCommand(c3).setMatch("bob*").setExclude("a??c?-t*").call());
+ assertNotEquals("bob-t2", describeExcluding(c3, "bob*"));
+ assertNotEquals("bob-t2", describeCommand(c3).setMatch("alice*").setExclude("bob*"));
+ assertNotEquals("bob-t2", describeExcluding(c3, "?ob*"));
+ assertNotEquals("bob-t2", describeCommand(c3).setMatch("a??c?-t*").setExclude("?ob*"));
+
// the value verified with git-describe(1)
assertEquals("bob-t2-1-g3e563c5", describe(c4));
assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
@@ -518,6 +531,15 @@ public class DescribeCommandTest extends RepositoryTestCase {
.setMatch(patterns).call();
}
+ private String describeExcluding(ObjectId c1, String... patterns) throws Exception {
+ return git.describe().setTarget(c1).setTags(describeUseAllTags)
+ .setExclude(patterns).call();
+ }
+
+ private DescribeCommand describeCommand(ObjectId c1) throws Exception {
+ return git.describe().setTarget(c1).setTags(describeUseAllTags);
+ }
+
private static void assertNameStartsWith(ObjectId c4, String prefix) {
assertTrue(c4.name(), c4.name().startsWith(prefix));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
index 3ec454cfc3..3731347f11 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
@@ -92,8 +92,8 @@ public class FetchCommandTest extends RepositoryTestCase {
assertTrue(remoteRef.getName().startsWith(Constants.R_REMOTES));
assertEquals(defaultBranchSha1, remoteRef.getObjectId());
- assertNotNull(git.getRepository().getReflogReader(remoteRef.getName())
- .getLastEntry());
+ assertNotNull(git.getRepository().getRefDatabase()
+ .getReflogReader(remoteRef.getName()).getLastEntry());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index 917b6c3297..1ec506798c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -21,6 +21,9 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import java.io.File;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Iterator;
import java.util.regex.Pattern;
@@ -33,6 +36,7 @@ import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.Sets;
@@ -45,6 +49,7 @@ import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.GitDateFormatter;
import org.eclipse.jgit.util.GitDateFormatter.Format;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.theories.DataPoints;
@@ -76,12 +81,12 @@ public class MergeCommandTest extends RepositoryTestCase {
assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
}
// no reflog entry written by merge
- assertEquals("commit (initial): initial commit",
- db
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("commit (initial): initial commit", refDb
.getReflogReader(Constants.HEAD).getLastEntry().getComment());
- assertEquals("commit (initial): initial commit",
- db
- .getReflogReader(db.getBranch()).getLastEntry().getComment());
+ assertEquals("commit (initial): initial commit", refDb
+ .getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
@Test
@@ -96,10 +101,11 @@ public class MergeCommandTest extends RepositoryTestCase {
assertEquals(second, result.getNewHead());
}
// no reflog entry written by merge
- assertEquals("commit: second commit", db
+ assertEquals("commit: second commit", db.getRefDatabase()
.getReflogReader(Constants.HEAD).getLastEntry().getComment());
- assertEquals("commit: second commit", db
- .getReflogReader(db.getBranch()).getLastEntry().getComment());
+ assertEquals("commit: second commit", db.getRefDatabase()
+ .getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
@Test
@@ -117,10 +123,13 @@ public class MergeCommandTest extends RepositoryTestCase {
assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
assertEquals(second, result.getNewHead());
}
+ RefDatabase refDb = db.getRefDatabase();
assertEquals("merge refs/heads/master: Fast-forward",
- db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+ refDb.getReflogReader(Constants.HEAD)
+ .getLastEntry().getComment());
assertEquals("merge refs/heads/master: Fast-forward",
- db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+ refDb.getReflogReader(db.getFullBranch())
+ .getLastEntry().getComment());
}
@Test
@@ -140,10 +149,12 @@ public class MergeCommandTest extends RepositoryTestCase {
result.getMergeStatus());
assertEquals(second, result.getNewHead());
}
- assertEquals("merge refs/heads/master: Fast-forward", db
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("merge refs/heads/master: Fast-forward", refDb
.getReflogReader(Constants.HEAD).getLastEntry().getComment());
- assertEquals("merge refs/heads/master: Fast-forward", db
- .getReflogReader(db.getBranch()).getLastEntry().getComment());
+ assertEquals("merge refs/heads/master: Fast-forward", refDb
+ .getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
@Test
@@ -171,10 +182,12 @@ public class MergeCommandTest extends RepositoryTestCase {
assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
assertEquals(second, result.getNewHead());
}
- assertEquals("merge refs/heads/master: Fast-forward",
- db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
- assertEquals("merge refs/heads/master: Fast-forward",
- db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("merge refs/heads/master: Fast-forward", refDb
+ .getReflogReader(Constants.HEAD).getLastEntry().getComment());
+ assertEquals("merge refs/heads/master: Fast-forward", refDb
+ .getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
@Test
@@ -229,14 +242,17 @@ public class MergeCommandTest extends RepositoryTestCase {
.include(db.exactRef(R_HEADS + MASTER)).call();
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
}
+ RefDatabase refDb = db.getRefDatabase();
assertEquals(
"merge refs/heads/master: Merge made by "
+ mergeStrategy.getName() + ".",
- db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+ refDb.getReflogReader(Constants.HEAD).getLastEntry()
+ .getComment());
assertEquals(
"merge refs/heads/master: Merge made by "
+ mergeStrategy.getName() + ".",
- db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+ refDb.getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
@Theory
@@ -662,14 +678,17 @@ public class MergeCommandTest extends RepositoryTestCase {
.setStrategy(MergeStrategy.RESOLVE).call();
assertEquals(MergeStatus.MERGED, result.getMergeStatus());
assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b")));
- assertEquals("merge " + secondCommit.getId().getName()
- + ": Merge made by resolve.", db
- .getReflogReader(Constants.HEAD)
- .getLastEntry().getComment());
- assertEquals("merge " + secondCommit.getId().getName()
- + ": Merge made by resolve.", db
- .getReflogReader(db.getBranch())
- .getLastEntry().getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(
+ "merge " + secondCommit.getId().getName()
+ + ": Merge made by resolve.",
+ refDb.getReflogReader(Constants.HEAD).getLastEntry()
+ .getComment());
+ assertEquals(
+ "merge " + secondCommit.getId().getName()
+ + ": Merge made by resolve.",
+ refDb.getReflogReader(db.getFullBranch()).getLastEntry()
+ .getComment());
}
}
@@ -2086,6 +2105,94 @@ public class MergeCommandTest extends RepositoryTestCase {
}
}
+ @Test
+ public void testMergeCaseInsensitiveRename() throws Exception {
+ Assume.assumeTrue(
+ "Test makes only sense on a case-insensitive file system",
+ db.isWorkTreeCaseInsensitive());
+ try (Git git = new Git(db)) {
+ writeTrashFile("a", "aaa");
+ git.add().addFilepattern("a").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+ // "Rename" "a" to "A"
+ git.rm().addFilepattern("a").call();
+ writeTrashFile("A", "aaa");
+ git.add().addFilepattern("A").call();
+ RevCommit master = git.commit().setMessage("rename to A").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("b", "bbb");
+ git.add().addFilepattern("b").call();
+ git.commit().setMessage("side").call();
+
+ // Merge master into side
+ MergeResult result = git.merge().include(master)
+ .setStrategy(MergeStrategy.RECURSIVE).call();
+ assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+ assertTrue(new File(db.getWorkTree(), "A").isFile());
+ // Double check
+ boolean found = true;
+ try (DirectoryStream<Path> dir = Files
+ .newDirectoryStream(db.getWorkTree().toPath())) {
+ for (Path p : dir) {
+ found = "A".equals(p.getFileName().toString());
+ if (found) {
+ break;
+ }
+ }
+ }
+ assertTrue(found);
+ }
+ }
+
+ @Test
+ public void testMergeCaseInsensitiveRenameConflict() throws Exception {
+ Assume.assumeTrue(
+ "Test makes only sense on a case-insensitive file system",
+ db.isWorkTreeCaseInsensitive());
+ try (Git git = new Git(db)) {
+ writeTrashFile("a", "aaa");
+ git.add().addFilepattern("a").call();
+ RevCommit initialCommit = git.commit().setMessage("initial").call();
+ // "Rename" "a" to "A" and change it
+ git.rm().addFilepattern("a").call();
+ writeTrashFile("A", "yyy");
+ git.add().addFilepattern("A").call();
+ RevCommit master = git.commit().setMessage("rename to A").call();
+
+ createBranch(initialCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+
+ writeTrashFile("a", "xxx");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("side").call();
+
+ // Merge master into side
+ MergeResult result = git.merge().include(master)
+ .setStrategy(MergeStrategy.RECURSIVE).call();
+ assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+ File a = new File(db.getWorkTree(), "A");
+ assertTrue(a.isFile());
+ // Double check
+ boolean found = true;
+ try (DirectoryStream<Path> dir = Files
+ .newDirectoryStream(db.getWorkTree().toPath())) {
+ for (Path p : dir) {
+ found = "A".equals(p.getFileName().toString());
+ if (found) {
+ break;
+ }
+ }
+ }
+ assertTrue(found);
+ assertEquals(1, result.getConflicts().size());
+ assertTrue(result.getConflicts().containsKey("a"));
+ checkFile(a, "yyy");
+ }
+ }
+
private static void setExecutable(Git git, String path, boolean executable) {
FS.DETECTED.setExecute(
new File(git.getRepository().getWorkTree(), path), executable);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 6d5e45c98f..695681de8d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -480,7 +480,7 @@ public class PullCommandTest extends RepositoryTestCase {
@Test
/** without config it should merge */
public void testPullWithoutConfig() throws Exception {
- Callable<PullResult> setup = target.pull()::call;
+ Callable<PullResult> setup = target.pull();
doTestPullWithRebase(setup, TestPullMode.MERGE);
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 84aa4b58a8..4c8cf06a67 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -57,6 +57,7 @@ import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RebaseTodoLine;
import org.eclipse.jgit.lib.RebaseTodoLine.Action;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.RepositoryState;
@@ -133,11 +134,12 @@ public class RebaseCommandTest extends RepositoryTestCase {
checkFile(file2, "file2");
assertEquals(Status.FAST_FORWARD, res.getStatus());
- List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
.getReverseEntries();
- List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+ List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
.getReverseEntries();
- List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+ List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
.getReverseEntries();
assertEquals("rebase finished: returning to refs/heads/topic", headLog
.get(0).getComment());
@@ -179,11 +181,12 @@ public class RebaseCommandTest extends RepositoryTestCase {
checkFile(file2, "file2 new content");
assertEquals(Status.FAST_FORWARD, res.getStatus());
- List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
.getReverseEntries();
- List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+ List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
.getReverseEntries();
- List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+ List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
.getReverseEntries();
assertEquals("rebase finished: returning to refs/heads/topic", headLog
.get(0).getComment());
@@ -447,13 +450,14 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals(a, rw.next());
}
- List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
.getReverseEntries();
- List<ReflogEntry> sideLog = db.getReflogReader("refs/heads/side")
+ List<ReflogEntry> sideLog = refDb.getReflogReader("refs/heads/side")
.getReverseEntries();
- List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+ List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
.getReverseEntries();
- List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+ List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
.getReverseEntries();
assertEquals("rebase finished: returning to refs/heads/topic", headLog
.get(0).getComment());
@@ -768,9 +772,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
RebaseResult result = git.rebase().setUpstream(parent).call();
assertEquals(Status.UP_TO_DATE, result.getStatus());
- assertEquals(2, db.getReflogReader(Constants.HEAD).getReverseEntries()
- .size());
- assertEquals(2, db.getReflogReader("refs/heads/master")
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(2, refDb.getReflogReader(Constants.HEAD)
+ .getReverseEntries().size());
+ assertEquals(2, refDb.getReflogReader("refs/heads/master")
.getReverseEntries().size());
}
@@ -786,9 +791,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
RebaseResult res = git.rebase().setUpstream(first).call();
assertEquals(Status.UP_TO_DATE, res.getStatus());
- assertEquals(1, db.getReflogReader(Constants.HEAD).getReverseEntries()
- .size());
- assertEquals(1, db.getReflogReader("refs/heads/master")
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(1, refDb.getReflogReader(Constants.HEAD)
+ .getReverseEntries().size());
+ assertEquals(1, refDb.getReflogReader("refs/heads/master")
.getReverseEntries().size());
}
@@ -846,11 +852,12 @@ public class RebaseCommandTest extends RepositoryTestCase {
db.resolve(Constants.HEAD)).getParent(0));
}
assertEquals(origHead, db.readOrigHead());
- List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
.getReverseEntries();
- List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+ List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
.getReverseEntries();
- List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+ List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
.getReverseEntries();
assertEquals(2, masterLog.size());
assertEquals(3, topicLog.size());
@@ -898,8 +905,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
db.resolve(Constants.HEAD)).getParent(0));
}
- List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
- .getReverseEntries();
+ List<ReflogEntry> headLog = db.getRefDatabase()
+ .getReflogReader(Constants.HEAD).getReverseEntries();
assertEquals(8, headLog.size());
assertEquals("rebase: change file1 in topic", headLog.get(0)
.getComment());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
index 534ebd9c61..add5886c2d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
@@ -118,23 +118,21 @@ public class RenameBranchCommandTest extends RepositoryTestCase {
String branch = "b1";
assertEquals(BranchRebaseMode.REBASE,
- config.getEnum(BranchRebaseMode.values(),
- ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
- ConfigConstants.CONFIG_KEY_REBASE,
+ config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION,
+ Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
assertNull(config.getEnum(BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, branch,
- ConfigConstants.CONFIG_KEY_REBASE, null));
+ ConfigConstants.CONFIG_KEY_REBASE));
assertNotNull(git.branchRename().setNewName(branch).call());
config = git.getRepository().getConfig();
assertNull(config.getEnum(BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
- ConfigConstants.CONFIG_KEY_REBASE, null));
+ ConfigConstants.CONFIG_KEY_REBASE));
assertEquals(BranchRebaseMode.REBASE,
- config.getEnum(BranchRebaseMode.values(),
- ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+ config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION, branch,
ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
}
@@ -170,13 +168,12 @@ public class RenameBranchCommandTest extends RepositoryTestCase {
String branch = "b1";
assertEquals(BranchRebaseMode.REBASE,
- config.getEnum(BranchRebaseMode.values(),
- ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
- ConfigConstants.CONFIG_KEY_REBASE,
+ config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION,
+ Constants.MASTER, ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
assertNull(config.getEnum(BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, branch,
- ConfigConstants.CONFIG_KEY_REBASE, null));
+ ConfigConstants.CONFIG_KEY_REBASE));
assertTrue(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
Constants.MASTER, ConfigConstants.CONFIG_KEY_MERGE, true));
assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
@@ -187,10 +184,9 @@ public class RenameBranchCommandTest extends RepositoryTestCase {
config = git.getRepository().getConfig();
assertNull(config.getEnum(BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION, Constants.MASTER,
- ConfigConstants.CONFIG_KEY_REBASE, null));
+ ConfigConstants.CONFIG_KEY_REBASE));
assertEquals(BranchRebaseMode.REBASE,
- config.getEnum(BranchRebaseMode.values(),
- ConfigConstants.CONFIG_BRANCH_SECTION, branch,
+ config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION, branch,
ConfigConstants.CONFIG_KEY_REBASE,
BranchRebaseMode.NONE));
assertFalse(config.getBoolean(ConfigConstants.CONFIG_BRANCH_SECTION,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 8a479a0ca0..99873e1be1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -36,11 +36,13 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Test;
public class ResetCommandTest extends RepositoryTestCase {
@@ -554,46 +556,73 @@ public class ResetCommandTest extends RepositoryTestCase {
assertNull(db.resolve(Constants.HEAD));
}
+ @Test
+ public void testHardResetFileMode() throws Exception {
+ Assume.assumeTrue("Test must be able to set executable bit",
+ db.getFS().supportsExecute());
+ git = new Git(db);
+ File a = writeTrashFile("a.txt", "aaa");
+ File b = writeTrashFile("b.txt", "bbb");
+ db.getFS().setExecute(b, true);
+ assertFalse(db.getFS().canExecute(a));
+ assertTrue(db.getFS().canExecute(b));
+ git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+ RevCommit commit = git.commit().setMessage("files created").call();
+ db.getFS().setExecute(a, true);
+ db.getFS().setExecute(b, false);
+ assertTrue(db.getFS().canExecute(a));
+ assertFalse(db.getFS().canExecute(b));
+ git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+ git.commit().setMessage("change exe bits").call();
+ Ref ref = git.reset().setRef(commit.getName()).setMode(HARD).call();
+ assertSameAsHead(ref);
+ assertEquals(commit.getId(), ref.getObjectId());
+ assertFalse(db.getFS().canExecute(a));
+ assertTrue(db.getFS().canExecute(b));
+ }
+
private void assertReflog(ObjectId prevHead, ObjectId head)
throws IOException {
// Check the reflog for HEAD
- String actualHeadMessage = db.getReflogReader(Constants.HEAD)
+ RefDatabase refDb = db.getRefDatabase();
+ String actualHeadMessage = refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getComment();
String expectedHeadMessage = head.getName() + ": updating HEAD";
assertEquals(expectedHeadMessage, actualHeadMessage);
- assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+ assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getNewId().getName());
- assertEquals(prevHead.getName(), db.getReflogReader(Constants.HEAD)
+ assertEquals(prevHead.getName(), refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getOldId().getName());
// The reflog for master contains the same as the one for HEAD
- String actualMasterMessage = db.getReflogReader("refs/heads/master")
+ String actualMasterMessage = refDb.getReflogReader("refs/heads/master")
.getLastEntry().getComment();
String expectedMasterMessage = head.getName() + ": updating HEAD"; // yes!
assertEquals(expectedMasterMessage, actualMasterMessage);
- assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+ assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getNewId().getName());
- assertEquals(prevHead.getName(), db
- .getReflogReader("refs/heads/master").getLastEntry().getOldId()
- .getName());
+ assertEquals(prevHead.getName(),
+ refDb.getReflogReader("refs/heads/master").getLastEntry()
+ .getOldId().getName());
}
private void assertReflogDisabled(ObjectId head)
throws IOException {
+ RefDatabase refDb = db.getRefDatabase();
// Check the reflog for HEAD
- String actualHeadMessage = db.getReflogReader(Constants.HEAD)
+ String actualHeadMessage = refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getComment();
String expectedHeadMessage = "commit: adding a.txt and dir/b.txt";
assertEquals(expectedHeadMessage, actualHeadMessage);
- assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+ assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getOldId().getName());
// The reflog for master contains the same as the one for HEAD
- String actualMasterMessage = db.getReflogReader("refs/heads/master")
+ String actualMasterMessage = refDb.getReflogReader("refs/heads/master")
.getLastEntry().getComment();
String expectedMasterMessage = "commit: adding a.txt and dir/b.txt";
assertEquals(expectedMasterMessage, actualMasterMessage);
- assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+ assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
.getLastEntry().getOldId().getName());
}
/**
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
index afd6708d21..89fdb32220 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
@@ -29,6 +29,7 @@ import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@@ -87,10 +88,11 @@ public class RevertCommandTest extends RepositoryTestCase {
assertEquals("create a", history.next().getFullMessage());
assertFalse(history.hasNext());
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
}
@@ -170,10 +172,11 @@ public class RevertCommandTest extends RepositoryTestCase {
assertEquals("add first", history.next().getFullMessage());
assertFalse(history.hasNext());
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
}
@@ -223,10 +226,11 @@ public class RevertCommandTest extends RepositoryTestCase {
assertEquals("add first", history.next().getFullMessage());
assertFalse(history.hasNext());
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
- reader = db.getReflogReader(db.getBranch());
+ reader = refDb.getReflogReader(db.getFullBranch());
assertTrue(reader.getLastEntry().getComment()
.startsWith("revert: Revert \""));
}
@@ -431,12 +435,13 @@ public class RevertCommandTest extends RepositoryTestCase {
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
if (reason == null) {
- ReflogReader reader = db.getReflogReader(Constants.HEAD);
- assertTrue(reader.getLastEntry().getComment()
- .startsWith("revert: "));
- reader = db.getReflogReader(db.getBranch());
- assertTrue(reader.getLastEntry().getComment()
- .startsWith("revert: "));
+ RefDatabase refDb = db.getRefDatabase();
+ ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
+ assertTrue(
+ reader.getLastEntry().getComment().startsWith("revert: "));
+ reader = refDb.getReflogReader(db.getFullBranch());
+ assertTrue(
+ reader.getLastEntry().getComment().startsWith("revert: "));
}
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
index 5d0ab05174..18cd21a5d7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
@@ -409,8 +409,8 @@ public class StashCreateCommandTest extends RepositoryTestCase {
assertEquals("content", read(committedFile));
validateStashedCommit(stashed);
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
ReflogEntry entry = reader.getLastEntry();
assertNotNull(entry);
assertEquals(ObjectId.zeroId(), entry.getOldId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
index c81731d746..d937579283 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
@@ -92,8 +92,8 @@ public class StashDropCommandTest extends RepositoryTestCase {
stashRef = git.getRepository().exactRef(Constants.R_STASH);
assertNull(stashRef);
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
assertNull(reader);
}
@@ -120,8 +120,8 @@ public class StashDropCommandTest extends RepositoryTestCase {
assertNull(git.stashDrop().setAll(true).call());
assertNull(git.getRepository().exactRef(Constants.R_STASH));
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
assertNull(reader);
}
@@ -150,8 +150,8 @@ public class StashDropCommandTest extends RepositoryTestCase {
assertNotNull(stashRef);
assertEquals(firstStash, stashRef.getObjectId());
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
List<ReflogEntry> entries = reader.getReverseEntries();
assertEquals(1, entries.size());
assertEquals(ObjectId.zeroId(), entries.get(0).getOldId());
@@ -192,8 +192,8 @@ public class StashDropCommandTest extends RepositoryTestCase {
assertNotNull(stashRef);
assertEquals(thirdStash, stashRef.getObjectId());
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
List<ReflogEntry> entries = reader.getReverseEntries();
assertEquals(2, entries.size());
assertEquals(ObjectId.zeroId(), entries.get(1).getOldId());
@@ -250,8 +250,8 @@ public class StashDropCommandTest extends RepositoryTestCase {
assertNotNull(stashRef);
assertEquals(thirdStash, stashRef.getObjectId());
- ReflogReader reader = git.getRepository().getReflogReader(
- Constants.R_STASH);
+ ReflogReader reader = git.getRepository().getRefDatabase()
+ .getReflogReader(Constants.R_STASH);
List<ReflogEntry> entries = reader.getReverseEntries();
assertEquals(2, entries.size());
assertEquals(ObjectId.zeroId(), entries.get(1).getOldId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
index f47f447375..c2c06b2477 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
@@ -23,20 +23,22 @@ import org.junit.Test;
/** Unit tests of {@link BlameGenerator}. */
public class BlameGeneratorTest extends RepositoryTestCase {
+ private static final String FILE = "file.txt";
+
@Test
public void testBoundLineDelete() throws Exception {
try (Git git = new Git(db)) {
String[] content1 = new String[] { "first", "second" };
- writeTrashFile("file.txt", join(content1));
- git.add().addFilepattern("file.txt").call();
+ writeTrashFile(FILE, join(content1));
+ git.add().addFilepattern(FILE).call();
RevCommit c1 = git.commit().setMessage("create file").call();
String[] content2 = new String[] { "third", "first", "second" };
- writeTrashFile("file.txt", join(content2));
- git.add().addFilepattern("file.txt").call();
+ writeTrashFile(FILE, join(content2));
+ git.add().addFilepattern(FILE).call();
RevCommit c2 = git.commit().setMessage("create file").call();
- try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
+ try (BlameGenerator generator = new BlameGenerator(db, FILE)) {
generator.push(null, db.resolve(Constants.HEAD));
assertEquals(3, generator.getResultContents().size());
@@ -47,7 +49,7 @@ public class BlameGeneratorTest extends RepositoryTestCase {
assertEquals(1, generator.getResultEnd());
assertEquals(0, generator.getSourceStart());
assertEquals(1, generator.getSourceEnd());
- assertEquals("file.txt", generator.getSourcePath());
+ assertEquals(FILE, generator.getSourcePath());
assertTrue(generator.next());
assertEquals(c1, generator.getSourceCommit());
@@ -56,7 +58,7 @@ public class BlameGeneratorTest extends RepositoryTestCase {
assertEquals(3, generator.getResultEnd());
assertEquals(0, generator.getSourceStart());
assertEquals(2, generator.getSourceEnd());
- assertEquals("file.txt", generator.getSourcePath());
+ assertEquals(FILE, generator.getSourcePath());
assertFalse(generator.next());
}
@@ -87,7 +89,8 @@ public class BlameGeneratorTest extends RepositoryTestCase {
git.add().addFilepattern(FILENAME_2).call();
RevCommit c2 = git.commit().setMessage("change file2").call();
- try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+ try (BlameGenerator generator = new BlameGenerator(db,
+ FILENAME_2)) {
generator.push(null, db.resolve(Constants.HEAD));
assertEquals(3, generator.getResultContents().size());
@@ -113,7 +116,8 @@ public class BlameGeneratorTest extends RepositoryTestCase {
}
// and test again with other BlameGenerator API:
- try (BlameGenerator generator = new BlameGenerator(db, FILENAME_2)) {
+ try (BlameGenerator generator = new BlameGenerator(db,
+ FILENAME_2)) {
generator.push(null, db.resolve(Constants.HEAD));
BlameResult result = generator.computeBlameResult();
@@ -136,21 +140,21 @@ public class BlameGeneratorTest extends RepositoryTestCase {
try (Git git = new Git(db)) {
String[] content1 = new String[] { "first", "second", "third" };
- writeTrashFile("file.txt", join(content1));
- git.add().addFilepattern("file.txt").call();
+ writeTrashFile(FILE, join(content1));
+ git.add().addFilepattern(FILE).call();
git.commit().setMessage("create file").call();
String[] content2 = new String[] { "" };
- writeTrashFile("file.txt", join(content2));
- git.add().addFilepattern("file.txt").call();
+ writeTrashFile(FILE, join(content2));
+ git.add().addFilepattern(FILE).call();
git.commit().setMessage("create file").call();
- writeTrashFile("file.txt", join(content1));
- git.add().addFilepattern("file.txt").call();
+ writeTrashFile(FILE, join(content1));
+ git.add().addFilepattern(FILE).call();
RevCommit c3 = git.commit().setMessage("create file").call();
- try (BlameGenerator generator = new BlameGenerator(db, "file.txt")) {
+ try (BlameGenerator generator = new BlameGenerator(db, FILE)) {
generator.push(null, db.resolve(Constants.HEAD));
assertEquals(3, generator.getResultContents().size());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java
new file mode 100644
index 0000000000..3e4ac1f993
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import static java.lang.String.join;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.blame.cache.BlameCache;
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class BlameGeneratorCacheTest extends RepositoryTestCase {
+ private static final String FILE = "file.txt";
+
+ /**
+ * Simple history:
+ *
+ * <pre>
+ * C1 C2 C3 C4 C4 blame
+ * lines ----------------------------------
+ * L1 | C1 C1 C1 C1 C1
+ * L2 | C1 C1 *C3 *C4 C4
+ * L3 | C1 C1 *C3 C3 C3
+ * L4 | *C2 C2 *C4 C4
+ * </pre>
+ *
+ * @throws Exception
+ * any error
+ */
+ @Test
+ public void blame_simple_correctRegions() throws Exception {
+ RevCommit c1, c2, c3, c4;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+ c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+ c3 = commit(r, lines("L1C1", "L2C3", "L3C3", "L4C2"), c2);
+ c4 = commit(r, lines("L1C1", "L2C4", "L3C3", "L4C4"), c3);
+ }
+
+ List<EmittedRegion> expectedRegions = Arrays.asList(
+ new EmittedRegion(c1, 0, 1),
+ new EmittedRegion(c4, 1, 2),
+ new EmittedRegion(c3, 2, 3),
+ new EmittedRegion(c4, 3, 4));
+
+ assertRegions(c4, null, expectedRegions, 4);
+ assertRegions(c4, emptyCache(), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c4), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c3), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c2), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c1), expectedRegions, 4);
+ }
+
+ @Test
+ public void blame_simple_cacheUsage() throws Exception {
+ RevCommit c1, c2, c3, c4;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+ c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+ c3 = commit(r, lines("L1C1", "L2C3", "L3C3", "L4C2"), c2);
+ c4 = commit(r, lines("L1C1", "L2C4", "L3C3", "L4C4"), c3);
+ }
+
+ assertCacheUsage(c4, null, false, 4);
+ assertCacheUsage(c4, emptyCache(), false, 4);
+ assertCacheUsage(c4, blameAndCache(c4), true, 1);
+ assertCacheUsage(c4, blameAndCache(c3), true, 2);
+ assertCacheUsage(c4, blameAndCache(c2), true, 3);
+ // Cache not needed because c1 doesn't have parents
+ assertCacheUsage(c4, blameAndCache(c1), false, 4);
+ }
+
+ @Test
+ public void blame_simple_createdMidHistory_correctRegions() throws Exception {
+ String c1Content = lines("L1C1", "L2C1", "L3C1");
+ String c2Content = lines("L1C1", "L2C1", "L3C1", "L4C2");
+ String c3Content = lines("L1C1", "L2C3", "L3C3", "L4C2");
+ String c4Content = lines("L1C1", "L2C4", "L3C3", "L4C4");
+
+ RevCommit c0, c1, c2, c3, c4;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c0 = r.commit().add("otherfile", "content").create();
+ c1 = r.commit().parent(c0).add(FILE, c1Content).create();
+ c2 = r.commit().parent(c1).add(FILE, c2Content).create();
+ c3 = r.commit().parent(c2).add(FILE, c3Content).create();
+ c4 = r.commit().parent(c3).add(FILE, c4Content).create();
+ }
+
+ List<EmittedRegion> expectedRegions = Arrays.asList(
+ new EmittedRegion(c1, 0, 1),
+ new EmittedRegion(c4, 1, 2),
+ new EmittedRegion(c3, 2, 3),
+ new EmittedRegion(c4, 3, 4));
+
+ assertRegions(c4, null, expectedRegions, 4);
+ assertRegions(c4, emptyCache(), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c4, FILE), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c3, FILE), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c2, FILE), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c1, FILE), expectedRegions, 4);
+ assertRegions(c4, blameAndCache(c0, FILE), expectedRegions, 4);
+ }
+
+ @Test
+ public void blame_simple_createdMidHistory_cacheUsage() throws Exception {
+ String c1Content = lines("L1C1", "L2C1", "L3C1");
+ String c2Content = lines("L1C1", "L2C1", "L3C1", "L4C2");
+ String c3Content = lines("L1C1", "L2C3", "L3C3", "L4C2");
+ String c4Content = lines("L1C1", "L2C4", "L3C3", "L4C4");
+
+ RevCommit c0, c1, c2, c3, c4;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c0 = r.commit().add("otherfile", "content").create();
+ c1 = r.commit().parent(c0).add(FILE, c1Content).create();
+ c2 = r.commit().parent(c1).add(FILE, c2Content).create();
+ c3 = r.commit().parent(c2).add(FILE, c3Content).create();
+ c4 = r.commit().parent(c3).add(FILE, c4Content).create();
+ }
+
+ assertCacheUsage(c4, null, false, 4);
+ assertCacheUsage(c4, emptyCache(), false, 4);
+ assertCacheUsage(c4, blameAndCache(c4, FILE), true, 1);
+ assertCacheUsage(c4, blameAndCache(c3, FILE), true, 2);
+ assertCacheUsage(c4, blameAndCache(c2, FILE), true, 3);
+ // Cache not needed because c1 created the file
+ assertCacheUsage(c4, blameAndCache(c1, FILE), false, 4);
+ }
+
+ /**
+ * Overwrite:
+ *
+ * <pre>
+ * C1 C2 C3 C3 blame
+ * lines ----------------------------------
+ * L1 | C1 C1 *C3 C3
+ * L2 | C1 C1 *C3 C3
+ * L3 | C1 C1 *C3 C3
+ * L4 | *C2
+ * </pre>
+ *
+ * @throws Exception
+ * any error
+ */
+ @Test
+ public void blame_overwrite_correctRegions() throws Exception {
+ RevCommit c1, c2, c3;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+ c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+ c3 = commit(r, lines("L1C3", "L2C3", "L3C3"), c2);
+ }
+
+ List<EmittedRegion> expectedRegions = Arrays.asList(
+ new EmittedRegion(c3, 0, 3));
+
+ assertRegions(c3, null, expectedRegions, 3);
+ assertRegions(c3, emptyCache(), expectedRegions, 3);
+ assertRegions(c3, blameAndCache(c3), expectedRegions, 3);
+ assertRegions(c3, blameAndCache(c2), expectedRegions, 3);
+ assertRegions(c3, blameAndCache(c1), expectedRegions, 3);
+ }
+
+ @Test
+ public void blame_overwrite_cacheUsage() throws Exception {
+ RevCommit c1, c2, c3;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+ c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+ c3 = commit(r, lines("L1C3", "L2C3", "L3C3"), c2);
+ }
+
+ assertCacheUsage(c3, null, false, 1);
+ assertCacheUsage(c3, emptyCache(), false, 1);
+ assertCacheUsage(c3, blameAndCache(c3), true, 1);
+ assertCacheUsage(c3, blameAndCache(c2), false, 1);
+ assertCacheUsage(c3, blameAndCache(c1), false, 1);
+ }
+
+ /**
+ * Merge:
+ *
+ * <pre>
+ * root
+ * ----
+ * L1 -
+ * L2 -
+ * L3 -
+ * / \
+ * sideA sideB
+ * ----- -----
+ * *L1 a L1 -
+ * *L2 a L2 -
+ * *L3 a L3 -
+ * *L4 a *L4 b
+ * L5 - *L5 b
+ * L6 - *L6 b
+ * L7 - *L7 b
+ * \ /
+ * merge
+ * -----
+ * L1-L4 a (from sideA)
+ * L5-L7 - (common, from root)
+ * L8-L11 b (from sideB)
+ * </pre>
+ *
+ * @throws Exception
+ * any error
+ */
+ @Test
+ public void blame_merge_correctRegions() throws Exception {
+ RevCommit root, sideA, sideB, mergedTip;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ root = commitAsLines(r, "---");
+ sideA = commitAsLines(r, "aaaa---", root);
+ sideB = commitAsLines(r, "---bbbb", root);
+ mergedTip = commitAsLines(r, "aaaa---bbbb", sideA, sideB);
+ }
+
+ List<EmittedRegion> expectedRegions = Arrays.asList(
+ new EmittedRegion(sideA, 0, 4),
+ new EmittedRegion(root, 4, 7),
+ new EmittedRegion(sideB, 7, 11));
+
+ assertRegions(mergedTip, null, expectedRegions, 11);
+ assertRegions(mergedTip, emptyCache(), expectedRegions, 11);
+ assertRegions(mergedTip, blameAndCache(root), expectedRegions, 11);
+ assertRegions(mergedTip, blameAndCache(sideA), expectedRegions, 11);
+ assertRegions(mergedTip, blameAndCache(sideB), expectedRegions, 11);
+ assertRegions(mergedTip, blameAndCache(mergedTip), expectedRegions, 11);
+ }
+
+ @Test
+ public void blame_merge_cacheUsage() throws Exception {
+ RevCommit root, sideA, sideB, mergedTip;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ root = commitAsLines(r, "---");
+ sideA = commitAsLines(r, "aaaa---", root);
+ sideB = commitAsLines(r, "---bbbb", root);
+ mergedTip = commitAsLines(r, "aaaa---bbbb", sideA, sideB);
+ }
+
+ assertCacheUsage(mergedTip, null, /* cacheUsed */ false,
+ /* candidates */ 4);
+ assertCacheUsage(mergedTip, emptyCache(), false, 4);
+
+ // While splitting unblamed regions to parents, sideA comes first
+ // and gets "aaaa----". Processing is by commit time, so sideB is
+ // explored first
+ assertCacheUsage(mergedTip, blameAndCache(sideA), true, 3);
+ assertCacheUsage(mergedTip, blameAndCache(sideB), true, 4);
+ assertCacheUsage(mergedTip, blameAndCache(root), false, 4);
+ }
+
+ /**
+ * Moving block (insertion)
+ *
+ * <pre>
+ * C1 C2 C3 C3 blame
+ * lines ----------------------------------
+ * L1 | C1 C1 C1 C1
+ * L2 | C1 *C2 C2 C2
+ * L3 | C1 *C3 C3
+ * L4 | C1 C1
+ * </pre>
+ *
+ * @throws Exception
+ * any error
+ */
+ @Test
+ public void blame_movingBlock_correctRegions() throws Exception {
+ RevCommit c1, c2, c3;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commit(r, lines("L1C1", "L2C1"));
+ c2 = commit(r, lines("L1C1", "middle", "L2C1"), c1);
+ c3 = commit(r, lines("L1C1", "middle", "extra", "L2C1"), c2);
+ }
+
+ List<EmittedRegion> expectedRegions = Arrays.asList(
+ new EmittedRegion(c1, 0, 1),
+ new EmittedRegion(c2, 1, 2),
+ new EmittedRegion(c3, 2, 3),
+ new EmittedRegion(c1, 3, 4));
+
+ assertRegions(c3, null, expectedRegions, 4);
+ assertRegions(c3, emptyCache(), expectedRegions, 4);
+ assertRegions(c3, blameAndCache(c3), expectedRegions, 4);
+ assertRegions(c3, blameAndCache(c2), expectedRegions, 4);
+ assertRegions(c3, blameAndCache(c1), expectedRegions, 4);
+ }
+
+ @Test
+ public void blame_movingBlock_cacheUsage() throws Exception {
+ RevCommit c1, c2, c3;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = commitAsLines(r, "root---");
+ c2 = commitAsLines(r, "rootXXX---", c1);
+ c3 = commitAsLines(r, "rootYYYXXX---", c2);
+ }
+
+ assertCacheUsage(c3, null, false, 3);
+ assertCacheUsage(c3, emptyCache(), false, 3);
+ assertCacheUsage(c3, blameAndCache(c3), true, 1);
+ assertCacheUsage(c3, blameAndCache(c2), true, 2);
+ assertCacheUsage(c3, blameAndCache(c1), false, 3);
+ }
+
+ @Test
+ public void blame_cacheOnlyOnChange_unmodifiedInSomeCommits_cacheUsage() throws Exception {
+ String README = "README";
+ String fileC1Content = lines("L1C1", "L2C1", "L3C1");
+ String fileC2Content = lines("L1C1", "L2C1", "L3C1", "L4C2");
+ String fileC3Content = lines("L1C1", "L2C3", "L3C3", "L4C2");
+ String fileC4Content = lines("L1C1", "L2C4", "L3C3", "L4C4");
+
+ RevCommit c1, c2, c3, c4, ni;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = r.commit().add(FILE, fileC1Content).create();
+ c2 = r.commit().parent(c1).add(FILE, fileC2Content).create();
+ // Keep FILE and edit 100 times README
+ ni = c2;
+ for (int i = 0; i < 100; i++) {
+ ni = r.commit().parent(ni).add(README, "whatever " + i).create();
+ }
+ c3 = r.commit().parent(ni).add(FILE, fileC3Content).create();
+ c4 = r.commit().parent(c3).add(FILE, fileC4Content).create();
+ r.branch("refs/heads/master").update(c4);
+ }
+
+ InMemoryBlameCache empty = emptyCache();
+ assertCacheUsage(c4, empty, false, 104);
+ assertEquals(3, empty.callCount);
+
+ InMemoryBlameCache c4Cached = blameAndCache(c4, FILE);
+ assertCacheUsage(c4, c4Cached, true, 1);
+ assertEquals(1, c4Cached.callCount);
+
+ InMemoryBlameCache c3Cached = blameAndCache(c3, FILE);
+ assertCacheUsage(c4, c3Cached, true, 2);
+ assertEquals(2, c3Cached.callCount);
+
+ // This commit doesn't touch the file, shouldn't check the cache
+ InMemoryBlameCache niCached = blameAndCache(ni, FILE);
+ assertCacheUsage(c4, niCached, false, 104);
+ assertEquals(3, niCached.callCount);
+
+ InMemoryBlameCache c2Cached = blameAndCache(c2, FILE);
+ assertCacheUsage(c4, c2Cached, true, 103);
+ assertEquals(3, c2Cached.callCount);
+
+ // No parents, c1 doesn't need cache.
+ InMemoryBlameCache c1Cached = blameAndCache(c1, FILE);
+ assertCacheUsage(c4, c1Cached, false, 104);
+ assertEquals(3, c1Cached.callCount);
+ }
+
+ @Test
+ public void blame_cacheOnlyOnChange_renameWithoutChange_cacheUsage() throws Exception {
+ String OTHER = "other.txt";
+ String c1Content = lines("L1C1", "L2C1", "L3C1");
+ String c2Content = lines("L1C1", "L2C1", "L3C1", "L4C2");
+
+ RevCommit c1, c2, c3;
+ try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+ c1 = r.commit().add(OTHER, c1Content).create();
+ c2 = r.commit().parent(c1).add(OTHER, c2Content).create();
+ c3 = r.commit().parent(c2).rm(OTHER).add(FILE, c2Content).create();
+ r.branch("refs/heads/master").update(c3);
+ }
+
+ assertCacheUsage(c3, null, false, 3);
+ assertCacheUsage(c3, emptyCache(), false, 3);
+ assertCacheUsage(c3, blameAndCache(c3, FILE), true, 1);
+ assertCacheUsage(c3, blameAndCache(c2, OTHER), true, 2);
+ assertCacheUsage(c3, blameAndCache(c1, OTHER), false, 3);
+ }
+
+ private void assertRegions(RevCommit commit, InMemoryBlameCache cache,
+ List<EmittedRegion> expectedRegions, int resultLineCount)
+ throws IOException {
+ try (BlameGenerator gen = new BlameGenerator(db, FILE, cache)) {
+ gen.push(null, db.parseCommit(commit));
+ List<EmittedRegion> regions = consume(gen);
+ assertRegionsEquals(expectedRegions, regions);
+ assertAllLinesCovered(/* lines= */ resultLineCount, regions);
+ }
+ }
+
+ private void assertCacheUsage(RevCommit commit, InMemoryBlameCache cache,
+ boolean cacheHit, int candidatesVisited) throws IOException {
+ try (BlameGenerator gen = new BlameGenerator(db, FILE, cache)) {
+ gen.push(null, db.parseCommit(commit));
+ consume(gen);
+ assertEquals(cacheHit, gen.getStats().isCacheHit());
+ assertEquals(candidatesVisited,
+ gen.getStats().getCandidatesVisited());
+ }
+ }
+
+ private static void assertAllLinesCovered(int lines,
+ List<EmittedRegion> regions) {
+ Collections.sort(regions);
+ assertEquals("Starts in first line", 0, regions.get(0).resultStart());
+ for (int i = 1; i < regions.size(); i++) {
+ assertEquals("No gaps", regions.get(i).resultStart(),
+ regions.get(i - 1).resultEnd());
+ }
+ assertEquals("Ends in last line", lines,
+ regions.get(regions.size() - 1).resultEnd());
+ }
+
+ private static void assertRegionsEquals(List<EmittedRegion> expected,
+ List<EmittedRegion> actual) {
+ assertEquals(expected.size(), actual.size());
+ Collections.sort(actual);
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(String.format("List differ in element %d", i),
+ expected.get(i), actual.get(i));
+ }
+ }
+
+ private static InMemoryBlameCache emptyCache() {
+ return new InMemoryBlameCache("<empty>");
+ }
+
+ private List<EmittedRegion> consume(BlameGenerator generator)
+ throws IOException {
+ List<EmittedRegion> result = new ArrayList<>();
+ while (generator.next()) {
+ EmittedRegion genRegion = new EmittedRegion(
+ generator.getSourceCommit().toObjectId(),
+ generator.getResultStart(), generator.getResultEnd());
+ result.add(genRegion);
+ }
+ return result;
+ }
+
+ private InMemoryBlameCache blameAndCache(RevCommit commit)
+ throws IOException {
+ return blameAndCache(commit, FILE);
+ }
+
+ private InMemoryBlameCache blameAndCache(RevCommit commit, String path)
+ throws IOException {
+ List<CacheRegion> regions;
+ try (BlameGenerator generator = new BlameGenerator(db, path)) {
+ generator.push(null, commit);
+ regions = consume(generator).stream()
+ .map(EmittedRegion::asCacheRegion)
+ .toList();
+ }
+ InMemoryBlameCache cache = new InMemoryBlameCache("<x>");
+ cache.put(commit, path, regions);
+ return cache;
+ }
+
+ private static RevCommit commitAsLines(TestRepository<?> r,
+ String charPerLine, RevCommit... parents) throws Exception {
+ return commit(r, charPerLine.replaceAll("\\S", "$0\n"), parents);
+ }
+
+ private static RevCommit commit(TestRepository<?> r, String contents,
+ RevCommit... parents) throws Exception {
+ return commit(r, Map.of(FILE, contents), parents);
+ }
+
+ private static RevCommit commit(TestRepository<?> r,
+ Map<String, String> fileContents, RevCommit... parents)
+ throws Exception {
+ TestRepository<?>.CommitBuilder builder = r.commit();
+ for (RevCommit commit : parents) {
+ builder.parent(commit);
+ }
+ fileContents.forEach((path, content) -> {
+ try {
+ builder.add(path, content);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return builder.create();
+ }
+
+ private static String lines(String... l) {
+ return join("\n", l);
+ }
+
+ private record EmittedRegion(ObjectId oid, int resultStart, int resultEnd)
+ implements Comparable<EmittedRegion> {
+ @Override
+ public int compareTo(EmittedRegion o) {
+ return resultStart - o.resultStart;
+ }
+
+ CacheRegion asCacheRegion() {
+ return new CacheRegion(FILE, oid, resultStart, resultEnd);
+ }
+ }
+
+ private static class InMemoryBlameCache implements BlameCache {
+
+ private final Map<Key, List<CacheRegion>> cache = new HashMap<>();
+
+ private final String description;
+
+ private int callCount;
+
+ public InMemoryBlameCache(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public List<CacheRegion> get(Repository repo, ObjectId commitId,
+ String path) throws IOException {
+ callCount++;
+ return cache.get(new Key(commitId.name(), path));
+ }
+
+ public void put(ObjectId commitId, String path,
+ List<CacheRegion> cachedRegions) {
+ cache.put(new Key(commitId.name(), path), cachedRegions);
+ }
+
+ @Override
+ public String toString() {
+ return "InMemoryCache: " + description;
+ }
+
+ record Key(String commitId, String path) {
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java
new file mode 100644
index 0000000000..1b28676fbf
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class BlameRegionMergerTest extends RepositoryTestCase {
+
+ private static final ObjectId O1 = ObjectId
+ .fromString("ff6dd8db6edc9aa0ac58fea1d14a55be46c3eb14");
+
+ private static final ObjectId O2 = ObjectId
+ .fromString("c3c7f680c6bee238617f25f6aa85d0b565fc8ecb");
+
+ private static final ObjectId O3 = ObjectId
+ .fromString("29e014aad0399fe8ede7c101d01b6e440ac9966b");
+
+ List<RevCommit> fakeCommits = List.of(new FakeRevCommit(O1),
+ new FakeRevCommit(O2), new FakeRevCommit(O3));
+
+ // In reverse order, so the code doesn't assume a sorted list
+ List<CacheRegion> cachedRegions = List.of(
+ new CacheRegion("README", O3, 20, 30),
+ new CacheRegion("README", O2, 10, 20),
+ new CacheRegion("README", O1, 0, 10));
+
+ BlameRegionMerger blamer = new BlameRegionMergerFakeCommits(fakeCommits,
+ cachedRegions);
+
+ @Test
+ public void intersectRegions_allInside() {
+ Region unblamed = new Region(15, 18, 10);
+ CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+ Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+ // Same lines in result and source
+ assertEquals(15, result.resultStart);
+ assertEquals(18, result.sourceStart);
+ assertEquals(10, result.length);
+ assertNull(result.next);
+ }
+
+ @Test
+ public void intersectRegions_startsBefore() {
+ // Intesecting [4, 14) with [10, 90)
+ Region unblamed = new Region(30, 4, 10);
+ CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+ Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+ // The unblamed region starting at 4 (sourceStart), starts at 30 in the
+ // original file (resultStart). e.g. some commit introduced
+ // lines. If we take the second portion of the region, we need to move
+ // the result start accordingly.
+ assertEquals(36, result.resultStart);
+ assertEquals(10, result.sourceStart);
+ assertEquals(4, result.length);
+ assertNull(result.next);
+ }
+
+ @Test
+ public void intersectRegions_endsAfter() {
+ // Intesecting [85, 95) with [10, 90)
+ Region unblamed = new Region(30, 85, 10);
+ CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+ Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+ assertEquals(30, result.resultStart);
+ assertEquals(85, result.sourceStart);
+ assertEquals(5, result.length);
+ assertNull(result.next);
+ }
+
+ @Test
+ public void intersectRegions_spillOverBothSides() {
+ // Intesecting [5, 100) with [10, 90)
+ Region unblamed = new Region(30, 5, 95);
+ CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+ Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+ assertEquals(35, result.resultStart);
+ assertEquals(10, result.sourceStart);
+ assertEquals(80, result.length);
+ assertNull(result.next);
+ }
+
+ @Test
+ public void intersectRegions_exactMatch() {
+ // Intesecting [5, 100) with [10, 90)
+ Region unblamed = new Region(30, 10, 80);
+ CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+ Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+ assertEquals(30, result.resultStart);
+ assertEquals(10, result.sourceStart);
+ assertEquals(80, result.length);
+ assertNull(result.next);
+ }
+
+ @Test
+ public void findOverlaps_allInside() {
+ Region unblamed = new Region(0, 11, 4);
+ List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+ assertEquals(1, overlaps.size());
+ assertEquals(10, overlaps.get(0).getStart());
+ assertEquals(20, overlaps.get(0).getEnd());
+ }
+
+ @Test
+ public void findOverlaps_overTwoRegions() {
+ Region unblamed = new Region(0, 8, 4);
+ List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+ assertEquals(2, overlaps.size());
+ assertEquals(0, overlaps.get(0).getStart());
+ assertEquals(10, overlaps.get(0).getEnd());
+ assertEquals(10, overlaps.get(1).getStart());
+ assertEquals(20, overlaps.get(1).getEnd());
+ }
+
+ @Test
+ public void findOverlaps_overThreeRegions() {
+ Region unblamed = new Region(0, 8, 15);
+ List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+ assertEquals(3, overlaps.size());
+ assertEquals(0, overlaps.get(0).getStart());
+ assertEquals(10, overlaps.get(0).getEnd());
+ assertEquals(10, overlaps.get(1).getStart());
+ assertEquals(20, overlaps.get(1).getEnd());
+ assertEquals(20, overlaps.get(2).getStart());
+ assertEquals(30, overlaps.get(2).getEnd());
+ }
+
+ @Test
+ public void blame_exactOverlap() throws IOException {
+ Region unblamed = new Region(0, 10, 10);
+ List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+
+ assertEquals(1, blamed.size());
+ Candidate c = blamed.get(0);
+ assertEquals(c.sourceCommit.name(), O2.name());
+ assertEquals(c.regionList.resultStart, unblamed.resultStart);
+ assertEquals(c.regionList.sourceStart, unblamed.sourceStart);
+ assertEquals(10, c.regionList.length);
+ assertNull(c.regionList.next);
+ }
+
+ @Test
+ public void blame_corruptedIndex() {
+ Region outOfRange = new Region(0, 43, 4);
+ // This region is out of the blamed area
+ assertThrows(IOException.class,
+ () -> blamer.mergeOneRegion(outOfRange));
+ }
+
+ @Test
+ public void blame_allInsideOneBlamedRegion() throws IOException {
+ Region unblamed = new Region(0, 5, 3);
+ // This region if fully blamed to O1
+ List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+ assertEquals(1, blamed.size());
+ Candidate c = blamed.get(0);
+ assertEquals(c.sourceCommit.name(), O1.name());
+ assertEquals(c.regionList.resultStart, unblamed.resultStart);
+ assertEquals(c.regionList.sourceStart, unblamed.sourceStart);
+ assertEquals(3, c.regionList.length);
+ assertNull(c.regionList.next);
+ }
+
+ @Test
+ public void blame_overTwoBlamedRegions() throws IOException {
+ Region unblamed = new Region(0, 8, 5);
+ // (8, 10) belongs go C1, (10, 13) to C2
+ List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+ assertEquals(2, blamed.size());
+ Candidate c = blamed.get(0);
+ assertEquals(c.sourceCommit.name(), O1.name());
+ assertEquals(unblamed.resultStart, c.regionList.resultStart);
+ assertEquals(unblamed.sourceStart, c.regionList.sourceStart);
+ assertEquals(2, c.regionList.length);
+ assertNull(c.regionList.next);
+
+ c = blamed.get(1);
+ assertEquals(c.sourceCommit.name(), O2.name());
+ assertEquals(2, c.regionList.resultStart);
+ assertEquals(10, c.regionList.sourceStart);
+ assertEquals(3, c.regionList.length);
+ assertNull(c.regionList.next);
+ }
+
+ @Test
+ public void blame_all() throws IOException {
+ Region unblamed = new Region(0, 0, 30);
+ List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+ assertEquals(3, blamed.size());
+ Candidate c = blamed.get(0);
+ assertEquals(c.sourceCommit.name(), O1.name());
+ assertEquals(unblamed.resultStart, c.regionList.resultStart);
+ assertEquals(unblamed.sourceStart, c.regionList.sourceStart);
+ assertEquals(10, c.regionList.length);
+ assertNull(c.regionList.next);
+
+ c = blamed.get(1);
+ assertEquals(c.sourceCommit.name(), O2.name());
+ assertEquals(10, c.regionList.resultStart);
+ assertEquals(10, c.regionList.sourceStart);
+ assertEquals(10, c.regionList.length);
+ assertNull(c.regionList.next);
+
+ c = blamed.get(2);
+ assertEquals(c.sourceCommit.name(), O3.name());
+ assertEquals(20, c.regionList.resultStart);
+ assertEquals(20, c.regionList.sourceStart);
+ assertEquals(10, c.regionList.length);
+ assertNull(c.regionList.next);
+ }
+
+ @Test
+ public void blame_fromCandidate() {
+ // We don't use anything from the candidate besides the
+ // regionList
+ Candidate c = new Candidate(null, null, null);
+ c.regionList = new Region(0, 8, 5);
+ c.regionList.next = new Region(22, 22, 4);
+
+ Candidate blamed = blamer.mergeCandidate(c);
+ // Three candidates
+ assertNotNull(blamed);
+ assertNotNull(blamed.queueNext);
+ assertNotNull(blamed.queueNext.queueNext);
+ assertNull(blamed.queueNext.queueNext.queueNext);
+
+ assertEquals(O1.name(), blamed.sourceCommit.name());
+
+ Candidate second = blamed.queueNext;
+ assertEquals(O2.name(), second.sourceCommit.name());
+
+ Candidate third = blamed.queueNext.queueNext;
+ assertEquals(O3.name(), third.sourceCommit.name());
+ }
+
+ @Test
+ public void blame_fromCandidate_twiceCandidateInOutput() {
+ Candidate c = new Candidate(null, null, null);
+ // This produces O1 and O2
+ c.regionList = new Region(0, 8, 5);
+ // This produces O2 and O3
+ c.regionList.next = new Region(20, 15, 7);
+
+ Candidate blamed = blamer.mergeCandidate(c);
+ assertCandidateSingleRegion(O1, 2, blamed);
+ blamed = blamed.queueNext;
+ assertCandidateSingleRegion(O2, 3, blamed);
+ // We do not merge candidates afterwards, so these are
+ // two different candidates to the same source
+ blamed = blamed.queueNext;
+ assertCandidateSingleRegion(O2, 5, blamed);
+ blamed = blamed.queueNext;
+ assertCandidateSingleRegion(O3, 2, blamed);
+ assertNull(blamed.queueNext);
+ }
+
+ private static void assertCandidateSingleRegion(ObjectId expectedOid,
+ int expectedLength, Candidate actual) {
+ assertNotNull("candidate", actual);
+ assertNotNull("region list not empty", actual.regionList);
+ assertNull("region list has only one element", actual.regionList.next);
+ assertEquals(expectedOid, actual.sourceCommit);
+ assertEquals(expectedLength, actual.regionList.length);
+ }
+
+ private static final class BlameRegionMergerFakeCommits
+ extends BlameRegionMerger {
+
+ private final Map<ObjectId, RevCommit> cache;
+
+ BlameRegionMergerFakeCommits(List<RevCommit> commits,
+ List<CacheRegion> blamedRegions) {
+ super(null, null, blamedRegions);
+ cache = commits.stream().collect(Collectors
+ .toMap(RevCommit::toObjectId, Function.identity()));
+ }
+
+ @Override
+ protected RevCommit parse(ObjectId oid) {
+ return cache.get(oid);
+ }
+ }
+
+ private static final class FakeRevCommit extends RevCommit {
+ FakeRevCommit(AnyObjectId id) {
+ super(id);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java
index f657bab771..a2c20aaaba 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java
@@ -28,7 +28,7 @@ public class EditListTest {
assertTrue(l.isEmpty());
assertEquals("EditList[]", l.toString());
- assertEquals(l, l);
+ assertTrue(l.equals(l));
assertEquals(new EditList(), l);
assertFalse(l.equals(""));
assertEquals(l.hashCode(), new EditList().hashCode());
@@ -44,7 +44,7 @@ public class EditListTest {
assertSame(e, l.get(0));
assertSame(e, l.iterator().next());
- assertEquals(l, l);
+ assertTrue(l.equals(l));
assertFalse(l.equals(new EditList()));
final EditList l2 = new EditList();
@@ -69,7 +69,7 @@ public class EditListTest {
assertSame(e1, i.next());
assertSame(e2, i.next());
- assertEquals(l, l);
+ assertTrue(l.equals(l));
assertFalse(l.equals(new EditList()));
final EditList l2 = new EditList();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java
index 8ab9bb12de..86c6d77cc6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java
@@ -98,7 +98,7 @@ public class EditTest {
final Edit e1 = new Edit(1, 2, 3, 4);
final Edit e2 = new Edit(1, 2, 3, 4);
- assertEquals(e1, e1);
+ assertTrue(e1.equals(e1));
assertEquals(e2, e1);
assertEquals(e1, e2);
assertEquals(e1.hashCode(), e2.hashCode());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index fca27d32aa..0949d040e9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -12,12 +12,16 @@ package org.eclipse.jgit.gitrepo;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -221,4 +225,33 @@ public class ManifestParserTest {
testNormalize("", "");
testNormalize("a/b", "a/b");
}
+
+ @Test
+ public void testXXE() throws Exception {
+ File externalEntity = File.createTempFile("injected", "xml");
+ externalEntity.deleteOnExit();
+ Files.write(externalEntity.toPath(),
+ "<evil>injected xml</evil>"
+ .getBytes(UTF_8),
+ StandardOpenOption.WRITE);
+ String baseUrl = "https://git.google.com/";
+ StringBuilder xmlContent = new StringBuilder();
+ xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+ .append("<!DOCTYPE booo [ <!ENTITY foobar SYSTEM \"")
+ .append(externalEntity.getPath()).append("\"> ]>\n")
+ .append("<manifest>")
+ .append("<remote name=\"remote1\" fetch=\".\" />")
+ .append("<default revision=\"master\" remote=\"remote1\" />")
+ .append("&foobar;")
+ .append("<project path=\"foo\" name=\"foo\" groups=\"a,test\" />")
+ .append("</manifest>");
+
+ IOException e = assertThrows(IOException.class,
+ () -> new ManifestParser(null, null, "master", baseUrl, null,
+ null)
+ .read(new ByteArrayInputStream(
+ xmlContent.toString().getBytes(UTF_8))));
+ assertTrue(e.getCause().getMessage().contains("DOCTYPE"));
+ }
+
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
index 6112952549..6983eaa354 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/BasicRuleTest.java
@@ -31,7 +31,7 @@ public class BasicRuleTest {
assertFalse(rule1.getNegation());
assertTrue(rule3.getNegation());
assertNotEquals(rule1, null);
- assertEquals(rule1, rule1);
+ assertTrue(rule1.equals(rule1));
assertEquals(rule1, rule2);
assertNotEquals(rule1, rule3);
assertNotEquals(rule1, rule4);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
index d02bfcd3f6..1119db3712 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/indexdiff/IndexDiffWithSymlinkTest.java
@@ -132,7 +132,8 @@ public class IndexDiffWithSymlinkTest extends LocalDiskRepositoryTestCase {
Writer writer = new OutputStreamWriter(out, UTF_8)) {
writer.write("echo `which git` 1>&2\n");
writer.write("echo `git --version` 1>&2\n");
- writer.write("git init " + name + " && \\\n");
+ writer.write("git -c init.defaultBranch=master init " + name
+ + " && \\\n");
writer.write("cd ./" + name + " && \\\n");
writer.write("git fast-import < ../" + name + ".txt && \\\n");
writer.write("git checkout -f\n");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index 00a3760e21..80bd689084 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -1182,7 +1182,8 @@ public class DfsGarbageCollectorTest {
DfsReader reader = odb.newReader();
DfsPackFile gcPack = findFirstBySource(odb.getPacks(), GC);
assertTrue(gcPack.hasObjectSizeIndex(reader));
- assertEquals(12, gcPack.getIndexedObjectSize(reader, headsBlob));
+ assertEquals(12, gcPack.getIndexedObjectSize(reader,
+ gcPack.findIdxPosition(reader, headsBlob)));
}
@Test
@@ -1203,7 +1204,8 @@ public class DfsGarbageCollectorTest {
DfsReader reader = odb.newReader();
DfsPackFile gcPack = findFirstBySource(odb.getPacks(), GC);
assertTrue(gcPack.hasObjectSizeIndex(reader));
- assertEquals(-1, gcPack.getIndexedObjectSize(reader, tooSmallBlob));
+ assertEquals(-1, gcPack.getIndexedObjectSize(reader,
+ gcPack.findIdxPosition(reader, tooSmallBlob)));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
index 0b558edf2c..efa98de549 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java
@@ -214,7 +214,7 @@ public class DfsInserterTest {
}
@Test
- public void testNoCheckExisting() throws IOException {
+ public void testNoDuplicates() throws IOException {
byte[] contents = Constants.encode("foo");
ObjectId fooId;
try (ObjectInserter ins = db.newObjectInserter()) {
@@ -224,21 +224,20 @@ public class DfsInserterTest {
assertEquals(1, db.getObjectDatabase().listPacks().size());
try (ObjectInserter ins = db.newObjectInserter()) {
- ((DfsInserter) ins).checkExisting(false);
+ ins.insert(Constants.OBJ_BLOB, Constants.encode("bar"));
assertEquals(fooId, ins.insert(Constants.OBJ_BLOB, contents));
ins.flush();
}
assertEquals(2, db.getObjectDatabase().listPacks().size());
- // Verify that we have a foo in both INSERT packs.
+ // Newer packs are first. Verify that foo is only in the second pack
try (DfsReader reader = new DfsReader(db.getObjectDatabase())) {
DfsPackFile packs[] = db.getObjectDatabase().getPacks();
-
assertEquals(2, packs.length);
DfsPackFile p1 = packs[0];
assertEquals(PackSource.INSERT,
p1.getPackDescription().getPackSource());
- assertTrue(p1.hasObject(reader, fooId));
+ assertFalse(p1.hasObject(reader, fooId));
DfsPackFile p2 = packs[1];
assertEquals(PackSource.INSERT,
@@ -310,7 +309,8 @@ public class DfsInserterTest {
assertEquals(PackSource.INSERT,
insertPack.getPackDescription().getPackSource());
assertTrue(insertPack.hasObjectSizeIndex(reader));
- assertEquals(contents.length, insertPack.getIndexedObjectSize(reader, fooId));
+ assertEquals(contents.length, insertPack.getIndexedObjectSize(reader,
+ insertPack.findIdxPosition(reader, fooId)));
}
private static String readString(ObjectLoader loader) throws IOException {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
index 9680019f88..f2129fd3c5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
@@ -131,7 +131,8 @@ public class DfsPackFileTest {
DfsReader reader = db.getObjectDatabase().newReader();
DfsPackFile pack = db.getObjectDatabase().getPacks()[0];
assertTrue(pack.hasObjectSizeIndex(reader));
- assertEquals(800, pack.getIndexedObjectSize(reader, blobId));
+ assertEquals(800, pack.getIndexedObjectSize(reader,
+ pack.findIdxPosition(reader, blobId)));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackParserTest.java
index c1cd231c66..9d26978d66 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackParserTest.java
@@ -65,6 +65,7 @@ public class DfsPackParserTest {
DfsReader reader = repo.getObjectDatabase().newReader();
PackList packList = repo.getObjectDatabase().getPackList();
assertEquals(1, packList.packs.length);
- assertEquals(1, packList.packs[0].getIndexedObjectSize(reader, blobA));
+ assertEquals(1, packList.packs[0].getIndexedObjectSize(reader,
+ packList.packs[0].findIdxPosition(reader, blobA)));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
index 92d7465376..cd73c6ae83 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -702,13 +702,29 @@ public class BasePackWriterTest extends SampleDataRepositoryTestCase {
}
@Test
- public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSet()
+ public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSetTrue()
throws Exception {
+ totalPackFilesScanWhenSearchForReuseTimeoutNotSet(true);
+ }
+
+ @Test
+ public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSetFalse()
+ throws Exception {
+ totalPackFilesScanWhenSearchForReuseTimeoutNotSet(false);
+ }
+
+ public void totalPackFilesScanWhenSearchForReuseTimeoutNotSet(boolean doReturn) throws Exception {
FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+ int numberOfPackFiles = (int) new GC(fileRepository).getStatistics().numberOfPackFiles;
+ int objectsInMultiplePacks = 2;
+ int objectsInOnePacks = 1;
+ int expectedSelectCalls = objectsInMultiplePacks * (doReturn ? numberOfPackFiles : 1)
+ + objectsInOnePacks;
+
PackWriter mockedPackWriter = Mockito
.spy(new PackWriter(config, fileRepository.newObjectReader()));
- doNothing().when(mockedPackWriter).select(any(), any());
+ doReturn(doReturn).when(mockedPackWriter).select(any(), any());
try (FileOutputStream packOS = new FileOutputStream(
getPackFileToWrite(fileRepository, mockedPackWriter))) {
@@ -716,27 +732,37 @@ public class BasePackWriterTest extends SampleDataRepositoryTestCase {
NullProgressMonitor.INSTANCE, packOS);
}
- long numberOfPackFiles = new GC(fileRepository)
- .getStatistics().numberOfPackFiles;
- int expectedSelectCalls =
- // Objects contained in multiple packfiles * number of packfiles
- 2 * (int) numberOfPackFiles +
- // Objects in single packfile
- 1;
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
any());
}
@Test
- public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck()
+ public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheckTrue()
throws Exception {
+ totalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck(true);
+ }
+
+ @Test
+ public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheckFalse()
+ throws Exception {
+ totalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck(false);
+ }
+
+ public void totalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck(
+ boolean doReturn) throws Exception {
FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+ int numberOfPackFiles = (int) new GC(fileRepository).getStatistics().numberOfPackFiles;
+ int objectsInMultiplePacks = 2;
+ int objectsInOnePacks = 1;
+ int expectedSelectCalls = objectsInMultiplePacks * (doReturn ? numberOfPackFiles : 1)
+ + objectsInOnePacks;
+
PackConfig packConfig = new PackConfig();
packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
PackWriter mockedPackWriter = Mockito.spy(
new PackWriter(packConfig, fileRepository.newObjectReader()));
- doNothing().when(mockedPackWriter).select(any(), any());
+ doReturn(doReturn).when(mockedPackWriter).select(any(), any());
try (FileOutputStream packOS = new FileOutputStream(
getPackFileToWrite(fileRepository, mockedPackWriter))) {
@@ -744,28 +770,31 @@ public class BasePackWriterTest extends SampleDataRepositoryTestCase {
NullProgressMonitor.INSTANCE, packOS);
}
- long numberOfPackFiles = new GC(fileRepository)
- .getStatistics().numberOfPackFiles;
- int expectedSelectCalls =
- // Objects contained in multiple packfiles * number of packfiles
- 2 * (int) numberOfPackFiles +
- // Objects contained in single packfile
- 1;
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
any());
}
@Test
- public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck()
+ public void partialPackFilesScanWhenDoingSearchForReuseTimeoutCheck()
throws Exception {
+ int objectsInMultiplePacks = 2;
+ int objectsInOnePacks = 1;
+ int expectedSelectCalls = objectsInMultiplePacks + objectsInOnePacks;
+ testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck(true, expectedSelectCalls);
+ testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck(false, expectedSelectCalls);
+ }
+
+ public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck(
+ boolean doReturn, int expectedSelectCalls) throws Exception {
FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+
PackConfig packConfig = new PackConfig();
packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
PackWriter mockedPackWriter = Mockito.spy(
new PackWriter(packConfig, fileRepository.newObjectReader()));
mockedPackWriter.enableSearchForReuseTimeout();
- doNothing().when(mockedPackWriter).select(any(), any());
+ doReturn(doReturn).when(mockedPackWriter).select(any(), any());
try (FileOutputStream packOS = new FileOutputStream(
getPackFileToWrite(fileRepository, mockedPackWriter))) {
@@ -773,7 +802,6 @@ public class BasePackWriterTest extends SampleDataRepositoryTestCase {
NullProgressMonitor.INSTANCE, packOS);
}
- int expectedSelectCalls = 3; // Objects in packfiles
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
any());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index 1af42cb229..a0afc3ef13 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -1263,7 +1263,7 @@ public class BatchRefUpdateTest extends LocalDiskRepositoryTestCase {
}
private ReflogEntry getLastReflog(String name) throws IOException {
- ReflogReader r = diskRepo.getReflogReader(name);
+ ReflogReader r = diskRepo.getRefDatabase().getReflogReader(name);
if (r == null) {
return null;
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
index 6c7992716c..e8363ce21d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableStackTest.java
@@ -81,8 +81,7 @@ public class FileReftableStackTest {
}
public void testCompaction(int N) throws Exception {
- try (FileReftableStack stack = new FileReftableStack(
- new File(reftableDir, "refs"), reftableDir, null,
+ try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
() -> new Config())) {
writeBranches(stack, "refs/heads/branch%d", 0, N);
MergedReftable table = stack.getMergedReftable();
@@ -124,8 +123,7 @@ public class FileReftableStackTest {
// Can't delete in-use files on Windows.
assumeFalse(SystemReader.getInstance().isWindows());
- try (FileReftableStack stack = new FileReftableStack(
- new File(reftableDir, "refs"), reftableDir, null,
+ try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
() -> new Config())) {
outer: for (int i = 0; i < 10; i++) {
final long next = stack.getMergedReftable().maxUpdateIndex()
@@ -152,8 +150,8 @@ public class FileReftableStackTest {
}
}
assertThrows(FileNotFoundException.class,
- () -> new FileReftableStack(new File(reftableDir, "refs"),
- reftableDir, null, () -> new Config()));
+ () -> new FileReftableStack(reftableDir, null,
+ () -> new Config()));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
index 01cbcfd20f..5756b41442 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.FileOutputStream;
@@ -33,8 +34,15 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
-
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -51,6 +59,10 @@ import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
import org.junit.Test;
public class FileReftableTest extends SampleDataRepositoryTestCase {
@@ -66,6 +78,30 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
@SuppressWarnings("boxing")
@Test
+ public void testReloadIfNecessary() throws Exception {
+ ObjectId id = db.resolve("master");
+ try (FileRepository repo1 = new FileRepository(db.getDirectory());
+ FileRepository repo2 = new FileRepository(db.getDirectory())) {
+ ((FileReftableDatabase) repo1.getRefDatabase())
+ .setAutoRefresh(true);
+ ((FileReftableDatabase) repo2.getRefDatabase())
+ .setAutoRefresh(true);
+ FileRepository repos[] = { repo1, repo2 };
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 2; j++) {
+ FileRepository repo = repos[j];
+ RefUpdate u = repo.getRefDatabase().newUpdate(
+ String.format("branch%d", i * 10 + j), false);
+ u.setNewObjectId(id);
+ RefUpdate.Result r = u.update();
+ assertEquals(Result.NEW, r);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ @Test
public void testRacyReload() throws Exception {
ObjectId id = db.resolve("master");
int retry = 0;
@@ -98,6 +134,54 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
}
@Test
+ public void testConcurrentRacyReload() throws Exception {
+ ObjectId id = db.resolve("master");
+ final CyclicBarrier barrier = new CyclicBarrier(2);
+
+ class UpdateRef implements Callable<RefUpdate.Result> {
+
+ private RefUpdate u;
+
+ UpdateRef(FileRepository repo, String branchName)
+ throws IOException {
+ u = repo.getRefDatabase().newUpdate(branchName,
+ false);
+ u.setNewObjectId(id);
+ }
+
+ @Override
+ public RefUpdate.Result call() throws Exception {
+ barrier.await(); // wait for the other thread to prepare
+ return u.update();
+ }
+ }
+
+ ExecutorService pool = Executors.newFixedThreadPool(2);
+ try (FileRepository repo1 = new FileRepository(db.getDirectory());
+ FileRepository repo2 = new FileRepository(db.getDirectory())) {
+ ((FileReftableDatabase) repo1.getRefDatabase())
+ .setAutoRefresh(true);
+ ((FileReftableDatabase) repo2.getRefDatabase())
+ .setAutoRefresh(true);
+ for (int i = 0; i < 10; i++) {
+ String branchName = String.format("branch%d",
+ Integer.valueOf(i));
+ Future<RefUpdate.Result> ru1 = pool
+ .submit(new UpdateRef(repo1, branchName));
+ Future<RefUpdate.Result> ru2 = pool
+ .submit(new UpdateRef(repo2, branchName));
+ assertTrue((ru1.get() == Result.NEW
+ && ru2.get() == Result.LOCK_FAILURE)
+ || (ru1.get() == Result.LOCK_FAILURE
+ && ru2.get() == Result.NEW));
+ }
+ } finally {
+ pool.shutdown();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
public void testCompactFully() throws Exception {
ObjectId c1 = db.resolve("master^^");
ObjectId c2 = db.resolve("master^");
@@ -171,7 +255,8 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
v.update();
db.convertToPackedRefs(true, false);
- List<ReflogEntry> logs = db.getReflogReader("refs/heads/master").getReverseEntries(2);
+ List<ReflogEntry> logs = db.getRefDatabase()
+ .getReflogReader("refs/heads/master").getReverseEntries(2);
assertEquals("banana", logs.get(0).getComment());
assertEquals("apple", logs.get(1).getComment());
}
@@ -185,8 +270,9 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
ReceiveCommand rc1 = new ReceiveCommand(ObjectId.zeroId(), cur, "refs/heads/batch1");
ReceiveCommand rc2 = new ReceiveCommand(ObjectId.zeroId(), prev, "refs/heads/batch2");
String msg = "message";
+ RefDatabase refDb = db.getRefDatabase();
try (RevWalk rw = new RevWalk(db)) {
- db.getRefDatabase().newBatchUpdate()
+ refDb.newBatchUpdate()
.addCommand(rc1, rc2)
.setAtomic(true)
.setRefLogIdent(person)
@@ -197,12 +283,14 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
assertEquals(ReceiveCommand.Result.OK, rc1.getResult());
assertEquals(ReceiveCommand.Result.OK, rc2.getResult());
- ReflogEntry e = db.getReflogReader("refs/heads/batch1").getLastEntry();
+ ReflogEntry e = refDb.getReflogReader("refs/heads/batch1")
+ .getLastEntry();
assertEquals(msg, e.getComment());
assertEquals(person, e.getWho());
assertEquals(cur, e.getNewId());
- e = db.getReflogReader("refs/heads/batch2").getLastEntry();
+ e = refDb.getReflogReader("refs/heads/batch2")
+ .getLastEntry();
assertEquals(msg, e.getComment());
assertEquals(person, e.getWho());
assertEquals(prev, e.getNewId());
@@ -309,7 +397,7 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
// the branch HEAD referred to is left untouched
assertEquals(pid, db.resolve("refs/heads/master"));
- ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
ReflogEntry e = reflogReader.getReverseEntries().get(0);
assertEquals(ppid, e.getNewId());
assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
@@ -330,7 +418,8 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
updateRef.setForceUpdate(true);
RefUpdate.Result update = updateRef.update();
assertEquals(FORCED, update); // internal
- ReflogReader r = db.getReflogReader("refs/heads/master");
+ ReflogReader r = db.getRefDatabase()
+ .getReflogReader("refs/heads/master");
ReflogEntry e = r.getLastEntry();
assertEquals(pid, e.getNewId());
@@ -355,7 +444,8 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
assertEquals(RefUpdate.Result.NO_CHANGE, ref.delete());
// Differs from RefupdateTest. Deleting a loose ref leaves reflog trail.
- ReflogReader reader = db.getReflogReader("refs/heads/abc");
+ ReflogReader reader = db.getRefDatabase()
+ .getReflogReader("refs/heads/abc");
assertEquals(ObjectId.zeroId(), reader.getReverseEntry(1).getOldId());
assertEquals(nonZero, reader.getReverseEntry(1).getNewId());
assertEquals(nonZero, reader.getReverseEntry(0).getOldId());
@@ -382,8 +472,9 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
assertNotSame(newid, r.getObjectId());
assertSame(ObjectId.class, r.getObjectId().getClass());
assertEquals(newid, r.getObjectId());
- List<ReflogEntry> reverseEntries1 = db.getReflogReader("refs/heads/abc")
- .getReverseEntries();
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> reverseEntries1 = refDb
+ .getReflogReader("refs/heads/abc").getReverseEntries();
ReflogEntry entry1 = reverseEntries1.get(0);
assertEquals(1, reverseEntries1.size());
assertEquals(ObjectId.zeroId(), entry1.getOldId());
@@ -392,7 +483,7 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
assertEquals(new PersonIdent(db).toString(),
entry1.getWho().toString());
assertEquals("", entry1.getComment());
- List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
+ List<ReflogEntry> reverseEntries2 = refDb.getReflogReader("HEAD")
.getReverseEntries();
assertEquals(0, reverseEntries2.size());
}
@@ -455,7 +546,7 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
// the branch HEAD referred to is left untouched
assertNull(db.resolve("refs/heads/unborn"));
- ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
ReflogEntry e = reflogReader.getReverseEntries().get(0);
assertEquals(ObjectId.zeroId(), e.getOldId());
assertEquals(ppid, e.getNewId());
@@ -499,7 +590,7 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
names.add("refs/heads/new/name");
for (String nm : names) {
- ReflogReader rd = db.getReflogReader(nm);
+ ReflogReader rd = db.getRefDatabase().getReflogReader(nm);
assertNotNull(rd);
ReflogEntry last = rd.getLastEntry();
ObjectId id = last.getNewId();
@@ -644,6 +735,54 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
checkContainsRef(refs, db.exactRef("HEAD"));
}
+ @Test
+ public void testExternalUpdate_bug_102() throws Exception {
+ ((FileReftableDatabase) db.getRefDatabase()).setAutoRefresh(true);
+ assumeTrue(atLeastGitVersion(2, 45));
+ Git git = Git.wrap(db);
+ git.tag().setName("foo").call();
+ Ref ref = db.exactRef("refs/tags/foo");
+ assertNotNull(ref);
+ runGitCommand("tag", "--force", "foo", "e");
+ Ref e = db.exactRef("refs/heads/e");
+ Ref foo = db.exactRef("refs/tags/foo");
+ assertEquals(e.getObjectId(), foo.getObjectId());
+ }
+
+ private String toString(TemporaryBuffer b) throws IOException {
+ return RawParseUtils.decode(b.toByteArray());
+ }
+
+ private ExecutionResult runGitCommand(String... args)
+ throws IOException, InterruptedException {
+ FS fs = db.getFS();
+ ProcessBuilder pb = fs.runInShell("git", args);
+ pb.directory(db.getWorkTree());
+ System.err.println("PATH=" + pb.environment().get("PATH"));
+ ExecutionResult result = fs.execute(pb, null);
+ assertEquals(0, result.getRc());
+ String err = toString(result.getStderr());
+ if (!err.isEmpty()) {
+ System.err.println(err);
+ }
+ String out = toString(result.getStdout());
+ if (!out.isEmpty()) {
+ System.out.println(out);
+ }
+ return result;
+ }
+
+ private boolean atLeastGitVersion(int minMajor, int minMinor)
+ throws IOException, InterruptedException {
+ String version = toString(runGitCommand("version").getStdout())
+ .split(" ")[2];
+ System.out.println(version);
+ String[] digits = version.split("\\.");
+ int major = Integer.parseInt(digits[0]);
+ int minor = Integer.parseInt(digits[1]);
+ return (major >= minMajor) && (minor >= minMinor);
+ }
+
private RefUpdate updateRef(String name) throws IOException {
final RefUpdate ref = db.updateRef(name);
ref.setNewObjectId(db.resolve(Constants.HEAD));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java
new file mode 100644
index 0000000000..1a05d88583
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2025, Google LLC. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+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 java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class GcObjectSizeIndexTest extends GcTestCase {
+
+ @Test
+ public void gc_2commits_noSizeLimit_blobsInIndex() throws Exception {
+ TestRepository<FileRepository>.BranchBuilder bb = tr
+ .branch("refs/heads/master");
+ RevBlob blobA1 = tr.blob("7-bytes");
+ RevBlob blobA2 = tr.blob("11-bytes xx");
+ RevBlob blobB1 = tr.blob("B");
+ RevBlob blobB2 = tr.blob("B2");
+ bb.commit().add("A", blobA1).add("B", blobB1).create();
+ bb.commit().add("A", blobA2).add("B", blobB2).create();
+
+ stats = gc.getStatistics();
+ assertEquals(8, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ configureGc(gc, 0);
+ gc.gc().get();
+
+ stats = gc.getStatistics();
+ assertEquals(1, stats.numberOfPackFiles);
+ assertEquals(4, stats.numberOfSizeIndexedObjects);
+
+ assertTrue(getOnlyPack(repo).hasObjectSizeIndex());
+ Pack pack = getOnlyPack(repo);
+ assertEquals(7, pack.getIndexedObjectSize(blobA1));
+ assertEquals(11, pack.getIndexedObjectSize(blobA2));
+ assertEquals(1, pack.getIndexedObjectSize(blobB1));
+ assertEquals(2, pack.getIndexedObjectSize(blobB2));
+ }
+
+ @Test
+ public void gc_2commits_sizeLimit_biggerBlobsInIndex() throws Exception {
+ TestRepository<FileRepository>.BranchBuilder bb = tr
+ .branch("refs/heads/master");
+ RevBlob blobA1 = tr.blob("7-bytes");
+ RevBlob blobA2 = tr.blob("11-bytes xx");
+ RevBlob blobB1 = tr.blob("B");
+ RevBlob blobB2 = tr.blob("B2");
+ bb.commit().add("A", blobA1).add("B", blobB1).create();
+ bb.commit().add("A", blobA2).add("B", blobB2).create();
+
+ stats = gc.getStatistics();
+ assertEquals(8, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ configureGc(gc, 5);
+ gc.gc().get();
+
+ stats = gc.getStatistics();
+ assertEquals(1, stats.numberOfPackFiles);
+ assertEquals(2, stats.numberOfSizeIndexedObjects);
+
+ assertTrue(getOnlyPack(repo).hasObjectSizeIndex());
+ Pack pack = getOnlyPack(repo);
+ assertEquals(7, pack.getIndexedObjectSize(blobA1));
+ assertEquals(11, pack.getIndexedObjectSize(blobA2));
+ assertEquals(-1, pack.getIndexedObjectSize(blobB1));
+ assertEquals(-1, pack.getIndexedObjectSize(blobB2));
+ }
+
+ @Test
+ public void gc_2commits_disableSizeIdx_noIdx() throws Exception {
+ TestRepository<FileRepository>.BranchBuilder bb = tr
+ .branch("refs/heads/master");
+ RevBlob blobA1 = tr.blob("7-bytes");
+ RevBlob blobA2 = tr.blob("11-bytes xx");
+ RevBlob blobB1 = tr.blob("B");
+ RevBlob blobB2 = tr.blob("B2");
+ bb.commit().add("A", blobA1).add("B", blobB1).create();
+ bb.commit().add("A", blobA2).add("B", blobB2).create();
+
+ stats = gc.getStatistics();
+ assertEquals(8, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ configureGc(gc, -1);
+ gc.gc().get();
+
+
+ stats = gc.getStatistics();
+ assertEquals(1, stats.numberOfPackFiles);
+ assertEquals(0, stats.numberOfSizeIndexedObjects);
+ }
+
+ @Test
+ public void gc_alreadyPacked_noChanges()
+ throws Exception {
+ tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
+ .create();
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ configureGc(gc, 0);
+ gc.gc().get();
+
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ assertTrue(getOnlyPack(repo).hasObjectSizeIndex());
+ assertEquals(2, stats.numberOfSizeIndexedObjects);
+
+ // Do the gc again and check that it hasn't changed anything
+ gc.gc().get();
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ assertTrue(getOnlyPack(repo).hasObjectSizeIndex());
+ assertEquals(2, stats.numberOfSizeIndexedObjects);
+ }
+
+ @Test
+ public void gc_twoReachableCommits_oneUnreachable_twoPacks()
+ throws Exception {
+ TestRepository<FileRepository>.BranchBuilder bb = tr
+ .branch("refs/heads/master");
+ RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
+ bb.commit().add("A", "A2").add("B", "B2").create();
+ tr.update("refs/heads/master", first);
+
+ stats = gc.getStatistics();
+ assertEquals(8, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ configureGc(gc, 0);
+ gc.gc().get();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(8, stats.numberOfPackedObjects);
+ assertEquals(2, stats.numberOfPackFiles);
+ assertEquals(4, stats.numberOfSizeIndexedObjects);
+ }
+
+ @Test
+ public void gc_preserved_objSizeIdxIsPreserved() throws Exception {
+ Collection<Pack> oldPacks = preserveOldPacks();
+ assertEquals(1, oldPacks.size());
+ PackFile preserved = oldPacks.iterator().next().getPackFile()
+ .create(PackExt.OBJECT_SIZE_INDEX)
+ .createPreservedForDirectory(
+ repo.getObjectDatabase().getPreservedDirectory());
+ assertTrue(preserved.exists());
+ }
+
+ @Test
+ public void gc_preserved_prune_noPreserves() throws Exception {
+ preserveOldPacks();
+ configureGc(gc, 0).setPrunePreserved(true);
+ gc.gc().get();
+
+ assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists());
+ }
+
+ private Collection<Pack> preserveOldPacks() throws Exception {
+ TestRepository<FileRepository>.BranchBuilder bb = tr
+ .branch("refs/heads/master");
+ bb.commit().message("P").add("P", "P").create();
+
+ // pack loose object into packfile
+ configureGc(gc, 0);
+ gc.setExpireAgeMillis(0);
+ gc.gc().get();
+ Collection<Pack> oldPacks = tr.getRepository().getObjectDatabase()
+ .getPacks();
+ PackFile oldPackfile = oldPacks.iterator().next().getPackFile();
+ assertTrue(oldPackfile.exists());
+
+ fsTick();
+ bb.commit().message("B").add("B", "Q").create();
+
+ // repack again but now without a grace period for packfiles. We should
+ // end up with a new packfile and the old one should be placed in the
+ // preserved directory
+ gc.setPackExpireAgeMillis(0);
+ configureGc(gc, 0).setPreserveOldPacks(true);
+ gc.gc().get();
+
+ File preservedPackFile = oldPackfile.createPreservedForDirectory(
+ repo.getObjectDatabase().getPreservedDirectory());
+ assertTrue(preservedPackFile.exists());
+ return oldPacks;
+ }
+
+ @Ignore
+ public void testPruneAndRestoreOldPacks() throws Exception {
+ String tempRef = "refs/heads/soon-to-be-unreferenced";
+ TestRepository<FileRepository>.BranchBuilder bb = tr.branch(tempRef);
+ bb.commit().add("A", "A").add("B", "B").create();
+
+ // Verify setup conditions
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+
+ // Force all referenced objects into packs (to avoid having loose objects)
+ configureGc(gc, 0);
+ gc.setExpireAgeMillis(0);
+ gc.setPackExpireAgeMillis(0);
+ gc.gc().get();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+
+ // Delete the temp ref, orphaning its commit
+ RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
+ update.setForceUpdate(true);
+ ObjectId objectId = update.getOldObjectId(); // remember it so we can restore it!
+ RefUpdate.Result result = update.delete();
+ assertEquals(RefUpdate.Result.FORCED, result);
+
+ fsTick();
+
+ // Repack with only orphaned commit, so packfile will be pruned
+ configureGc(gc, 0).setPreserveOldPacks(true);
+ gc.gc().get();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ assertEquals(0, stats.numberOfPackFiles);
+
+ // Restore the temp ref to the deleted commit, should restore old-packs!
+ update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
+ update.setNewObjectId(objectId);
+ update.setExpectedOldObjectId(null);
+ result = update.update();
+ assertEquals(RefUpdate.Result.NEW, result);
+
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ }
+
+ private PackConfig configureGc(GC myGc, int minSize) {
+ PackConfig pconfig = new PackConfig(repo);
+ pconfig.setMinBytesForObjSizeIndex(minSize);
+ myGc.setPackConfig(pconfig);
+ return pconfig;
+ }
+
+ private Pack getOnlyPack(FileRepository fileRepo)
+ throws IOException {
+ Collection<Pack> packs = fileRepo.getObjectDatabase().getPacks();
+ if (packs.size() != 1) {
+ throw new IOException("More than one pack");
+ }
+
+ return packs.iterator().next();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java
index 3cd766c4e9..af52e2cb85 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcSinceBitmapStatisticsTest.java
@@ -20,6 +20,7 @@ import java.util.stream.StreamSupport;
import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.junit.Test;
public class GcSinceBitmapStatisticsTest extends GcTestCase {
@@ -51,11 +52,19 @@ public class GcSinceBitmapStatisticsTest extends GcTestCase {
}
@Test
- public void testShouldReportNoPacksDirectlyAfterGc() throws Exception {
- // given
+ public void testShouldReportNoPacksFilesSinceBitmapWhenPackfilesAreOlderThanBitmapFile()
+ throws Exception {
addCommit(null);
- gc.gc().get();
+ configureGC(/* buildBitmap */ false).gc().get();
+ assertEquals(1L, gc.getStatistics().numberOfPackFiles);
+ assertEquals(0L, repositoryBitmapFiles());
+ assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+
+ addCommit(null);
+ configureGC(/* buildBitmap */ true).gc().get();
+
assertEquals(1L, repositoryBitmapFiles());
+ assertEquals(2L, gc.getStatistics().numberOfPackFiles);
assertEquals(0L, gc.getStatistics().numberOfPackFilesSinceBitmap);
}
@@ -79,8 +88,11 @@ public class GcSinceBitmapStatisticsTest extends GcTestCase {
// progress & pack
addCommit(parent);
- tr.packAndPrune();
+ assertEquals(1L, gc.getStatistics().numberOfPackFiles);
+ assertEquals(0L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+ tr.packAndPrune();
+ assertEquals(2L, gc.getStatistics().numberOfPackFiles);
assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap);
}
@@ -90,13 +102,17 @@ public class GcSinceBitmapStatisticsTest extends GcTestCase {
// commit & gc
RevCommit parent = addCommit(null);
gc.gc().get();
+ assertEquals(0L, gc.getStatistics().numberOfLooseObjects);
assertEquals(0L, gc.getStatistics().numberOfObjectsSinceBitmap);
// progress & pack
addCommit(parent);
+ assertEquals(1L, gc.getStatistics().numberOfLooseObjects);
assertEquals(1L, gc.getStatistics().numberOfObjectsSinceBitmap);
tr.packAndPrune();
+ assertEquals(0L, gc.getStatistics().numberOfLooseObjects);
+ // Number of objects contained in the newly created PackFile
assertEquals(3L, gc.getStatistics().numberOfObjectsSinceBitmap);
}
@@ -164,4 +180,11 @@ public class GcSinceBitmapStatisticsTest extends GcTestCase {
}
}).sum();
}
+
+ private GC configureGC(boolean buildBitmap) {
+ PackConfig pc = new PackConfig(repo.getObjectDatabase().getConfig());
+ pc.setBuildBitmaps(buildBitmap);
+ gc.setPackConfig(pc);
+ return gc;
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index d1342c0fbd..33cbc868ca 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -49,7 +49,10 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.io.File;
@@ -66,6 +69,7 @@ import java.util.concurrent.Future;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -207,33 +211,35 @@ public class ObjectDirectoryTest extends RepositoryTestCase {
.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b");
WindowCursor curs = new WindowCursor(db.getObjectDatabase());
- LooseObjects mock = mock(LooseObjects.class);
+ Config config = new Config();
+ config.setString("core", null, "trustLooseObjectStat", "ALWAYS");
+ LooseObjects spy = Mockito.spy(new LooseObjects(config, trash));
UnpackedObjectCache unpackedObjectCacheMock = mock(
UnpackedObjectCache.class);
- Mockito.when(mock.getObjectLoader(any(), any(), any()))
- .thenThrow(new IOException("Stale File Handle"));
- Mockito.when(mock.open(curs, id)).thenCallRealMethod();
- Mockito.when(mock.unpackedObjectCache())
- .thenReturn(unpackedObjectCacheMock);
+ doThrow(new IOException("Stale File Handle")).when(spy)
+ .getObjectLoader(any(), any(), any());
+ doReturn(unpackedObjectCacheMock).when(spy).unpackedObjectCache();
- assertNull(mock.open(curs, id));
+ assertNull(spy.open(curs, id));
verify(unpackedObjectCacheMock).remove(id);
}
- @Test
+ @Test(expected = IOException.class)
public void testOpenLooseObjectPropagatesIOExceptions() throws Exception {
ObjectId id = ObjectId
.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b");
WindowCursor curs = new WindowCursor(db.getObjectDatabase());
- LooseObjects mock = mock(LooseObjects.class);
+ Config config = new Config();
+ config.setString("core", null, "trustLooseObjectStat", "NEVER");
+ LooseObjects spy = spy(new LooseObjects(config,
+ db.getObjectDatabase().getDirectory()));
- Mockito.when(mock.getObjectLoader(any(), any(), any()))
- .thenThrow(new IOException("some IO failure"));
- Mockito.when(mock.open(curs, id)).thenCallRealMethod();
+ doThrow(new IOException("some IO failure")).when(spy)
+ .getObjectLoader(any(), any(), any());
- assertThrows(IOException.class, () -> mock.open(curs, id));
+ spy.open(curs, id);
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
index 24bdc4a97a..1f934acced 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
@@ -13,6 +13,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.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
@@ -25,6 +26,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -99,6 +101,39 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
}
}
+ @Test
+ public void testIteratorMutableEntryCompareTo() {
+ Iterator<PackIndex.MutableEntry> iterA = smallIdx.iterator();
+ Iterator<PackIndex.MutableEntry> iterB = smallIdx.iterator();
+
+ MutableEntry aEntry = iterA.next();
+ iterB.next();
+ MutableEntry bEntry = iterB.next();
+ // b is one ahead
+ assertTrue(aEntry.compareBySha1To(bEntry) < 0);
+ assertTrue(bEntry.compareBySha1To(aEntry) > 0);
+
+ // advance a, now should be equal
+ assertEquals(0, iterA.next().compareBySha1To(bEntry));
+ }
+
+ @Test
+ public void testIteratorMutableEntryCopyTo() {
+ Iterator<PackIndex.MutableEntry> it = smallIdx.iterator();
+
+ MutableObjectId firstOidCopy = new MutableObjectId();
+ MutableEntry next = it.next();
+ next.copyOidTo(firstOidCopy);
+ ObjectId firstImmutable = next.toObjectId();
+
+ MutableEntry second = it.next();
+
+ // The copy has the right value after "next"
+ assertTrue(firstImmutable.equals(firstOidCopy));
+ assertFalse("iterator has moved",
+ second.toObjectId().equals(firstImmutable));
+ }
+
/**
* Test results of iterator comparing to content of well-known (prepared)
* small index.
@@ -106,22 +141,22 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
@Test
public void testIteratorReturnedValues1() {
Iterator<PackIndex.MutableEntry> iter = smallIdx.iterator();
- assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", iter.next()
- .name());
- assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab", iter.next()
- .name());
- assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259", iter.next()
- .name());
- assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3", iter.next()
- .name());
- assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7", iter.next()
- .name());
- assertEquals("902d5476fa249b7abc9d84c611577a81381f0327", iter.next()
- .name());
- assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035", iter.next()
- .name());
- assertEquals("c59759f143fb1fe21c197981df75a7ee00290799", iter.next()
- .name());
+ assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904",
+ iter.next().name());
+ assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab",
+ iter.next().name());
+ assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259",
+ iter.next().name());
+ assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3",
+ iter.next().name());
+ assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7",
+ iter.next().name());
+ assertEquals("902d5476fa249b7abc9d84c611577a81381f0327",
+ iter.next().name());
+ assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035",
+ iter.next().name());
+ assertEquals("c59759f143fb1fe21c197981df75a7ee00290799",
+ iter.next().name());
assertFalse(iter.hasNext());
}
@@ -198,16 +233,16 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
@Test
public void testIteratorReturnedValues2() {
Iterator<PackIndex.MutableEntry> iter = denseIdx.iterator();
- while (!iter.next().name().equals(
- "0a3d7772488b6b106fb62813c4d6d627918d9181")) {
+ while (!iter.next().name()
+ .equals("0a3d7772488b6b106fb62813c4d6d627918d9181")) {
// just iterating
}
- assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3", iter.next()
- .name()); // same level-1
- assertEquals("10da5895682013006950e7da534b705252b03be6", iter.next()
- .name()); // same level-1
- assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8", iter.next()
- .name());
+ assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3",
+ iter.next().name()); // same level-1
+ assertEquals("10da5895682013006950e7da534b705252b03be6",
+ iter.next().name()); // same level-1
+ assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8",
+ iter.next().name());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
index 85043034aa..cc43d3c2bb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java
@@ -53,6 +53,7 @@ import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
@@ -77,12 +78,14 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ObjectStream;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.util.IO;
@@ -489,6 +492,38 @@ public class PackInserterTest extends RepositoryTestCase {
}
}
+ @Test
+ public void createsObjectSizeIndex() throws Exception {
+ FileBasedConfig jGitConfig = mockSystemReader.getJGitConfig();
+ jGitConfig.setInt(
+ ConfigConstants.CONFIG_PACK_SECTION,
+ null,
+ ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, 10);
+ jGitConfig.save();
+ byte[] oneBlob = Constants.encode("a blob with some content");
+ byte[] anotherBlob = Constants.encode("some more contents");
+ byte[] streamMeBlob = Constants.encode("some more content to write");
+
+ ObjectId oneBlobOid, anotherBlobOid, streamMeBlobOid;
+ try (PackInserter ins = newInserter()) {
+ oneBlobOid = ins.insert(OBJ_BLOB, oneBlob);
+ anotherBlobOid = ins.insert(OBJ_BLOB, anotherBlob);
+ streamMeBlobOid = ins.insert(OBJ_BLOB, streamMeBlob.length,
+ new ByteArrayInputStream(streamMeBlob));
+ ins.flush();
+ }
+
+ List<Pack> listPacks = listPacks(db);
+ assertEquals(1, listPacks.size());
+ Pack thePack = listPacks.get(0);
+ assertTrue(thePack.hasObjectSizeIndex());
+ assertEquals(oneBlob.length, thePack.getIndexedObjectSize(oneBlobOid));
+ assertEquals(anotherBlob.length,
+ thePack.getIndexedObjectSize(anotherBlobOid));
+ assertEquals(streamMeBlob.length,
+ thePack.getIndexedObjectSize(streamMeBlobOid));
+ }
+
private List<Pack> listPacks() throws Exception {
List<Pack> fromOpenDb = listPacks(db);
List<Pack> reopened;
@@ -549,7 +584,8 @@ public class PackInserterTest extends RepositoryTestCase {
}
private void assertPacksOnly() throws Exception {
- new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx"))
+ new BadFileCollector(f -> !f.endsWith(".pack") && !f.endsWith(".idx")
+ && !f.endsWith(".objsize"))
.assertNoBadFiles(db.getObjectDatabase().getDirectory());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java
index e1509456e5..016a6afd70 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java
@@ -10,6 +10,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import java.util.zip.Deflater;
import org.eclipse.jgit.errors.LargeObjectException;
@@ -39,6 +41,7 @@ import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRng;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -47,6 +50,7 @@ import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectStream;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.transport.PackParser;
import org.eclipse.jgit.transport.PackedObjectInfo;
@@ -295,6 +299,29 @@ public class PackTest extends LocalDiskRepositoryTestCase {
}
}
+ @Test
+ public void testObjectSize() throws Exception {
+ byte[] data = getRng().nextBytes(300);
+ RevBlob aBlob = tr.blob(data);
+ RevCommit aCommit = tr.branch("master").commit().add("A", aBlob).create();
+ repo.getConfig().setInt(CONFIG_PACK_SECTION, null, ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, 0);
+ tr.packAndPrune();
+
+ List<Pack> packs = repo.getObjectDatabase().getPacks().stream().collect(Collectors.toList());
+ assertEquals(1, packs.size());
+ // Indexed object
+ assertEquals(300, packs.get(0).getIndexedObjectSize(aBlob));
+ assertEquals(300, packs.get(0).getObjectSize(wc, aBlob));
+ // Non indexed object
+ assertEquals(-1, packs.get(0).getIndexedObjectSize(aCommit));
+ assertEquals(168, packs.get(0).getObjectSize(wc, aCommit));
+ // Object not in pack
+ assertEquals(-1, packs.get(0).getObjectSize(wc,
+ ObjectId.fromString("1111111111111111111111111111111111111111")));
+ assertEquals(-1, packs.get(0).getIndexedObjectSize(
+ ObjectId.fromString("1111111111111111111111111111111111111111")));
+ }
+
private static byte[] clone(int first, byte[] base) {
byte[] r = new byte[base.length];
System.arraycopy(base, 1, r, 1, r.length - 1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index cb977bd601..acc36d76f4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -40,6 +40,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -111,16 +112,17 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertNotSame(newid, r.getObjectId());
assertSame(ObjectId.class, r.getObjectId().getClass());
assertEquals(newid, r.getObjectId());
- List<ReflogEntry> reverseEntries1 = db
+ List<ReflogEntry> reverseEntries1 = db.getRefDatabase()
.getReflogReader("refs/heads/abc").getReverseEntries();
ReflogEntry entry1 = reverseEntries1.get(0);
assertEquals(1, reverseEntries1.size());
assertEquals(ObjectId.zeroId(), entry1.getOldId());
assertEquals(r.getObjectId(), entry1.getNewId());
- assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString());
+ assertEquals(new PersonIdent(db).toString(),
+ entry1.getWho().toString());
assertEquals("", entry1.getComment());
- List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
- .getReverseEntries();
+ List<ReflogEntry> reverseEntries2 = db.getRefDatabase()
+ .getReflogReader("HEAD").getReverseEntries();
assertEquals(0, reverseEntries2.size());
}
@@ -136,8 +138,11 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
final RefUpdate ru2 = updateRef(newRef2);
Result update2 = ru2.update();
assertEquals(Result.LOCK_FAILURE, update2);
- assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(1, refDb.getReflogReader("refs/heads/z")
+ .getReverseEntries().size());
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
@Test
@@ -147,8 +152,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
final RefUpdate ru = updateRef(newRef);
Result update = ru.update();
assertEquals(Result.LOCK_FAILURE, update);
- assertNull(db.getReflogReader("refs/heads/master/x"));
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertNull(refDb.getReflogReader("refs/heads/master/x"));
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
@Test
@@ -163,9 +170,12 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
final RefUpdate ru2 = updateRef(newRef2);
Result update2 = ru2.update();
assertEquals(Result.LOCK_FAILURE, update2);
- assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
- assertNull(db.getReflogReader("refs/heads/z"));
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(1, refDb.getReflogReader("refs/heads/z/a")
+ .getReverseEntries().size());
+ assertNull(refDb.getReflogReader("refs/heads/z"));
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
@Test
@@ -175,8 +185,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
final RefUpdate ru = updateRef(newRef);
Result update = ru.update();
assertEquals(Result.LOCK_FAILURE, update);
- assertNull(db.getReflogReader("refs/heads/prefix"));
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertNull(refDb.getReflogReader("refs/heads/prefix"));
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
/**
@@ -197,8 +209,11 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
Result delete = updateRef2.delete();
assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
assertEquals(pid, db.resolve("refs/heads/master"));
- assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
- assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(1, refDb.getReflogReader("refs/heads/master")
+ .getReverseEntries().size());
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
@Test
@@ -209,7 +224,8 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
updateRef.setForceUpdate(true);
Result update = updateRef.update();
assertEquals(Result.FORCED, update);
- assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
+ assertEquals(1, db.getRefDatabase().getReflogReader("refs/heads/master")
+ .getReverseEntries().size());
}
@Test
@@ -219,15 +235,18 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
ref.update(); // create loose ref
ref = updateRef(newRef); // refresh
delete(ref, Result.NO_CHANGE);
- assertNull(db.getReflogReader("refs/heads/abc"));
+ assertNull(db.getRefDatabase().getReflogReader("refs/heads/abc"));
}
@Test
public void testDeleteHead() throws IOException {
final RefUpdate ref = updateRef(Constants.HEAD);
delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
- assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(0, refDb.getReflogReader("refs/heads/master")
+ .getReverseEntries().size());
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
}
@Test
@@ -423,7 +442,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
// the branch HEAD referred to is left untouched
assertEquals(pid, db.resolve("refs/heads/master"));
- ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
ReflogEntry e = reflogReader.getReverseEntries().get(0);
assertEquals(pid, e.getOldId());
assertEquals(ppid, e.getNewId());
@@ -453,7 +472,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
// the branch HEAD referred to is left untouched
assertNull(db.resolve("refs/heads/unborn"));
- ReflogReader reflogReader = db.getReflogReader("HEAD");
+ ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
ReflogEntry e = reflogReader.getReverseEntries().get(0);
assertEquals(ObjectId.zeroId(), e.getOldId());
assertEquals(ppid, e.getNewId());
@@ -691,9 +710,12 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.RENAMED, result);
assertEquals(rb, db.resolve("refs/heads/new/name"));
assertNull(db.resolve("refs/heads/b"));
- assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size());
- assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
- .getLastEntry().getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(1, refDb.getReflogReader("refs/heads/new/name")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+ .getComment());
assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
}
@@ -713,11 +735,15 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.RENAMED, result);
assertEquals(rb, db.resolve("refs/heads/new/name"));
assertNull(db.resolve("refs/heads/b"));
- assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
- assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
- .getLastEntry().getComment());
- assertEquals("Just a message", db.getReflogReader("new/name")
- .getReverseEntries().get(1).getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(2, refDb.getReflogReader("refs/heads/new/name")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+ .getComment());
+ assertEquals("Just a message",
+ refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+ .get(1).getComment());
assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
}
@@ -737,13 +763,20 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.RENAMED, result);
assertEquals(rb, db.resolve("refs/heads/new/name"));
assertNull(db.resolve("refs/heads/b"));
- assertEquals("Branch: renamed b to new/name", db.getReflogReader(
- "new/name").getLastEntry().getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+ .getComment());
assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
assertEquals(rb, db.resolve(Constants.HEAD));
- assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
- assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment());
- assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment());
+ assertEquals(2, refDb.getReflogReader("refs/heads/new/name")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+ .get(0).getComment());
+ assertEquals("Just a message",
+ refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+ .get(1).getComment());
}
@Test
@@ -766,11 +799,17 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.RENAMED, result);
assertEquals(rb2, db.resolve("refs/heads/new/name"));
assertNull(db.resolve("refs/heads/b"));
- assertEquals("Branch: renamed b to new/name", db.getReflogReader(
- "new/name").getLastEntry().getComment());
- assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
- assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
- assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+ .getComment());
+ assertEquals(3, refDb.getReflogReader("refs/heads/new/name")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed b to new/name",
+ refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+ .get(0).getComment());
+ assertEquals(0,
+ refDb.getReflogReader("HEAD").getReverseEntries().size());
// make sure b's log file is gone too.
assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
@@ -789,9 +828,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
ObjectId oldfromId = db.resolve(fromName);
ObjectId oldHeadId = db.resolve(Constants.HEAD);
writeReflog(db, oldfromId, "Just a message", fromName);
- List<ReflogEntry> oldFromLog = db
+ RefDatabase refDb = db.getRefDatabase();
+ List<ReflogEntry> oldFromLog = refDb
.getReflogReader(fromName).getReverseEntries();
- List<ReflogEntry> oldHeadLog = oldHeadId != null ? db
+ List<ReflogEntry> oldHeadLog = oldHeadId != null ? refDb
.getReflogReader(Constants.HEAD).getReverseEntries() : null;
assertTrue("internal check, we have a log", new File(db.getDirectory(),
@@ -818,10 +858,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(oldHeadId, db.resolve(Constants.HEAD));
assertEquals(oldfromId, db.resolve(fromName));
assertNull(db.resolve(toName));
- assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
+ assertEquals(oldFromLog.toString(), refDb.getReflogReader(fromName)
.getReverseEntries().toString());
if (oldHeadId != null && oldHeadLog != null)
- assertEquals(oldHeadLog.toString(), db.getReflogReader(
+ assertEquals(oldHeadLog.toString(), refDb.getReflogReader(
Constants.HEAD).getReverseEntries().toString());
} finally {
lockFile.unlock();
@@ -942,15 +982,18 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.RENAMED, result);
assertNull(db.resolve("refs/heads/a"));
assertEquals(rb, db.resolve("refs/heads/a/b"));
- assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size());
- assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b")
- .getReverseEntries().get(0).getComment());
- assertEquals("Just a message", db.getReflogReader("a/b")
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(3, refDb.getReflogReader("refs/heads/a/b")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed a to a/b",
+ refDb.getReflogReader("refs/heads/a/b").getReverseEntries()
+ .get(0).getComment());
+ assertEquals("Just a message", refDb.getReflogReader("refs/heads/a/b")
.getReverseEntries().get(1).getComment());
- assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
- .get(2).getComment());
+ assertEquals("Setup", refDb.getReflogReader("refs/heads/a/b")
+ .getReverseEntries().get(2).getComment());
// same thing was logged to HEAD
- assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
+ assertEquals("Branch: renamed a to a/b", refDb.getReflogReader("HEAD")
.getReverseEntries().get(0).getComment());
}
@@ -978,15 +1021,20 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertNull(db.resolve("refs/heads/prefix/a"));
assertEquals(rb, db.resolve("refs/heads/prefix"));
- assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size());
- assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
- "prefix").getReverseEntries().get(0).getComment());
- assertEquals("Just a message", db.getReflogReader("prefix")
- .getReverseEntries().get(1).getComment());
- assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
- .get(2).getComment());
- assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
- "HEAD").getReverseEntries().get(0).getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(3, refDb.getReflogReader("refs/heads/prefix")
+ .getReverseEntries().size());
+ assertEquals("Branch: renamed prefix/a to prefix",
+ refDb.getReflogReader("refs/heads/prefix").getReverseEntries()
+ .get(0).getComment());
+ assertEquals("Just a message",
+ refDb.getReflogReader("refs/heads/prefix").getReverseEntries()
+ .get(1).getComment());
+ assertEquals("Setup", refDb.getReflogReader("refs/heads/prefix")
+ .getReverseEntries().get(2).getComment());
+ assertEquals("Branch: renamed prefix/a to prefix",
+ refDb.getReflogReader("HEAD").getReverseEntries().get(0)
+ .getComment());
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
index eb521ff9eb..16645cbcd7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
@@ -27,6 +27,7 @@ import org.eclipse.jgit.lib.CheckoutEntry;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
@@ -154,18 +155,22 @@ public class ReflogReaderTest extends SampleDataRepositoryTestCase {
setupReflog("logs/refs/heads/a", aLine);
setupReflog("logs/refs/heads/master", masterLine);
setupReflog("logs/HEAD", headLine);
- assertEquals("branch: change to master", db.getReflogReader("master")
- .getLastEntry().getComment());
- assertEquals("branch: change to a", db.getReflogReader("a")
- .getLastEntry().getComment());
- assertEquals("branch: change to HEAD", db.getReflogReader("HEAD")
- .getLastEntry().getComment());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals("branch: change to master",
+ refDb.getReflogReader("refs/heads/master").getLastEntry()
+ .getComment());
+ assertEquals("branch: change to a",
+ refDb.getReflogReader("refs/heads/a").getLastEntry()
+ .getComment());
+ assertEquals("branch: change to HEAD",
+ refDb.getReflogReader("HEAD").getLastEntry().getComment());
}
@Test
public void testReadLineWithMissingComment() throws Exception {
setupReflog("logs/refs/heads/master", oneLineWithoutComment);
- final ReflogReader reader = db.getReflogReader("master");
+ final ReflogReader reader = db.getRefDatabase()
+ .getReflogReader("refs/heads/master");
ReflogEntry e = reader.getLastEntry();
assertEquals(ObjectId
.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e
@@ -183,15 +188,18 @@ public class ReflogReaderTest extends SampleDataRepositoryTestCase {
@Test
public void testNoLog() throws Exception {
- assertEquals(0, db.getReflogReader("master").getReverseEntries().size());
- assertNull(db.getReflogReader("master").getLastEntry());
+ RefDatabase refDb = db.getRefDatabase();
+ assertEquals(0,
+ refDb.getReflogReader("refs/heads/master").getReverseEntries()
+ .size());
+ assertNull(refDb.getReflogReader("refs/heads/master").getLastEntry());
}
@Test
public void testCheckout() throws Exception {
setupReflog("logs/HEAD", switchBranch);
- List<ReflogEntry> entries = db.getReflogReader(Constants.HEAD)
- .getReverseEntries();
+ List<ReflogEntry> entries = db.getRefDatabase()
+ .getReflogReader(Constants.HEAD).getReverseEntries();
assertEquals(1, entries.size());
ReflogEntry entry = entries.get(0);
CheckoutEntry checkout = entry.parseCheckout();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexBuilderTest.java
new file mode 100644
index 0000000000..e6fefc623d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexBuilderTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexLoader.MultiPackIndexBuilder;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexLoader.MultiPackIndexFormatException;
+import org.junit.Test;
+
+public class MultiPackIndexBuilderTest {
+
+ @Test
+ public void testRepeatedChunk() throws Exception {
+ byte[] buffer = new byte[2048];
+
+ MultiPackIndexBuilder builder1 = MultiPackIndexBuilder.builder();
+ builder1.addOidFanout(buffer);
+ Exception e1 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder1.addOidFanout(buffer);
+ });
+ assertEquals("midx chunk id 0x4f494446 appears multiple times",
+ e1.getMessage());
+
+ MultiPackIndexBuilder builder2 = MultiPackIndexBuilder.builder();
+ builder2.addOidLookUp(buffer);
+ Exception e2 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder2.addOidLookUp(buffer);
+ });
+ assertEquals("midx chunk id 0x4f49444c appears multiple times",
+ e2.getMessage());
+
+ MultiPackIndexBuilder builder3 = MultiPackIndexBuilder.builder();
+ builder3.addObjectOffsets(buffer);
+ Exception e3 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder3.addObjectOffsets(buffer);
+ });
+ assertEquals("midx chunk id 0x4f4f4646 appears multiple times",
+ e3.getMessage());
+
+ MultiPackIndexBuilder builder4 = MultiPackIndexBuilder.builder();
+ builder4.addPackNames(buffer);
+ Exception e4 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder4.addPackNames(buffer);
+ });
+ assertEquals("midx chunk id 0x504e414d appears multiple times",
+ e4.getMessage());
+
+ MultiPackIndexBuilder builder5 = MultiPackIndexBuilder.builder();
+ builder5.addBitmappedPacks(buffer);
+ Exception e5 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder5.addBitmappedPacks(buffer);
+ });
+ assertEquals("midx chunk id 0x42544d50 appears multiple times",
+ e5.getMessage());
+
+ MultiPackIndexBuilder builder6 = MultiPackIndexBuilder.builder();
+ builder6.addObjectLargeOffsets(buffer);
+ Exception e6 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder6.addObjectLargeOffsets(buffer);
+ });
+ assertEquals("midx chunk id 0x4c4f4646 appears multiple times",
+ e6.getMessage());
+
+ MultiPackIndexBuilder builder7 = MultiPackIndexBuilder.builder();
+ builder7.addReverseIndex(buffer);
+ Exception e7 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ builder7.addReverseIndex(buffer);
+ });
+ assertEquals("midx chunk id 0x52494458 appears multiple times",
+ e7.getMessage());
+ }
+
+ @Test
+ public void testNeededChunk() {
+ byte[] buffer = new byte[2048];
+
+ Exception e1 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ MultiPackIndexBuilder.builder().addOidLookUp(buffer).build();
+ });
+ assertEquals("midx 0x4f494446 chunk has not been loaded",
+ e1.getMessage());
+
+ Exception e2 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ MultiPackIndexBuilder.builder().addOidFanout(buffer)
+ .addOidLookUp(buffer).build();
+ });
+ assertEquals("midx 0x504e414d chunk has not been loaded",
+ e2.getMessage());
+
+ Exception e3 = assertThrows(MultiPackIndexFormatException.class, () -> {
+ MultiPackIndexBuilder.builder().addOidFanout(buffer)
+ .addOidLookUp(buffer).addPackNames(buffer).build();
+ });
+ assertEquals("midx 0x4f4f4646 chunk has not been loaded",
+ e3.getMessage());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java
new file mode 100644
index 0000000000..494f1d1137
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.midx;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.junit.Test;
+
+/**
+ * Test that the loader accepts valid files, discard broken files
+ * <p>
+ * Contents and lookups are covered in the MultiPackIndexTest
+ */
+public class MultiPackIndexLoaderTest {
+
+ @Test
+ public void load_validFile_basic_upstream() throws Exception {
+ MultiPackIndex midx = MultiPackIndexLoader
+ .open(JGitTestUtil.getTestResourceFile("multi-pack-index.v1"));
+ assertNotNull(midx);
+ }
+
+ @Test
+ public void load_validFile_basic_jgit() throws Exception {
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000001", 500),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000005", 12),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000010", 1500)));
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000002", 501),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000003", 13),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000015", 1501)));
+ PackIndex idxThree = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000004", 502),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000007", 14),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000012", 1502)));
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo, "p3",
+ idxThree);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+ assertNotNull(midx);
+ }
+
+ @Test
+ public void load_emptyFile() {
+ assertThrows(IOException.class, () -> MultiPackIndexLoader
+ .read(new ByteArrayInputStream(new byte[0])));
+ }
+
+ @Test
+ public void load_rubbishFile() {
+ assertThrows(MultiPackIndexLoader.MultiPackIndexFormatException.class,
+ () -> MultiPackIndexLoader.read(new ByteArrayInputStream(
+ "More than 12 bytes of not-midx".getBytes(UTF_8))));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java
new file mode 100644
index 0000000000..ab452854b2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class MultiPackIndexTest {
+
+ @Test
+ public void basic_upstream() throws IOException {
+ int knownPackId = 22;
+ int knowOffset = 258;
+ String knownOid = "3f4ee50f784c1e9550f09a67d2ffc1bc76917bdc";
+ String knownPackName = "pack-e4b191e4343f2b7ff851026c2d8595a001077344.idx";
+ String[] packNames = {
+ "pack-15d67b35f2b6a66ff995e09cedb36b101e0e0262.idx",
+ "pack-1a979514a5965e71523187a17806e03af44344ed.idx",
+ "pack-1de6731c035633ba8f5b41dacbc680a5a36ddd90.idx",
+ "pack-1ee98948e4e362c56f3cdec7f5837d06e152854f.idx",
+ "pack-1f6fe52ac3d33f3091d8eb8497474554bfa80bc4.idx",
+ "pack-34b1aa6b437a9d968412454204c2676a88dc55fa.idx",
+ "pack-3b245f7b4aff32a52d0520608f662bbf403792b9.idx",
+ "pack-47901f7f8d1c440492035c4165796a330c7f79e0.idx",
+ "pack-4e7f889b79aea8905a0062ce1bd68e5ef3af6a55.idx",
+ "pack-71ea652e4aea2cbc609545b4fbc3eda6325d88a1.idx",
+ "pack-723b1238411a4257c18167e91fbabed313ba332f.idx",
+ "pack-7bd57092a7daa4dc31277e1ec86f3de8d968ae17.idx",
+ "pack-883d4f469c5ea0f6d373ee623a758aeaf17715fc.idx",
+ "pack-8eadd378a011ddaa5ec751f2a6d9789ef501120f.idx",
+ "pack-92221b6f79a211944ccc6740fc22c9553ea1ba22.idx",
+ "pack-b139d0cae5f54c70d057a8f4d2cf99f0ae0c326c.idx",
+ "pack-b4f5c96d1fa6b1fac17a2a43710693c5514a9224.idx",
+ "pack-bed4bc1521f965e55a5a8a58dffaaefc70ea4753.idx",
+ "pack-cdc6baa7d90707a3c0dac4c188f797f0f79b97bb.idx",
+ "pack-d6d58a58fa24b74c8c082f4f63c4d2ddfb824cc9.idx",
+ "pack-daec59ae07f1091f3b81bd8266481bb5db3c868a.idx",
+ "pack-e2197d60e09ad9091407eff4e06d39ec940851e1.idx",
+ "pack-e4b191e4343f2b7ff851026c2d8595a001077344.idx",
+ "pack-eedf783b5da4caa57be33b08990fe57f245a7413.idx",
+ "pack-efb23e968801b9050bc70f0115a8a0eec88fb879.idx",
+ "pack-f919c0660c207ddf6bb0569a3041d682d19fb4f7.idx" };
+ MultiPackIndex midx = MultiPackIndexLoader
+ .open(JGitTestUtil.getTestResourceFile("multi-pack-index.v1"));
+ assertNotNull(midx);
+ assertArrayEquals(packNames, midx.getPackNames());
+
+ MultiPackIndex.PackOffset oo = midx.find(ObjectId.fromString(knownOid));
+
+ assertEquals(knowOffset, oo.getOffset());
+ assertEquals(knownPackId, oo.getPackId());
+ assertEquals(knownPackName, midx.getPackNames()[oo.getPackId()]);
+ }
+
+ @Test
+ public void basicMidx() throws IOException {
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000001", 500),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000005", 12),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000010", 1500)));
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000002", 501),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000003", 13),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000015", 1501)));
+ PackIndex idxThree = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000004", 502),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000007", 14),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000012", 1502)));
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo, "p3",
+ idxThree);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+ assertEquals(3, midx.getPackNames().length);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000001", 500);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000005", 12);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000010",
+ 1500);
+ assertInIndex(midx, 1, "0000000000000000000000000000000000000002", 501);
+ assertInIndex(midx, 1, "0000000000000000000000000000000000000003", 13);
+ assertInIndex(midx, 1, "0000000000000000000000000000000000000015",
+ 1501);
+ assertInIndex(midx, 2, "0000000000000000000000000000000000000004", 502);
+ assertInIndex(midx, 2, "0000000000000000000000000000000000000007", 14);
+ assertInIndex(midx, 2, "0000000000000000000000000000000000000012",
+ 1502);
+
+ assertNull(midx.find(ObjectId.zeroId()));
+ }
+
+ @Test
+ public void jgit_largeOffsetChunk() throws IOException {
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000001", (1L << 34)),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000005", 12)));
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000002", (1L << 35)),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000003", 13)));
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+ assertEquals(2, midx.getPackNames().length);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000001",
+ (1L << 34));
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000005", 12);
+ assertInIndex(midx, 1, "0000000000000000000000000000000000000002",
+ (1L << 35));
+ }
+
+ @Test
+ public void jgit_largeOffset_noChunk() throws IOException {
+ // All offsets fit in 32 bits, no large offset chunk
+ // Most significant bit to 1 is still valid offset
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000001",
+ 0xff00_0000),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000005", 12)));
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of(
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000002", 501),
+ new FakeIndexFactory.IndexObject(
+ "0000000000000000000000000000000000000003", 13)));
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+ assertEquals(2, midx.getPackNames().length);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000001",
+ 0xff00_0000L);
+ assertInIndex(midx, 0, "0000000000000000000000000000000000000005", 12);
+ }
+
+ @Test
+ public void jgit_resolve() throws IOException {
+ AbbreviatedObjectId abbrev = AbbreviatedObjectId
+ .fromString("32fe829a1c");
+
+ PackIndex idxOne = indexWith(
+ // Noise
+ "0000000000000000000000000000000000000001",
+ "3000000000000000000000000000000000000005",
+ // One before abbrev
+ "32fe829a1b000000000000000000000000000001",
+ // matches
+ "32fe829a1c000000000000000000000000000001",
+ "32fe829a1c000000000000000000000000000100",
+ // One after abbrev
+ "32fe829a1d000000000000000000000000000000");
+ PackIndex idxTwo = indexWith(
+ // Noise
+ "8888880000000000000000000000000000000002",
+ "bbbbbb0000000000000000000000000000000003",
+ // Match
+ "32fe829a1c000000000000000000000000000010");
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+
+
+ Set<ObjectId> results = new HashSet<>();
+ midx.resolve(results, abbrev, 100);
+
+ assertEquals(3, results.size());
+ assertTrue(results.contains(ObjectId
+ .fromString("32fe829a1c000000000000000000000000000001")));
+ assertTrue(results.contains(ObjectId
+ .fromString("32fe829a1c000000000000000000000000000010")));
+ assertTrue(results.contains(ObjectId
+ .fromString("32fe829a1c000000000000000000000000000100")));
+
+ }
+
+ @Test
+ public void jgit_resolve_matchLimit() throws IOException {
+ AbbreviatedObjectId abbrev = AbbreviatedObjectId
+ .fromString("32fe829a1c");
+
+ PackIndex idxOne = indexWith(
+ // Noise
+ "0000000000000000000000000000000000000001",
+ "3000000000000000000000000000000000000005",
+ // One before abbrev
+ "32fe829a1b000000000000000000000000000001",
+ // matches
+ "32fe829a1c000000000000000000000000000001",
+ "32fe829a1c000000000000000000000000000100",
+ // One after abbrev
+ "32fe829a1d000000000000000000000000000000");
+ PackIndex idxTwo = indexWith(
+ // Noise
+ "8888880000000000000000000000000000000002",
+ "bbbbbb0000000000000000000000000000000003",
+ // Match
+ "32fe829a1c000000000000000000000000000010");
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+
+
+ Set<ObjectId> results = new HashSet<>();
+ midx.resolve(results, abbrev, 2);
+
+ assertEquals(2, results.size());
+ assertTrue(results.contains(ObjectId
+ .fromString("32fe829a1c000000000000000000000000000001")));
+ assertTrue(results.contains(ObjectId
+ .fromString("32fe829a1c000000000000000000000000000010")));
+ }
+
+ @Test
+ public void jgit_resolve_noMatches() throws IOException {
+ AbbreviatedObjectId abbrev = AbbreviatedObjectId
+ .fromString("4400000000");
+
+ PackIndex idxOne = indexWith(
+ "0000000000000000000000000000000000000001",
+ "3000000000000000000000000000000000000005",
+ "32fe829a1b000000000000000000000000000001",
+ "32fe829a1c000000000000000000000000000001",
+ "32fe829a1c000000000000000000000000000100",
+ "32fe829a1d000000000000000000000000000000");
+ PackIndex idxTwo = indexWith(
+ // Noise
+ "8888880000000000000000000000000000000002",
+ "bbbbbb0000000000000000000000000000000003",
+ "32fe829a1c000000000000000000000000000010");
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+
+
+ Set<ObjectId> results = new HashSet<>();
+ midx.resolve(results, abbrev, 200);
+
+ assertEquals(0, results.size());
+ }
+
+ @Test
+ public void jgit_resolve_empty() throws IOException {
+ AbbreviatedObjectId abbrev = AbbreviatedObjectId
+ .fromString("4400000000");
+
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
+
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(new ByteArrayInputStream(out.toByteArray()));
+
+
+ Set<ObjectId> results = new HashSet<>();
+ midx.resolve(results, abbrev, 200);
+
+ assertEquals(0, results.size());
+ }
+
+ private static PackIndex indexWith(String... oids) {
+ List<FakeIndexFactory.IndexObject> idxObjs = new ArrayList<>(
+ oids.length);
+ int offset = 12;
+ for (String oid : oids) {
+ idxObjs.add(new FakeIndexFactory.IndexObject(oid, offset));
+ offset += 10;
+ }
+ return FakeIndexFactory.indexOf(idxObjs);
+ }
+
+ private static void assertInIndex(MultiPackIndex midx, int expectedPackId,
+ String oid, long expectedOffset) {
+ MultiPackIndex.PackOffset packOffset = midx
+ .find(ObjectId.fromString(oid));
+ assertNotNull(packOffset);
+ assertEquals("Wrong packId for " + oid, expectedPackId,
+ packOffset.getPackId());
+ assertEquals(expectedOffset, packOffset.getOffset());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
new file mode 100644
index 0000000000..8b57a2dcb4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_LARGEOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_REVINDEX;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.FakeIndexFactory.IndexObject;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.util.NB;
+import org.junit.Test;
+
+public class MultiPackIndexWriterTest {
+
+ @Test
+ public void write_allSmallOffsets() throws IOException {
+ PackIndex index1 = indexOf(
+ object("0000000000000000000000000000000000000001", 500),
+ object("0000000000000000000000000000000000000003", 1500),
+ object("0000000000000000000000000000000000000005", 3000));
+ PackIndex index2 = indexOf(
+ object("0000000000000000000000000000000000000002", 500),
+ object("0000000000000000000000000000000000000004", 1500),
+ object("0000000000000000000000000000000000000006", 3000));
+
+ Map<String, PackIndex> data = Map.of("packname1", index1, "packname2",
+ index2);
+
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, data);
+ // header (12 bytes)
+ // + chunkHeader (6 * 12 bytes)
+ // + fanout table (256 * 4 bytes)
+ // + OIDs (6 * 20 bytes)
+ // + (pack, offset) pairs (6 * 8)
+ // + RIDX (6 * 4 bytes)
+ // + packfile names (2 * 10)
+ // + checksum (20)
+ assertEquals(1340, out.size());
+ List<Integer> chunkIds = readChunkIds(out);
+ assertEquals(5, chunkIds.size());
+ assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+ assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+ assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+ assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+ assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+ }
+
+ @Test
+ public void write_smallOffset_limit() throws IOException {
+ PackIndex index1 = indexOf(
+ object("0000000000000000000000000000000000000001", 500),
+ object("0000000000000000000000000000000000000003", 1500),
+ object("0000000000000000000000000000000000000005", (1L << 32) -1));
+ PackIndex index2 = indexOf(
+ object("0000000000000000000000000000000000000002", 500),
+ object("0000000000000000000000000000000000000004", 1500),
+ object("0000000000000000000000000000000000000006", 3000));
+ Map<String, PackIndex> data =
+ Map.of("packname1", index1, "packname2", index2);
+
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, data);
+ // header (12 bytes)
+ // + chunkHeader (6 * 12 bytes)
+ // + fanout table (256 * 4 bytes)
+ // + OIDs (6 * 20 bytes)
+ // + (pack, offset) pairs (6 * 8)
+ // + RIDX (6 * 4 bytes)
+ // + packfile names (2 * 10)
+ // + checksum (20)
+ assertEquals(1340, out.size());
+ List<Integer> chunkIds = readChunkIds(out);
+ assertEquals(5, chunkIds.size());
+ assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+ assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+ assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+ assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+ assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+ }
+
+ @Test
+ public void write_largeOffset() throws IOException {
+ PackIndex index1 = indexOf(
+ object("0000000000000000000000000000000000000001", 500),
+ object("0000000000000000000000000000000000000003", 1500),
+ object("0000000000000000000000000000000000000005", 1L << 32));
+ PackIndex index2 = indexOf(
+ object("0000000000000000000000000000000000000002", 500),
+ object("0000000000000000000000000000000000000004", 1500),
+ object("0000000000000000000000000000000000000006", 3000));
+ Map<String, PackIndex> data =
+ Map.of("packname1", index1, "packname2", index2);
+
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, data);
+ // header (12 bytes)
+ // + chunkHeader (7 * 12 bytes)
+ // + fanout table (256 * 4 bytes)
+ // + OIDs (6 * 20 bytes)
+ // + (pack, offset) pairs (6 * 8)
+ // + (large-offset) (1 * 8)
+ // + RIDX (6 * 4 bytes)
+ // + packfile names (2 * 10)
+ // + checksum (20)
+ assertEquals(1360, out.size());
+ List<Integer> chunkIds = readChunkIds(out);
+ assertEquals(6, chunkIds.size());
+ assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+ assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+ assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+ assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_LARGEOFFSETS));
+ assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+ assertEquals(5, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+ }
+
+ @Test
+ public void jgit_emptyMidx() throws IOException {
+ PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
+ PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
+ Map<String, PackIndex> packs = Map.of("p1", idxOne, "p2", idxTwo);
+ MultiPackIndexWriter writer = new MultiPackIndexWriter();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.write(NullProgressMonitor.INSTANCE, out, packs);
+ List<Integer> chunkIds = readChunkIds(out);
+ assertEquals(1134, out.size());
+ assertEquals(5, chunkIds.size());
+ assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+ assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+ assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+ assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+ assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+ }
+
+ private List<Integer> readChunkIds(ByteArrayOutputStream out) {
+ List<Integer> chunkIds = new ArrayList<>();
+ byte[] raw = out.toByteArray();
+ int numChunks = raw[6];
+ int position = 12;
+ for (int i = 0; i < numChunks; i++) {
+ chunkIds.add(NB.decodeInt32(raw, position));
+ position += CHUNK_LOOKUP_WIDTH;
+ }
+ return chunkIds;
+ }
+
+ private static PackIndex indexOf(IndexObject... objs) {
+ return FakeIndexFactory.indexOf(Arrays.asList(objs));
+ }
+
+ private static IndexObject object(String name, long offset) {
+ return new IndexObject(name, offset);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
new file mode 100644
index 0000000000..8218cbc20d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.FakeIndexFactory.IndexObject;
+import org.junit.Test;
+
+public class PackIndexMergerTest {
+
+ @Test
+ public void rawIterator_noDuplicates() {
+ PackIndex idxOne = indexOf(
+ oidOffset("0000000000000000000000000000000000000001", 500),
+ oidOffset("0000000000000000000000000000000000000005", 12),
+ oidOffset("0000000000000000000000000000000000000010", 1500));
+ PackIndex idxTwo = indexOf(
+ oidOffset("0000000000000000000000000000000000000002", 501),
+ oidOffset("0000000000000000000000000000000000000003", 13),
+ oidOffset("0000000000000000000000000000000000000015", 1501));
+ PackIndex idxThree = indexOf(
+ oidOffset("0000000000000000000000000000000000000004", 502),
+ oidOffset("0000000000000000000000000000000000000007", 14),
+ oidOffset("0000000000000000000000000000000000000012", 1502));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+ assertEquals(9, merger.getUniqueObjectCount());
+ assertEquals(3, merger.getPackCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
+ assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
+ assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 502);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 14);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+ 1500);
+ assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+ 1502);
+ assertNextEntry(it, "0000000000000000000000000000000000000015", 1,
+ 1501);
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void rawIterator_allDuplicates() {
+ PackIndex idxOne = indexOf(
+ oidOffset("0000000000000000000000000000000000000001", 500),
+ oidOffset("0000000000000000000000000000000000000005", 12),
+ oidOffset("0000000000000000000000000000000000000010", 1500));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idxOne, "p2", idxOne, "p3", idxOne));
+ assertEquals(3, merger.getUniqueObjectCount());
+ assertEquals(3, merger.getPackCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 1, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 2, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 1, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 2, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+ 1500);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 1,
+ 1500);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 2,
+ 1500);
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void bySha1Iterator_noDuplicates() {
+ PackIndex idxOne = indexOf(
+ oidOffset("0000000000000000000000000000000000000001", 500),
+ oidOffset("0000000000000000000000000000000000000005", 12),
+ oidOffset("0000000000000000000000000000000000000010", 1500));
+ PackIndex idxTwo = indexOf(
+ oidOffset("0000000000000000000000000000000000000002", 501),
+ oidOffset("0000000000000000000000000000000000000003", 13),
+ oidOffset("0000000000000000000000000000000000000015", 1501));
+ PackIndex idxThree = indexOf(
+ oidOffset("0000000000000000000000000000000000000004", 502),
+ oidOffset("0000000000000000000000000000000000000007", 14),
+ oidOffset("0000000000000000000000000000000000000012", 1502));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+ assertEquals(9, merger.getUniqueObjectCount());
+ assertEquals(3, merger.getPackCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
+ assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
+ assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 502);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 14);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+ 1500);
+ assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+ 1502);
+ assertNextEntry(it, "0000000000000000000000000000000000000015", 1,
+ 1501);
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void bySha1Iterator_allDuplicates() {
+ PackIndex idxOne = indexOf(
+ oidOffset("0000000000000000000000000000000000000001", 500),
+ oidOffset("0000000000000000000000000000000000000005", 12),
+ oidOffset("0000000000000000000000000000000000000010", 1500));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idxOne, "p2", idxOne, "p3", idxOne));
+ assertEquals(3, merger.getUniqueObjectCount());
+ assertEquals(3, merger.getPackCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+ assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+ 1500);
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void bySha1Iterator_differentIndexSizes() {
+ PackIndex idxOne = indexOf(
+ oidOffset("0000000000000000000000000000000000000010", 1500));
+ PackIndex idxTwo = indexOf(
+ oidOffset("0000000000000000000000000000000000000002", 500),
+ oidOffset("0000000000000000000000000000000000000003", 12));
+ PackIndex idxThree = indexOf(
+ oidOffset("0000000000000000000000000000000000000004", 500),
+ oidOffset("0000000000000000000000000000000000000007", 12),
+ oidOffset("0000000000000000000000000000000000000012", 1500));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+ assertEquals(6, merger.getUniqueObjectCount());
+ assertEquals(3, merger.getPackCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+ assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 500);
+ assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 12);
+ assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+ 1500);
+ assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+ 1500);
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void merger_noIndexes() {
+ PackIndexMerger merger = new PackIndexMerger(Map.of());
+ assertEquals(0, merger.getUniqueObjectCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ assertTrue(merger.getPackNames().isEmpty());
+ assertEquals(0, merger.getPackCount());
+ assertFalse(merger.bySha1Iterator().hasNext());
+ }
+
+ @Test
+ public void merger_emptyIndexes() {
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", indexOf(), "p2", indexOf()));
+ assertEquals(0, merger.getUniqueObjectCount());
+ assertFalse(merger.needsLargeOffsetsChunk());
+ assertEquals(2, merger.getPackNames().size());
+ assertEquals(2, merger.getPackCount());
+ assertFalse(merger.bySha1Iterator().hasNext());
+ }
+
+ @Test
+ public void bySha1Iterator_largeOffsets_needsChunk() {
+ PackIndex idx1 = indexOf(
+ oidOffset("0000000000000000000000000000000000000002", 1L << 32),
+ oidOffset("0000000000000000000000000000000000000004", 12));
+ PackIndex idx2 = indexOf(oidOffset(
+ "0000000000000000000000000000000000000003", (1L << 31) + 10));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idx1, "p2", idx2));
+ assertTrue(merger.needsLargeOffsetsChunk());
+ assertEquals(2, merger.getOffsetsOver31BitsCount());
+ assertEquals(3, merger.getUniqueObjectCount());
+ }
+
+ @Test
+ public void bySha1Iterator_largeOffsets_noChunk() {
+ // If no value is over 2^32-1, then we don't need large offset
+ PackIndex idx1 = indexOf(
+ oidOffset("0000000000000000000000000000000000000002",
+ (1L << 31) + 15),
+ oidOffset("0000000000000000000000000000000000000004", 12));
+ PackIndex idx2 = indexOf(oidOffset(
+ "0000000000000000000000000000000000000003", (1L << 31) + 10));
+ PackIndexMerger merger = new PackIndexMerger(
+ Map.of("p1", idx1, "p2", idx2));
+ assertFalse(merger.needsLargeOffsetsChunk());
+ assertEquals(2, merger.getOffsetsOver31BitsCount());
+ assertEquals(3, merger.getUniqueObjectCount());
+ }
+
+ private static void assertNextEntry(
+ Iterator<PackIndexMerger.MidxMutableEntry> it, String oid,
+ int packId, long offset) {
+ assertTrue(it.hasNext());
+ PackIndexMerger.MidxMutableEntry e = it.next();
+ assertEquals(oid, e.getObjectId().name());
+ assertEquals(packId, e.getPackId());
+ assertEquals(offset, e.getOffset());
+ }
+
+ private static IndexObject oidOffset(String oid, long offset) {
+ return new IndexObject(oid, offset);
+ }
+
+ private static PackIndex indexOf(IndexObject... objs) {
+ return FakeIndexFactory.indexOf(Arrays.asList(objs));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
new file mode 100644
index 0000000000..0b3ccacfc1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.junit.Test;
+
+public class PackIndexPeekIteratorTest {
+ @Test
+ public void next() {
+ PackIndex index1 = indexOf(
+ object("0000000000000000000000000000000000000001", 500),
+ object("0000000000000000000000000000000000000003", 1500),
+ object("0000000000000000000000000000000000000005", 3000));
+ PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+ assertEquals("0000000000000000000000000000000000000001", it.next().name());
+ assertEquals("0000000000000000000000000000000000000003", it.next().name());
+ assertEquals("0000000000000000000000000000000000000005", it.next().name());
+ assertNull(it.next());
+ }
+
+ @Test
+ public void peek_doesNotAdvance() {
+ PackIndex index1 = indexOf(
+ object("0000000000000000000000000000000000000001", 500),
+ object("0000000000000000000000000000000000000003", 1500),
+ object("0000000000000000000000000000000000000005", 3000));
+ PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+ it.next();
+ assertEquals("0000000000000000000000000000000000000001", it.peek().name());
+ assertEquals("0000000000000000000000000000000000000001", it.peek().name());
+ it.next();
+ assertEquals("0000000000000000000000000000000000000003", it.peek().name());
+ assertEquals("0000000000000000000000000000000000000003", it.peek().name());
+ it.next();
+ assertEquals("0000000000000000000000000000000000000005", it.peek().name());
+ assertEquals("0000000000000000000000000000000000000005", it.peek().name());
+ it.next();
+ assertNull(it.peek());
+ assertNull(it.peek());
+ }
+
+ @Test
+ public void empty() {
+ PackIndex index1 = indexOf();
+ PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+ assertNull(it.next());
+ assertNull(it.peek());
+ }
+
+ private static PackIndex indexOf(FakeIndexFactory.IndexObject... objs) {
+ return FakeIndexFactory.indexOf(Arrays.asList(objs));
+ }
+
+ private static FakeIndexFactory.IndexObject object(String name, long offset) {
+ return new FakeIndexFactory.IndexObject(name, offset);
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java
index 6fc7f25475..62dbda47fd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java
@@ -19,6 +19,8 @@ import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -27,6 +29,7 @@ import org.eclipse.jgit.internal.storage.io.BlockSource;
import org.eclipse.jgit.internal.storage.reftable.ReftableWriter.Stats;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.junit.Test;
@@ -279,6 +282,95 @@ public class ReftableCompactorTest {
}
}
+ @Test
+ public void reflog_all() throws IOException {
+ byte[] inTab;
+ try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) {
+ ReftableWriter writer = new ReftableWriter(inBuf)
+ .setMinUpdateIndex(0).setMaxUpdateIndex(2).begin();
+ writer.writeLog(MASTER, 2, person(Instant.ofEpochSecond(500)),
+ id(3), id(4), null);
+ writer.writeLog(MASTER, 1, person(Instant.ofEpochSecond(300)),
+ id(2), id(3), null);
+ writer.writeLog(MASTER, 0, person(Instant.ofEpochSecond(100)),
+ id(1), id(2), null);
+ writer.finish();
+ inTab = inBuf.toByteArray();
+ }
+
+ ReftableCompactor compactor;
+ try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) {
+ compactor = new ReftableCompactor(outBuf);
+ // No setReflogExpire time is set
+ List<ReftableReader> readers = new ArrayList<>();
+ readers.add(read(inTab));
+ compactor.addAll(readers);
+ compactor.compact();
+ }
+ Stats stats = compactor.getStats();
+ assertEquals(3, stats.logCount());
+ }
+
+ @Test
+ public void reflog_setExpireOlderThan() throws IOException {
+ byte[] inTab;
+ try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) {
+ ReftableWriter writer = new ReftableWriter(inBuf)
+ .setMinUpdateIndex(0).setMaxUpdateIndex(2).begin();
+ writer.writeLog(MASTER, 2, person(Instant.ofEpochSecond(500)),
+ id(3), id(4), null);
+ writer.writeLog(MASTER, 1, person(Instant.ofEpochSecond(300)),
+ id(2), id(3), null);
+ writer.writeLog(MASTER, 0, person(Instant.ofEpochSecond(100)),
+ id(1), id(2), null);
+ writer.finish();
+ inTab = inBuf.toByteArray();
+ }
+
+ ReftableCompactor compactor;
+ try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) {
+ compactor = new ReftableCompactor(outBuf);
+ compactor.setReflogExpireOlderThan(Instant.ofEpochSecond(300));
+ List<ReftableReader> readers = new ArrayList<>();
+ readers.add(read(inTab));
+ compactor.addAll(readers);
+ compactor.compact();
+ }
+
+ Stats stats = compactor.getStats();
+ assertEquals(2, stats.logCount());
+ }
+
+ @Test
+ public void reflog_disable() throws IOException {
+ byte[] inTab;
+ try (ByteArrayOutputStream inBuf = new ByteArrayOutputStream()) {
+ ReftableWriter writer = new ReftableWriter(inBuf)
+ .setMinUpdateIndex(0).setMaxUpdateIndex(2).begin();
+ writer.writeLog(MASTER, 2, person(Instant.ofEpochSecond(500)),
+ id(3), id(4), null);
+ writer.writeLog(MASTER, 1, person(Instant.ofEpochSecond(300)),
+ id(2), id(3), null);
+ writer.writeLog(MASTER, 0, person(Instant.ofEpochSecond(100)),
+ id(1), id(2), null);
+ writer.finish();
+ inTab = inBuf.toByteArray();
+ }
+
+ ReftableCompactor compactor;
+ try (ByteArrayOutputStream outBuf = new ByteArrayOutputStream()) {
+ compactor = new ReftableCompactor(outBuf);
+ compactor.setReflogExpireOlderThan(Instant.MAX);
+ List<ReftableReader> readers = new ArrayList<>();
+ readers.add(read(inTab));
+ compactor.addAll(readers);
+ compactor.compact();
+ }
+
+ Stats stats = compactor.getStats();
+ assertEquals(0, stats.logCount());
+ }
+
private static Ref ref(String name, int id) {
return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
}
@@ -296,6 +388,10 @@ public class ReftableCompactorTest {
return ObjectId.fromRaw(buf);
}
+ private static PersonIdent person(Instant when) {
+ return new PersonIdent("a. u. thor", "author@jgit.com", when, ZoneId.systemDefault());
+ }
+
private static ReftableReader read(byte[] table) {
return new ReftableReader(BlockSource.from(table));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index 31940a16f7..06fee8ea71 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -1636,6 +1636,47 @@ public class ConfigTest {
assertFalse(config.get(CoreConfig.KEY).enableCommitGraph());
}
+ @Test
+ public void testGetNoDefaultBoolean() {
+ Config config = new Config();
+ assertNull(config.getBoolean("foo", "bar"));
+ assertNull(config.getBoolean("foo", "bar", "baz"));
+ }
+
+ @Test
+ public void testGetNoDefaultEnum() {
+ Config config = new Config();
+ assertNull(config.getEnum(new TestEnum[] { TestEnum.ONE_TWO }, "foo",
+ "bar", "baz"));
+ }
+
+ @Test
+ public void testGetNoDefaultInt() {
+ Config config = new Config();
+ assertNull(config.getInt("foo", "bar"));
+ assertNull(config.getInt("foo", "bar", "baz"));
+ }
+ @Test
+ public void testGetNoDefaultIntInRange() {
+ Config config = new Config();
+ assertNull(config.getIntInRange("foo", "bar", 1, 5));
+ assertNull(config.getIntInRange("foo", "bar", "baz", 1, 5));
+ }
+
+ @Test
+ public void testGetNoDefaultLong() {
+ Config config = new Config();
+ assertNull(config.getLong("foo", "bar"));
+ assertNull(config.getLong("foo", "bar", "baz"));
+ }
+
+ @Test
+ public void testGetNoDefaultTimeUnit() {
+ Config config = new Config();
+ assertNull(config.getTimeUnit("foo", "bar", "baz",
+ TimeUnit.SECONDS));
+ }
+
private static void assertValueRoundTrip(String value)
throws ConfigInvalidException {
assertValueRoundTrip(value, value);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index 2b7b6ca76c..cd98606e53 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -2,7 +2,7 @@
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2013, Robin Stocker <robin@nibor.org> and others
+ * Copyright (C) 2013, 2025 Robin Stocker <robin@nibor.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -539,7 +539,7 @@ public class IndexDiffTest extends RepositoryTestCase {
assertTrue(diff.getAssumeUnchanged().contains("file3"));
assertTrue(diff.getModified().contains("file"));
- git.add().addFilepattern(".").call();
+ git.add().addFilepattern(".").setAll(false).call();
iterator = new FileTreeIterator(db);
diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -551,6 +551,18 @@ public class IndexDiffTest extends RepositoryTestCase {
assertTrue(diff.getAssumeUnchanged().contains("file3"));
assertTrue(diff.getChanged().contains("file"));
assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+
+ git.add().addFilepattern(".").call();
+
+ iterator = new FileTreeIterator(db);
+ diff = new IndexDiff(db, Constants.HEAD, iterator);
+ diff.diff();
+ assertEquals(1, diff.getAssumeUnchanged().size());
+ assertEquals(0, diff.getModified().size());
+ assertEquals(1, diff.getChanged().size());
+ assertTrue(diff.getAssumeUnchanged().contains("file2"));
+ assertTrue(diff.getChanged().contains("file"));
+ assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
index b02f245865..85f9612b6a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
@@ -71,6 +71,11 @@ public class RefDatabaseConflictingNamesTest {
}
@Override
+ public ReflogReader getReflogReader(Ref ref) throws IOException {
+ return null;
+ }
+
+ @Override
public void create() throws IOException {
// Not needed
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index ad47cef0f4..a93937eeea 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -32,7 +32,8 @@ public class ReflogConfigTest extends RepositoryTestCase {
// check that there are no entries in the reflog and turn off writing
// reflogs
- assertTrue(db.getReflogReader(Constants.HEAD).getReverseEntries()
+ RefDatabase refDb = db.getRefDatabase();
+ assertTrue(refDb.getReflogReader(Constants.HEAD).getReverseEntries()
.isEmpty());
FileBasedConfig cfg = db.getConfig();
cfg.setBoolean("core", null, "logallrefupdates", false);
@@ -42,7 +43,7 @@ public class ReflogConfigTest extends RepositoryTestCase {
// written
commit("A Commit\n", commitTime, tz);
commitTime = commitTime.plus(Duration.ofMinutes(1));
- assertTrue("Reflog for HEAD still contain no entry", db
+ assertTrue("Reflog for HEAD still contain no entry", refDb
.getReflogReader(Constants.HEAD).getReverseEntries().isEmpty());
// set the logAllRefUpdates parameter to true and check it
@@ -56,9 +57,9 @@ public class ReflogConfigTest extends RepositoryTestCase {
// do one commit and check that reflog size is increased to 1
commit("A Commit\n", commitTime, tz);
commitTime = commitTime.plus(Duration.ofMinutes(1));
- assertTrue(
- "Reflog for HEAD should contain one entry",
- db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 1);
+ assertTrue("Reflog for HEAD should contain one entry",
+ refDb.getReflogReader(Constants.HEAD).getReverseEntries()
+ .size() == 1);
// set the logAllRefUpdates parameter to false and check it
cfg.setBoolean("core", null, "logallrefupdates", false);
@@ -71,9 +72,9 @@ public class ReflogConfigTest extends RepositoryTestCase {
// do one commit and check that reflog size is 2
commit("A Commit\n", commitTime, tz);
commitTime = commitTime.plus(Duration.ofMinutes(1));
- assertTrue(
- "Reflog for HEAD should contain two entries",
- db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 2);
+ assertTrue("Reflog for HEAD should contain two entries",
+ refDb.getReflogReader(Constants.HEAD).getReverseEntries()
+ .size() == 2);
// set the logAllRefUpdates parameter to false and check it
cfg.setEnum("core", null, "logallrefupdates",
@@ -87,7 +88,7 @@ public class ReflogConfigTest extends RepositoryTestCase {
// do one commit and check that reflog size is 3
commit("A Commit\n", commitTime, tz);
assertTrue("Reflog for HEAD should contain three entries",
- db.getReflogReader(Constants.HEAD).getReverseEntries()
+ refDb.getReflogReader(Constants.HEAD).getReverseEntries()
.size() == 3);
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
index c2f8f10631..e47dd898b0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
@@ -37,8 +37,9 @@ import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.ChangedPathTreeFilter;
+import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
-import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.junit.Test;
@@ -172,61 +173,99 @@ public class RevWalkCommitGraphTest extends RevWalkTestCase {
}
@Test
- public void testChangedPathFilter() throws Exception {
- RevCommit c1 = commitFile("file1", "1", "master");
- commitFile("file2", "2", "master");
- RevCommit c3 = commitFile("file1", "3", "master");
- RevCommit c4 = commitFile("file2", "4", "master");
+ public void testChangedPathFilter_allModify() throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file2", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file1", blob("3"))), c2);
+ RevCommit c4 = commit(tree(file("file2", blob("4"))), c3);
+
+ branch(c4, "master");
enableAndWriteCommitGraph();
- TreeRevFilter trf = new TreeRevFilter(rw, PathFilter.create("file1"));
+ TreeRevFilter trf = new TreeRevFilter(rw,
+ ChangedPathTreeFilter.create("file1"));
rw.markStart(rw.lookupCommit(c4));
rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
assertEquals(c1, rw.next());
assertNull(rw.next());
- // 1 commit that has exactly one parent and matches path
- assertEquals(1, trf.getChangedPathFilterTruePositive());
+ // all commits modified file1 but c1 did not have a parent
+ assertEquals(3, trf.getChangedPathFilterTruePositive());
// No false positives
assertEquals(0, trf.getChangedPathFilterFalsePositive());
- // 2 commits that have exactly one parent and don't match path
- assertEquals(2, trf.getChangedPathFilterNegative());
+ // No negatives because all 4 commits had modified file1
+ assertEquals(0, trf.getChangedPathFilterNegative());
}
@Test
- public void testChangedPathFilterWithMultiPaths() throws Exception {
- RevCommit c1 = commitFile("file1", "1", "master");
- RevCommit c2 = commitFile("file1", "2", "master");
- RevCommit c3 = commitFile("file2", "3", "master");
- RevCommit c4 = commitFile("file3", "4", "master");
+ public void testChangedPathFilter_someModify() throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file1", blob("1"))), c1);
+ RevCommit c3 = commit(tree(file("file1", blob("2"))), c2);
+ RevCommit c4 = commit(tree(file("file1", blob("1"))), c3);
+
+ branch(c4, "master");
enableAndWriteCommitGraph();
TreeRevFilter trf = new TreeRevFilter(rw,
- PathFilterGroup.createFromStrings(List.of("file1", "file2")));
+ ChangedPathTreeFilter.create("file1"));
rw.markStart(rw.lookupCommit(c4));
rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
assertEquals(c3, rw.next());
- assertEquals(c2, rw.next());
assertEquals(c1, rw.next());
assertNull(rw.next());
- // c2 and c3 has either file1 or file2, c1 did not use ChangedPathFilter
- // since it has no parent
+ // c4 and c3 modified file1. c1 did not have a parent
assertEquals(2, trf.getChangedPathFilterTruePositive());
// No false positives
assertEquals(0, trf.getChangedPathFilterFalsePositive());
- // c4 does not match either file1 or file2
+ // c2 did not modify file1
assertEquals(1, trf.getChangedPathFilterNegative());
}
@Test
+ public void testChangedPathFilterWithMultiPaths() throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file1", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file2", blob("3"))), c2);
+ RevCommit c4 = commit(tree(file("file3", blob("4"))), c3);
+
+ branch(c4, "master");
+
+ enableAndWriteCommitGraph();
+
+ TreeRevFilter trf = new TreeRevFilter(rw,
+ ChangedPathTreeFilter.create("file1", "file2"));
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
+ assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // all commits have modified either file1 or file2, c1 did not have a
+ // parent
+ assertEquals(3, trf.getChangedPathFilterTruePositive());
+
+ // No false positives
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // No negative
+ assertEquals(0, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
public void testChangedPathFilterWithFollowFilter() throws Exception {
RevCommit c0 = commit(tree());
RevCommit c1 = commit(tree(file("file", blob("contents"))), c0);
@@ -245,9 +284,8 @@ public class RevWalkCommitGraphTest extends RevWalkTestCase {
db.getConfig().setString(ConfigConstants.CONFIG_DIFF_SECTION, null,
ConfigConstants.CONFIG_KEY_RENAMES, "true");
- TreeRevFilter trf = new TreeRevFilter(rw,
- new FollowFilter(PathFilter.create("renamed-file"),
- db.getConfig().get(DiffConfig.KEY)));
+ TreeRevFilter trf = new TreeRevFilter(rw, FollowFilter
+ .create("renamed-file", db.getConfig().get(DiffConfig.KEY)));
rw.markStart(rw.lookupCommit(c4));
rw.setRevFilter(trf);
assertEquals(c3, rw.next());
@@ -267,6 +305,296 @@ public class RevWalkCommitGraphTest extends RevWalkTestCase {
}
@Test
+ public void testChangedPathFilter_pathFilter_or_pathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(
+ tree(file("file1", blob("1")), file("file2", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file2", blob("2"))), c2);
+ RevCommit c4 = commit(
+ tree(file("file2", blob("2")), file("file3", blob("3"))), c3);
+ RevCommit c5 = commit(
+ tree(file("file2", blob("2")), file("file3", blob("3"))), c4);
+
+ branch(c5, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1");
+ ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2");
+
+ TreeFilter tf = OrTreeFilter
+ .create(new ChangedPathTreeFilter[] { pf1, pf2 });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c5));
+ rw.setRevFilter(trf);
+ assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // c2 and c3 has either file1 or file2, c1 is not counted as
+ // ChangedPathFilter only applies to commits with 1 parent
+ assertEquals(2, trf.getChangedPathFilterTruePositive());
+
+ // No false positives
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // c4 and c5 did not modify file1 or file2
+ assertEquals(2, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_pathFilter_or_pathFilter_or_pathFilter_listOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(
+ tree(file("file1", blob("1")), file("file2", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file2", blob("2"))), c2);
+ RevCommit c4 = commit(tree(file("file3", blob("3"))), c3);
+ RevCommit c5 = commit(tree(file("file3", blob("3"))), c4);
+
+ branch(c5, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1");
+ ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2");
+ ChangedPathTreeFilter pf3 = ChangedPathTreeFilter.create("file3");
+
+ TreeFilter tf = OrTreeFilter
+ .create(new ChangedPathTreeFilter[] { pf1, pf2, pf3 });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c5));
+ rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
+ assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // c2 and c3 has either modified file1 or file2 or file3, c1 is not
+ // counted as ChangedPathFilter only applies to commits with 1 parent
+ assertEquals(3, trf.getChangedPathFilterTruePositive());
+
+ // No false positives
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // c5 does not modify either file1 or file2 or file3
+ assertEquals(1, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_pathFilter_or_nonPathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file2", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file2", blob("3"))), c2);
+ RevCommit c4 = commit(tree(file("file2", blob("3"))), c3);
+
+ branch(c4, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf = ChangedPathTreeFilter.create("file1");
+ TreeFilter npf = TreeFilter.ANY_DIFF;
+
+ TreeFilter tf = OrTreeFilter.create(new TreeFilter[] { pf, npf });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setRevFilter(trf);
+ assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // c2 modified file1, c3 defaulted positive due to ANY_DIFF, c1 is not
+ // counted as ChangedPathFilter only applies to commits with 1 parent
+ assertEquals(2, trf.getChangedPathFilterTruePositive());
+
+ // c4 defaulted positive due to ANY_DIFF, but didn't no diff with its
+ // parent c3
+ assertEquals(1, trf.getChangedPathFilterFalsePositive());
+
+ // No negative due to the OrTreeFilter
+ assertEquals(0, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_nonPathFilter_or_nonPathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commitFile("file1", "1", "master");
+ RevCommit c2 = commitFile("file2", "2", "master");
+ RevCommit c3 = commitFile("file3", "3", "master");
+ RevCommit c4 = commitFile("file4", "4", "master");
+
+ enableAndWriteCommitGraph();
+
+ TreeFilter npf1 = TreeFilter.ANY_DIFF;
+ TreeFilter npf2 = TreeFilter.ANY_DIFF;
+
+ TreeFilter tf = OrTreeFilter.create(new TreeFilter[] { npf1, npf2 });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
+ assertEquals(c3, rw.next());
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // No true positives since there's no pathFilter
+ assertEquals(0, trf.getChangedPathFilterTruePositive());
+
+ // No false positives since there's no pathFilter
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // No negative since there's no pathFilter
+ assertEquals(0, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_pathFilter_and_pathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file2", blob("2"))), c1);
+
+ branch(c2, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1");
+ ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2");
+
+ TreeFilter atf = AndTreeFilter
+ .create(new ChangedPathTreeFilter[] { pf1, pf2 });
+ TreeRevFilter trf = new TreeRevFilter(rw, atf);
+
+ rw.markStart(rw.lookupCommit(c2));
+ rw.setRevFilter(trf);
+
+ assertNull(rw.next());
+
+ // c1 is not counted as ChangedPathFilter only applies to commits with 1
+ // parent
+ assertEquals(0, trf.getChangedPathFilterTruePositive());
+
+ // c2 has modified both file 1 and file2,
+ // however nothing is returned from TreeWalk since a TreeHead
+ // cannot be two paths at once
+ assertEquals(1, trf.getChangedPathFilterFalsePositive());
+
+ // No negatives
+ assertEquals(0, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_pathFilter_and_pathFilter_and_pathFilter_listOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file2", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file3", blob("3"))), c2);
+
+ branch(c3, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1");
+ ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2");
+ ChangedPathTreeFilter pf3 = ChangedPathTreeFilter.create("file3");
+
+ TreeFilter tf = AndTreeFilter
+ .create(new ChangedPathTreeFilter[] { pf1, pf2, pf3 });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c3));
+ rw.setRevFilter(trf);
+ assertNull(rw.next());
+
+ // c1 is not counted as ChangedPathFilter only applies to commits with 1
+ // parent
+ assertEquals(0, trf.getChangedPathFilterTruePositive());
+
+ // No false positives
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // c2 and c3 can not possibly have both file1, file2, and file3 as
+ // treeHead at once
+ assertEquals(2, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_pathFilter_and_nonPathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commit(tree(file("file1", blob("1"))));
+ RevCommit c2 = commit(tree(file("file1", blob("2"))), c1);
+ RevCommit c3 = commit(tree(file("file1", blob("2"))), c2);
+
+ branch(c3, "master");
+
+ enableAndWriteCommitGraph();
+
+ ChangedPathTreeFilter pf = ChangedPathTreeFilter.create("file1");
+ TreeFilter npf = TreeFilter.ANY_DIFF;
+
+ TreeFilter tf = AndTreeFilter.create(new TreeFilter[] { pf, npf });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c3));
+ rw.setRevFilter(trf);
+ assertEquals(c2, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // c2 modified file1 and c1 is not counted as ChangedPathFilter only
+ // applies to commits with 1 parent
+ assertEquals(1, trf.getChangedPathFilterTruePositive());
+
+ // No false positives
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // c3 did not modify file1
+ assertEquals(1, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
+ public void testChangedPathFilter_nonPathFilter_and_nonPathFilter_binaryOperation()
+ throws Exception {
+ RevCommit c1 = commitFile("file1", "1", "master");
+ commitFile("file1", "1", "master");
+ RevCommit c3 = commitFile("file3", "3", "master");
+ RevCommit c4 = commitFile("file4", "4", "master");
+
+ enableAndWriteCommitGraph();
+
+ TreeFilter npf1 = TreeFilter.ANY_DIFF;
+ TreeFilter npf2 = TreeFilter.ANY_DIFF;
+
+ TreeFilter tf = AndTreeFilter.create(new TreeFilter[] { npf1, npf2 });
+
+ TreeRevFilter trf = new TreeRevFilter(rw, tf);
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setRevFilter(trf);
+ assertEquals(c4, rw.next());
+ assertEquals(c3, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ // No true positives since there's no path
+ assertEquals(0, trf.getChangedPathFilterTruePositive());
+
+ // No false positives since there's no path
+ assertEquals(0, trf.getChangedPathFilterFalsePositive());
+
+ // No negative since there's no path
+ assertEquals(0, trf.getChangedPathFilterNegative());
+ }
+
+ @Test
public void testWalkWithCommitMessageFilter() throws Exception {
RevCommit a = commit();
RevCommit b = commitBuilder().parent(a)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
index 0a045c917b..ffc7c96f69 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkUtilsReachableTest.java
@@ -14,6 +14,7 @@ import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import org.eclipse.jgit.api.Git;
@@ -121,7 +122,7 @@ public class RevWalkUtilsReachableTest extends RevWalkTestCase {
Collection<Ref> sortedRefs = RefComparator.sort(allRefs);
List<Ref> actual = RevWalkUtils.findBranchesReachableFrom(commit,
rw, sortedRefs);
- assertEquals(refsThatShouldContainCommit, actual);
+ assertEquals(new HashSet<>(refsThatShouldContainCommit), new HashSet<>(actual));
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
index c5e9c2deaa..d54117005d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
@@ -17,21 +17,25 @@ import java.io.File;
import java.io.IOException;
import java.util.Collection;
+import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.InitCommand;
+import org.eclipse.jgit.api.SubmoduleAddCommand;
import org.eclipse.jgit.api.SubmoduleUpdateCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.junit.Test;
@@ -40,6 +44,91 @@ import org.junit.Test;
*/
public class SubmoduleUpdateTest extends RepositoryTestCase {
+ private Repository submoduleRepo;
+
+ private Git git;
+
+ private AnyObjectId subRepoCommit2;
+
+ private void createSubmoduleRepo() throws IOException, GitAPIException {
+ File directory = createTempDirectory("submodule_repo");
+ InitCommand init = Git.init();
+ init.setDirectory(directory);
+ init.call();
+ submoduleRepo = Git.open(directory).getRepository();
+ try (Git sub = Git.wrap(submoduleRepo)) {
+ // commit something
+ JGitTestUtil.writeTrashFile(submoduleRepo, "commit1.txt",
+ "commit 1");
+ sub.add().addFilepattern("commit1.txt").call();
+ sub.commit().setMessage("commit 1").call().getId();
+
+ JGitTestUtil.writeTrashFile(submoduleRepo, "commit2.txt",
+ "commit 2");
+ sub.add().addFilepattern("commit2.txt").call();
+ subRepoCommit2 = sub.commit().setMessage("commit 2").call().getId();
+ }
+ }
+
+ private void addSubmodule(String path) throws GitAPIException {
+ SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+ command.setPath(path);
+ String uri = submoduleRepo.getDirectory().toURI().toString();
+ command.setURI(uri);
+ try (Repository repo = command.call()) {
+ assertNotNull(repo);
+ }
+ git.add().addFilepattern(path).addFilepattern(Constants.DOT_GIT_MODULES)
+ .call();
+ git.commit().setMessage("adding submodule").call();
+ recursiveDelete(new File(git.getRepository().getWorkTree(), path));
+ recursiveDelete(
+ new File(new File(git.getRepository().getCommonDirectory(),
+ Constants.MODULES), path));
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ createSubmoduleRepo();
+
+ git = Git.wrap(db);
+ // commit something
+ writeTrashFile("initial.txt", "initial");
+ git.add().addFilepattern("initial.txt").call();
+ git.commit().setMessage("initial commit").call();
+ }
+
+ public void updateModeClonedRestoredSubmoduleTemplate(String mode)
+ throws Exception {
+ String path = "sub";
+ addSubmodule(path);
+
+ StoredConfig cfg = git.getRepository().getConfig();
+ if (mode != null) {
+ cfg.load();
+ cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+ ConfigConstants.CONFIG_KEY_UPDATE, mode);
+ cfg.save();
+ }
+ SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(db);
+ update.call();
+ try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+ update.call();
+ assertEquals(subRepoCommit2.getName(),
+ subGit.getRepository().getBranch());
+ }
+
+ recursiveDelete(new File(db.getWorkTree(), path));
+
+ update.call();
+ try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+ update.call();
+ assertEquals(subRepoCommit2.getName(),
+ subGit.getRepository().getBranch());
+ }
+ }
+
@Test
public void repositoryWithNoSubmodules() throws GitAPIException {
SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
@@ -50,35 +139,9 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
@Test
public void repositoryWithSubmodule() throws Exception {
- writeTrashFile("file.txt", "content");
- Git git = Git.wrap(db);
- git.add().addFilepattern("file.txt").call();
- final RevCommit commit = git.commit().setMessage("create file").call();
final String path = "sub";
- DirCache cache = db.lockDirCache();
- DirCacheEditor editor = cache.editor();
- editor.add(new PathEdit(path) {
-
- @Override
- public void apply(DirCacheEntry ent) {
- ent.setFileMode(FileMode.GITLINK);
- ent.setObjectId(commit);
- }
- });
- editor.commit();
-
- StoredConfig config = db.getConfig();
- config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_URL, db.getDirectory().toURI()
- .toString());
- config.save();
-
- FileBasedConfig modulesConfig = new FileBasedConfig(new File(
- db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS());
- modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_PATH, path);
- modulesConfig.save();
+ addSubmodule(path);
SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
Collection<String> updated = command.call();
@@ -90,7 +153,7 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
assertTrue(generator.next());
try (Repository subRepo = generator.getRepository()) {
assertNotNull(subRepo);
- assertEquals(commit, subRepo.resolve(Constants.HEAD));
+ assertEquals(subRepoCommit2, subRepo.resolve(Constants.HEAD));
String worktreeDir = subRepo.getConfig().getString(
ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_WORKTREE);
@@ -104,8 +167,8 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
}
@Test
- public void repositoryWithUnconfiguredSubmodule() throws IOException,
- GitAPIException {
+ public void repositoryWithUnconfiguredSubmodule()
+ throws IOException, GitAPIException {
final ObjectId id = ObjectId
.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
final String path = "sub";
@@ -121,16 +184,14 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
});
editor.commit();
- FileBasedConfig modulesConfig = new FileBasedConfig(new File(
- db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS());
+ FileBasedConfig modulesConfig = new FileBasedConfig(
+ new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
+ db.getFS());
modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
ConfigConstants.CONFIG_KEY_PATH, path);
String url = "git://server/repo.git";
modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
ConfigConstants.CONFIG_KEY_URL, url);
- String update = "rebase";
- modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_UPDATE, update);
modulesConfig.save();
SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
@@ -140,8 +201,8 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
}
@Test
- public void repositoryWithInitializedSubmodule() throws IOException,
- GitAPIException {
+ public void repositoryWithInitializedSubmodule()
+ throws IOException, GitAPIException {
final ObjectId id = ObjectId
.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
final String path = "sub";
@@ -168,4 +229,77 @@ public class SubmoduleUpdateTest extends RepositoryTestCase {
assertNotNull(updated);
assertTrue(updated.isEmpty());
}
+
+ @Test
+ public void updateModeMergeClonedRestoredSubmodule() throws Exception {
+ updateModeClonedRestoredSubmoduleTemplate(
+ ConfigConstants.CONFIG_KEY_MERGE);
+ }
+
+ @Test
+ public void updateModeRebaseClonedRestoredSubmodule() throws Exception {
+ updateModeClonedRestoredSubmoduleTemplate(
+ ConfigConstants.CONFIG_KEY_REBASE);
+ }
+
+ @Test
+ public void updateModeCheckoutClonedRestoredSubmodule() throws Exception {
+ updateModeClonedRestoredSubmoduleTemplate(
+ ConfigConstants.CONFIG_KEY_CHECKOUT);
+ }
+
+ @Test
+ public void updateModeMissingClonedRestoredSubmodule() throws Exception {
+ updateModeClonedRestoredSubmoduleTemplate(null);
+ }
+
+ @Test
+ public void updateMode() throws Exception {
+ String path = "sub";
+ addSubmodule(path);
+
+ StoredConfig cfg = git.getRepository().getConfig();
+ cfg.load();
+ cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+ ConfigConstants.CONFIG_KEY_UPDATE,
+ ConfigConstants.CONFIG_KEY_REBASE);
+ cfg.save();
+
+ SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(db);
+ update.call();
+ try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+ CheckoutCommand checkout = subGit.checkout();
+ checkout.setName("master");
+ checkout.call();
+ update.call();
+ assertEquals("master", subGit.getRepository().getBranch());
+ }
+
+ cfg.load();
+ cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+ ConfigConstants.CONFIG_KEY_UPDATE,
+ ConfigConstants.CONFIG_KEY_CHECKOUT);
+ cfg.save();
+
+ update.call();
+ try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+ assertEquals(subRepoCommit2.getName(),
+ subGit.getRepository().getBranch());
+ }
+
+ cfg.load();
+ cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+ ConfigConstants.CONFIG_KEY_UPDATE,
+ ConfigConstants.CONFIG_KEY_MERGE);
+ cfg.save();
+
+ update.call();
+ try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+ CheckoutCommand checkout = subGit.checkout();
+ checkout.setName("master");
+ checkout.call();
+ update.call();
+ assertEquals("master", subGit.getRepository().getBranch());
+ }
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java
new file mode 100644
index 0000000000..b17c577087
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2021, Google LLC. and others
+ * Copyright (C) 2008, Imran M Yousuf <imyousuf@smartitengineering.com>
+ * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.transport;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.util.zip.Deflater;
+
+import org.eclipse.jgit.internal.storage.file.ObjectDirectoryPackParser;
+import org.eclipse.jgit.internal.storage.file.Pack;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Pack parsing is covered in {@link PackParserTest}.
+ *
+ * Here we test ObjectDirectoryPackParser specific parts. e.g. that is creates
+ * the object-size index.
+ */
+public class ObjectDirectoryPackParserTest extends RepositoryTestCase {
+
+ @Before
+ public void setup() throws IOException {
+ FileBasedConfig jGitConfig = mockSystemReader.getJGitConfig();
+ jGitConfig.setInt(ConfigConstants.CONFIG_PACK_SECTION, null,
+ ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, 7);
+ jGitConfig.save();
+ }
+
+ /**
+ * Test indexing one of the test packs in the egit repo. It has deltas.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testGitPack() throws IOException {
+ File packFile = JGitTestUtil.getTestResourceFile("pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
+ try (InputStream is = new FileInputStream(packFile)) {
+ ObjectDirectoryPackParser p = index(is);
+ p.parse(NullProgressMonitor.INSTANCE);
+
+ Pack pack = p.getPack();
+ assertTrue(pack.hasObjectSizeIndex());
+
+ // Only blobs in the pack
+ ObjectId blob1 = ObjectId
+ .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
+ ObjectId blob2 = ObjectId
+ .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259");
+ assertEquals(18787, pack.getIndexedObjectSize(blob1));
+ assertEquals(18009, pack.getIndexedObjectSize(blob2));
+
+ // Indexed sizes match object db sizes
+ assertEquals(db.getObjectDatabase().open(blob1).getSize(),
+ pack.getIndexedObjectSize(blob1));
+ assertEquals(db.getObjectDatabase().open(blob2).getSize(),
+ pack.getIndexedObjectSize(blob2));
+
+ }
+ }
+
+ /**
+ * This is just another pack. It so happens that we have two convenient pack to
+ * test with in the repository.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testAnotherGitPack() throws IOException {
+ File packFile = JGitTestUtil.getTestResourceFile("pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack");
+ try (InputStream is = new FileInputStream(packFile)) {
+ ObjectDirectoryPackParser p = index(is);
+ p.parse(NullProgressMonitor.INSTANCE);
+ Pack pack = p.getPack();
+
+ // Blob smaller than threshold:
+ assertEquals(-1, pack.getIndexedObjectSize(ObjectId
+ .fromString("15fae9e651043de0fd1deef588aa3fbf5a7a41c6")));
+
+ // Blob bigger than threshold
+ assertEquals(10, pack.getIndexedObjectSize(ObjectId
+ .fromString("8230f48330e0055d9e0bc5a2a77718f6dd9324b8")));
+
+ // A commit (not indexed)
+ assertEquals(-1, pack.getIndexedObjectSize(ObjectId
+ .fromString("d0114ab8ac326bab30e3a657a0397578c5a1af88")));
+
+ // Object not in pack
+ assertEquals(-1, pack.getIndexedObjectSize(ObjectId
+ .fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")));
+ }
+ }
+
+ @Test
+ public void testTinyThinPack() throws Exception {
+ // less than 16 bytes, so its length fits in a single byte later
+ String base = "abcdefghijklmn";
+ RevBlob a;
+ try (TestRepository d = new TestRepository<Repository>(db)) {
+ a = d.blob(base);
+ }
+
+ TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
+
+ packHeader(pack, 1);
+
+ pack.write(Constants.OBJ_REF_DELTA << 4 | 4);
+ a.copyRawTo(pack);
+ deflate(pack, new byte[] { (byte) base.length(), // size of the base
+ (byte) (base.length() + 1), // size after reconstruction
+ 0x1, 'b' }); // append one byte
+
+ digest(pack);
+
+ ObjectDirectoryPackParser p = index(new ByteArrayInputStream(pack.toByteArray()));
+ p.setAllowThin(true);
+ p.parse(NullProgressMonitor.INSTANCE);
+
+ Pack writtenPack = p.getPack();
+ // base
+ assertEquals(base.length(), writtenPack.getIndexedObjectSize(a));
+ // undeltified blob
+ assertEquals(base.length() + 1,
+ writtenPack.getIndexedObjectSize(ObjectId.fromString(
+ "f177875498138143c9657cc52b049ad4d20d5223")));
+ }
+
+ @Test
+ public void testPackWithDuplicateBlob() throws Exception {
+ final byte[] data = Constants.encode("0123456789abcdefg");
+ RevBlob blob;
+ try (TestRepository<Repository> d = new TestRepository<>(db)) {
+ blob = d.blob(data);
+ assertTrue(db.getObjectDatabase().has(blob));
+ }
+
+ TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024);
+ packHeader(pack, 1);
+ pack.write(Constants.OBJ_BLOB << 4 | 0x80 | 1);
+ pack.write(1);
+ deflate(pack, data);
+ digest(pack);
+
+ ObjectDirectoryPackParser p = index(
+ new ByteArrayInputStream(pack.toByteArray()));
+ p.setAllowThin(false);
+ p.parse(NullProgressMonitor.INSTANCE);
+
+ assertEquals(data.length, p.getPack().getIndexedObjectSize(blob));
+ }
+
+ private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt)
+ throws IOException {
+ final byte[] hdr = new byte[8];
+ NB.encodeInt32(hdr, 0, 2);
+ NB.encodeInt32(hdr, 4, cnt);
+
+ tinyPack.write(Constants.PACK_SIGNATURE);
+ tinyPack.write(hdr, 0, 8);
+ }
+
+ private static void deflate(TemporaryBuffer.Heap tinyPack,
+ final byte[] content)
+ throws IOException {
+ final Deflater deflater = new Deflater();
+ final byte[] buf = new byte[128];
+ deflater.setInput(content, 0, content.length);
+ deflater.finish();
+ do {
+ final int n = deflater.deflate(buf, 0, buf.length);
+ if (n > 0)
+ tinyPack.write(buf, 0, n);
+ } while (!deflater.finished());
+ }
+
+ private static void digest(TemporaryBuffer.Heap buf) throws IOException {
+ MessageDigest md = Constants.newMessageDigest();
+ md.update(buf.toByteArray());
+ buf.write(md.digest());
+ }
+
+ private ObjectInserter inserter;
+
+ @After
+ public void release() {
+ if (inserter != null) {
+ inserter.close();
+ }
+ }
+
+ private ObjectDirectoryPackParser index(InputStream in) throws IOException {
+ if (inserter == null)
+ inserter = db.newObjectInserter();
+ return (ObjectDirectoryPackParser) inserter.newPackParser(in);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
index d403624b71..67920029d4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
@@ -82,6 +82,43 @@ public class URIishTest {
}
@Test
+ public void testBrokenFilePath() throws Exception {
+ String str = "D:\\\\my\\\\x";
+ URIish u = new URIish(str);
+ assertNull(u.getScheme());
+ assertFalse(u.isRemote());
+ assertEquals(str, u.getPath());
+ assertEquals(u, new URIish(str));
+ }
+
+ @Test
+ public void testStackOverflow() throws Exception {
+ StringBuilder b = new StringBuilder("D:\\");
+ for (int i = 0; i < 4000; i++) {
+ b.append("x\\");
+ }
+ String str = b.toString();
+ URIish u = new URIish(str);
+ assertNull(u.getScheme());
+ assertFalse(u.isRemote());
+ assertEquals(str, u.getPath());
+ }
+
+ @Test
+ public void testStackOverflow2() throws Exception {
+ StringBuilder b = new StringBuilder("D:\\");
+ for (int i = 0; i < 4000; i++) {
+ b.append("x\\");
+ }
+ b.append('y');
+ String str = b.toString();
+ URIish u = new URIish(str);
+ assertNull(u.getScheme());
+ assertFalse(u.isRemote());
+ assertEquals(str, u.getPath());
+ }
+
+ @Test
public void testRelativePath() throws Exception {
final String str = "../../foo/bar";
URIish u = new URIish(str);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilterTest.java
new file mode 100644
index 0000000000..88f6b75c6b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilterTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.treewalk.filter;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.internal.storage.commitgraph.ChangedPathFilter;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter.MutableBoolean;
+import org.junit.Test;
+
+public class ChangedPathTreeFilterTest {
+
+ @Test
+ public void shouldTreeWalk_no_usingCpf() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ MutableBoolean cfpUsed = new MutableBoolean();
+
+ boolean result = f.shouldTreeWalk(FakeRevCommit.withCpfFor("c"), null,
+ cfpUsed);
+
+ assertFalse(result);
+ assertTrue(cfpUsed.get());
+ }
+
+ @Test
+ public void shouldTreeWalk_yes_usingCpf() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ MutableBoolean cfpUsed = new MutableBoolean();
+
+ boolean result = f.shouldTreeWalk(FakeRevCommit.withCpfFor("a/b"), null,
+ cfpUsed);
+
+ assertTrue(result);
+ assertTrue(cfpUsed.get());
+ }
+
+ @Test
+ public void shouldTreeWalk_yes_noCpf() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ MutableBoolean cfpUsed = new MutableBoolean();
+
+ boolean result = f.shouldTreeWalk(FakeRevCommit.noCpf(), null,
+ cfpUsed);
+
+ assertTrue(result);
+ assertFalse(cfpUsed.get());
+ }
+
+ @Test
+ public void shouldTreeWalk_no_usingCpf_noReport() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ boolean result = f.shouldTreeWalk(FakeRevCommit.withCpfFor("c"), null,
+ null);
+
+ assertFalse(result);
+ }
+
+ @Test
+ public void shouldTreeWalk_yes_usingCpf_noReport() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ boolean result = f.shouldTreeWalk(FakeRevCommit.withCpfFor("a/b"), null,
+ null);
+ assertTrue(result);
+ }
+
+ @Test
+ public void shouldTreeWalk_yes_noCpf_noReport() {
+ ChangedPathTreeFilter f = ChangedPathTreeFilter.create("a/b");
+ boolean result = f.shouldTreeWalk(FakeRevCommit.noCpf(), null,
+ null);
+
+ assertTrue(result);
+ }
+
+ private static class FakeRevCommit extends RevCommit {
+
+ static RevCommit withCpfFor(String... paths) {
+ return new FakeRevCommit(
+ ChangedPathFilter.fromPaths(Arrays.stream(paths)
+ .map(str -> ByteBuffer.wrap(str.getBytes(UTF_8)))
+ .collect(Collectors.toSet())));
+ }
+
+ static RevCommit noCpf() {
+ return new FakeRevCommit(null);
+ }
+
+ private final ChangedPathFilter cpf;
+
+ /**
+ * Create a new commit reference.
+ *
+ * @param cpf
+ * changedPathFilter
+ */
+ protected FakeRevCommit(ChangedPathFilter cpf) {
+ super(ObjectId.zeroId());
+ this.cpf = cpf;
+ }
+
+ @Override
+ public ChangedPathFilter getChangedPathFilter(RevWalk rw) {
+ return cpf;
+ }
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index e7f2049bb9..44e8632228 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -220,23 +220,23 @@ public class ChangeIdUtilTest {
@Test
public void testACommitWithSubjectBodyBugBrackersAndSob() throws Exception {
assertEquals(
- "a commit with subject body, bug. brackers and sob\n\nText\n\nBug: 33\nChange-Id: I90ecb589bef766302532c3e00915e10114b00f62\n[bracket]\nSigned-off-by: me@you.too\n",
- call("a commit with subject body, bug. brackers and sob\n\nText\n\nBug: 33\n[bracket]\nSigned-off-by: me@you.too\n\n"));
+ "a commit with subject body, bug, brackers and sob\n\nText\n\nBug: 33\n[bracket]\nChange-Id: I94dc6ed919a4baaa7c1bf8712717b888c6b90363\nSigned-off-by: me@you.too\n",
+ call("a commit with subject body, bug, brackers and sob\n\nText\n\nBug: 33\n[bracket]\nSigned-off-by: me@you.too\n\n"));
}
@Test
public void testACommitWithSubjectBodyBugLineWithASpaceAndSob()
throws Exception {
assertEquals(
- "a commit with subject body, bug. line with a space and sob\n\nText\n\nBug: 33\nChange-Id: I864e2218bdee033c8ce9a7f923af9e0d5dc16863\n \nSigned-off-by: me@you.too\n",
- call("a commit with subject body, bug. line with a space and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
+ "a commit with subject body, bug, line with a space and sob\n\nText\n\nBug: 33\n \nChange-Id: I126b472d2e0e64ad8187d61857f0169f9ccdae86\nSigned-off-by: me@you.too\n",
+ call("a commit with subject body, bug, line with a space and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
}
@Test
public void testACommitWithSubjectBodyBugEmptyLineAndSob() throws Exception {
assertEquals(
- "a commit with subject body, bug. empty line and sob\n\nText\n\nBug: 33\nChange-Id: I33f119f533313883e6ada3df600c4f0d4db23a76\n \nSigned-off-by: me@you.too\n",
- call("a commit with subject body, bug. empty line and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
+ "a commit with subject body, bug, empty line and sob\n\nText\n\nBug: 33\n\nChange-Id: Ic3b61b6e39a0815669b65302e9e75e6a5a019a26\nSigned-off-by: me@you.too\n",
+ call("a commit with subject body, bug, empty line and sob\n\nText\n\nBug: 33\n\nSigned-off-by: me@you.too\n\n"));
}
@Test
@@ -528,7 +528,7 @@ public class ChangeIdUtilTest {
}
@Test
- public void testChangeIdAfterBugOrIssue() throws Exception {
+ public void testChangeIdAfterOtherFooters() throws Exception {
assertEquals("a\n" + //
"\n" + //
"Bug: 42\n" + //
@@ -541,6 +541,18 @@ public class ChangeIdUtilTest {
assertEquals("a\n" + //
"\n" + //
+ "Bug: 42\n" + //
+ " multi-line Bug footer\n" + //
+ "Change-Id: Icc953ef35f1a4ee5eb945132aefd603ae3d9dd9f\n" + //
+ SOB1,//
+ call("a\n" + //
+ "\n" + //
+ "Bug: 42\n" + //
+ " multi-line Bug footer\n" + //
+ SOB1));
+
+ assertEquals("a\n" + //
+ "\n" + //
"Issue: 42\n" + //
"Change-Id: Ie66e07d89ae5b114c0975b49cf326e90331dd822\n" + //
SOB1,//
@@ -548,6 +560,14 @@ public class ChangeIdUtilTest {
"\n" + //
"Issue: 42\n" + //
SOB1));
+
+ assertEquals("a\n" + //
+ "\n" + //
+ "Other: none\n" + //
+ "Change-Id: Ide70e625dea61854206378a377dd12e462ae720f\n",//
+ call("a\n" + //
+ "\n" + //
+ "Other: none\n"));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
index a8077fdb0c..0c9cb2d2b0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
@@ -423,7 +423,7 @@ public class RefMapTest {
Map.Entry<String, Ref> ent_b = itr.next();
assertEquals(ent_a.hashCode(), "A".hashCode());
- assertEquals(ent_a, ent_a);
+ assertTrue(ent_a.equals(ent_a));
assertFalse(ent_a.equals(ent_b));
assertEquals(a.toString(), ent_a.toString());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
index 015da164c3..9a1c710752 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
@@ -12,6 +12,7 @@ package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -172,4 +173,22 @@ public class StringUtilsTest {
assertEquals("foo bar ",
StringUtils.commonPrefix("foo bar 42", "foo bar 24"));
}
+
+ @Test
+ public void testTrim() {
+ assertEquals("a", StringUtils.trim("a", '/'));
+ assertEquals("aaaa", StringUtils.trim("aaaa", '/'));
+ assertEquals("aaa", StringUtils.trim("/aaa", '/'));
+ assertEquals("aaa", StringUtils.trim("aaa/", '/'));
+ assertEquals("aaa", StringUtils.trim("/aaa/", '/'));
+ assertEquals("aa/aa", StringUtils.trim("/aa/aa/", '/'));
+ assertEquals("aa/aa", StringUtils.trim("aa/aa", '/'));
+
+ assertEquals("", StringUtils.trim("", '/'));
+ assertEquals("", StringUtils.trim("/", '/'));
+ assertEquals("", StringUtils.trim("//", '/'));
+ assertEquals("", StringUtils.trim("///", '/'));
+
+ assertNull(StringUtils.trim(null, '/'));
+ }
}