Merge "midx.MultiPackIndexPrettyPrinter: pretty printer to debug multi pack index"
diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java
index 52a881b..ea279fb 100644
--- a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java
+++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/GetRefsBenchmark.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache;
@@ -66,11 +67,11 @@ public static class BenchmarkState {
 		@Param({ "true", "false" })
 		boolean useRefTable;
 
-		@Param({ "100", "2500", "10000", "50000" })
+		@Param({ "100", "1000", "10000", "100000" })
 		int numBranches;
 
-		@Param({ "true", "false" })
-		boolean trustFolderStat;
+		@Param({ "ALWAYS", "AFTER_OPEN", "NEVER" })
+		TrustStat trustStat;
 
 		List<String> branches = new ArrayList<>(numBranches);
 
@@ -83,8 +84,8 @@ public static class BenchmarkState {
 		public void setupBenchmark() throws IOException, GitAPIException {
 			String firstBranch = "firstbranch";
 			testDir = Files.createDirectory(Paths.get("testrepos"));
-			String repoName = "branches-" + numBranches + "-trustFolderStat-"
-					+ trustFolderStat + "-" + refDatabaseType();
+			String repoName = "branches-" + numBranches + "-trustStat-"
+					+ trustStat + "-" + refDatabaseType();
 			Path workDir = testDir.resolve(repoName);
 			Path repoPath = workDir.resolve(".git");
 			Git git = Git.init().setDirectory(workDir.toFile()).call();
@@ -98,9 +99,9 @@ public void setupBenchmark() throws IOException, GitAPIException {
 						ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false,
 						false);
 			} else {
-				cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT,
-						trustFolderStat);
+				cfg.setEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+						ConfigConstants.CONFIG_KEY_TRUST_STAT,
+						trustStat);
 			}
 			cfg.setInt(ConfigConstants.CONFIG_RECEIVE_SECTION, null,
 					"maxCommandBytes", Integer.MAX_VALUE);
@@ -112,7 +113,7 @@ public void setupBenchmark() throws IOException, GitAPIException {
 			System.out.println("Preparing test");
 			System.out.println("- repository: \t\t" + repoPath);
 			System.out.println("- refDatabase: \t\t" + refDatabaseType());
-			System.out.println("- trustFolderStat: \t" + trustFolderStat);
+			System.out.println("- trustStat: \t" + trustStat);
 			System.out.println("- branches: \t\t" + numBranches);
 
 			BatchRefUpdate u = repo.getRefDatabase().newBatchUpdate();
@@ -152,7 +153,7 @@ public void teardown() throws IOException {
 	@BenchmarkMode({ Mode.AverageTime })
 	@OutputTimeUnit(TimeUnit.MICROSECONDS)
 	@Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
-	@Measurement(iterations = 2, time = 10, timeUnit = TimeUnit.SECONDS)
+	@Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
 	public void testGetExactRef(Blackhole blackhole, BenchmarkState state)
 			throws IOException {
 		String branchName = state.branches
@@ -164,7 +165,7 @@ public void testGetExactRef(Blackhole blackhole, BenchmarkState state)
 	@BenchmarkMode({ Mode.AverageTime })
 	@OutputTimeUnit(TimeUnit.MICROSECONDS)
 	@Warmup(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
-	@Measurement(iterations = 2, time = 10, timeUnit = TimeUnit.SECONDS)
+	@Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
 	public void testGetRefsByPrefix(Blackhole blackhole, BenchmarkState state)
 			throws IOException {
 		String branchPrefix = "refs/heads/branch/" + branchIndex.nextInt(100)
diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
index f266ce1..32c12a1 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -27,6 +27,7 @@
  org.eclipse.jgit.api.errors;version="[7.2.0,7.3.0)",
  org.eclipse.jgit.internal.signing.ssh;version="[7.2.0,7.3.0)",
  org.eclipse.jgit.internal.storage.file;version="[7.2.0,7.3.0)",
+ org.eclipse.jgit.internal.transport.sshd;version="[7.2.0,7.3.0)",
  org.eclipse.jgit.internal.transport.sshd.proxy;version="[7.2.0,7.3.0)",
  org.eclipse.jgit.junit;version="[7.2.0,7.3.0)",
  org.eclipse.jgit.junit.ssh;version="[7.2.0,7.3.0)",
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReaderTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReaderTest.java
new file mode 100644
index 0000000..d36c38f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReaderTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.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.internal.transport.sshd;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.sshd.client.config.hosts.KnownHostEntry;
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.junit.Test;
+
+public class KnownHostEntryReaderTest {
+
+	@Test
+	public void testUnsupportedHostKeyLine() {
+		KnownHostEntry entry = KnownHostEntryReader.parseHostEntry(
+				"[localhost]:2222 ssh-unknown AAAAC3NzaC1lZDI1NTE5AAAAIPu6ntmyfSOkqLl3qPxD5XxwW7OONwwSG3KO+TGn+PFu");
+		AuthorizedKeyEntry keyEntry = entry.getKeyEntry();
+		assertNotNull(keyEntry);
+		assertEquals("ssh-unknown", keyEntry.getKeyType());
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index b6b528c..822ef55 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -9,7 +9,7 @@
 Bundle-Version: 7.2.0.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Export-Package: org.eclipse.jgit.internal.signing.ssh;version="7.2.0";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.internal.transport.sshd;version="7.2.0";x-internal:=true;
+ org.eclipse.jgit.internal.transport.sshd;version="7.2.0";x-friends:="org.eclipse.jgit.ssh.apache.test";
   uses:="org.apache.sshd.client,
    org.apache.sshd.client.auth,
    org.apache.sshd.client.auth.keyboard,
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
index 96829b7..6b2345d 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/KnownHostEntryReader.java
@@ -29,6 +29,7 @@
 import org.apache.sshd.client.config.hosts.KnownHostEntry;
 import org.apache.sshd.client.config.hosts.KnownHostHashValue;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -97,7 +98,7 @@ private static String clean(String line) {
 		return i < 0 ? line.trim() : line.substring(0, i).trim();
 	}
 
-	private static KnownHostEntry parseHostEntry(String line) {
+	static KnownHostEntry parseHostEntry(String line) {
 		KnownHostEntry entry = new KnownHostEntry();
 		entry.setConfigLine(line);
 		String tmp = line;
@@ -135,8 +136,8 @@ private static KnownHostEntry parseHostEntry(String line) {
 			entry.setPatterns(patterns);
 		}
 		tmp = tmp.substring(i + 1).trim();
-		AuthorizedKeyEntry key = AuthorizedKeyEntry
-				.parseAuthorizedKeyEntry(tmp);
+		AuthorizedKeyEntry key = PublicKeyEntry
+				.parsePublicKeyEntry(new AuthorizedKeyEntry(), tmp);
 		if (key == null) {
 			return null;
 		}
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
index bbff7d4..88f0806 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
@@ -28,7 +28,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
 import org.eclipse.jgit.internal.storage.file.Pack;
 import org.eclipse.jgit.internal.storage.file.PackFile;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -123,8 +122,7 @@ private void writeMidx(byte[] midx) throws IOException {
 	}
 
 	private File getMIdxStandardLocation() {
-		return new File(
-				((ObjectDirectory) db.getObjectDatabase()).getPackDirectory(),
+		return new File(db.getObjectDatabase().getPackDirectory(),
 				"multi-pack-index");
 	}
 
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 f5fd73b..661878f 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
@@ -797,7 +797,7 @@ public void testCloneWithAutoSetupRebase() throws Exception {
 		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();
@@ -813,7 +813,6 @@ public void testCloneWithAutoSetupRebase() throws Exception {
 		addRepoToClose(git2.getRepository());
 		assertEquals(BranchRebaseMode.REBASE,
 				git2.getRepository().getConfig().getEnum(
-						BranchRebaseMode.values(),
 						ConfigConstants.CONFIG_BRANCH_SECTION, "test",
 						ConfigConstants.CONFIG_KEY_REBASE,
 						BranchRebaseMode.NONE));
@@ -830,7 +829,6 @@ public void testCloneWithAutoSetupRebase() throws Exception {
 		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/RenameBranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RenameBranchCommandTest.java
index 534ebd9..add5886 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 void renameBranchSingleConfigValue() throws Exception {
 		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 void renameBranchMultipleConfigValues() throws Exception {
 		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 void renameBranchMultipleConfigValues() throws Exception {
 		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/blame/BlameGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
index f47f447..c2c06b2 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 @@
 
 /** 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 void testBoundLineDelete() throws Exception {
 				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 void testBoundLineDelete() throws Exception {
 				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 void testRenamedBoundLineDelete() throws Exception {
 			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 void testRenamedBoundLineDelete() throws Exception {
 			}
 
 			// 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 void testLinesAllDeletedShortenedWalk() throws Exception {
 		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/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java
index d403624..6792002 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 void testWindowsFile2() throws Exception {
 	}
 
 	@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/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 0713c38..f24127b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -124,7 +124,7 @@ private FetchRecurseSubmodulesMode getRecurseMode(String path) {
 		FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(
 				FetchRecurseSubmodulesMode.values(),
 				ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-				ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null);
+				ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES);
 		if (mode != null) {
 			return mode;
 		}
@@ -132,7 +132,7 @@ private FetchRecurseSubmodulesMode getRecurseMode(String path) {
 		// Fall back to fetch.recurseSubmodules, if set
 		mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
 				ConfigConstants.CONFIG_FETCH_SECTION, null,
-				ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null);
+				ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES);
 		if (mode != null) {
 			return mode;
 		}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index 83ae0fc..4b2cee4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -533,9 +533,9 @@ public static BranchRebaseMode getRebaseMode(String branchName,
 			Config config) {
 		BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(),
 				ConfigConstants.CONFIG_BRANCH_SECTION,
-				branchName, ConfigConstants.CONFIG_KEY_REBASE, null);
+				branchName, ConfigConstants.CONFIG_KEY_REBASE);
 		if (mode == null) {
-			mode = config.getEnum(BranchRebaseMode.values(),
+			mode = config.getEnum(
 					ConfigConstants.CONFIG_PULL_SECTION, null,
 					ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
 		}
@@ -549,7 +549,7 @@ private FastForwardMode getFastForwardMode() {
 		Config config = repo.getConfig();
 		Merge ffMode = config.getEnum(Merge.values(),
 				ConfigConstants.CONFIG_PULL_SECTION, null,
-				ConfigConstants.CONFIG_KEY_FF, null);
+				ConfigConstants.CONFIG_KEY_FF);
 		return ffMode != null ? FastForwardMode.valueOf(ffMode) : null;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java
new file mode 100644
index 0000000..d44fb5f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java
@@ -0,0 +1,46 @@
+/*
+ * 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.cache;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Keeps the blame information for a path at certain commit.
+ * <p>
+ * If there is a result, it covers the whole file at that revision
+ *
+ * @since 7.2
+ */
+public interface BlameCache {
+	/**
+	 * Gets the blame of a path at a given commit if available.
+	 * <p>
+	 * Since this cache is used in blame calculation, this get() method should
+	 * only retrieve the cache value, and not re-trigger blame calculation. In
+	 * other words, this acts as "getIfPresent", and not "computeIfAbsent".
+	 *
+	 * @param repo
+	 *            repository containing the commit
+	 * @param commitId
+	 *            we are looking at the file in this revision
+	 * @param path
+	 *            path a file in the repo
+	 *
+	 * @return the blame of a path at a given commit or null if not in cache
+	 * @throws IOException
+	 *             error retrieving/parsing values from storage
+	 */
+	List<CacheRegion> get(Repository repo, ObjectId commitId, String path)
+			throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java
new file mode 100644
index 0000000..6aa4eef
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java
@@ -0,0 +1,121 @@
+/*
+ * 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.cache;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Region of the blame of a file.
+ * <p>
+ * Usually all parameters are non-null, except when the Region was created
+ * to fill an unblamed gap (to cover for bugs in the calculation). In that
+ * case, path, commit and author will be null.
+ *
+ * @since 7.2
+ **/
+public class CacheRegion implements Comparable<CacheRegion> {
+	private final String sourcePath;
+
+	private final ObjectId sourceCommit;
+
+	private final int end;
+
+	private final int start;
+
+	/**
+	 * A blamed portion of a file
+	 *
+	 * @param path
+	 *            location of the file
+	 * @param commit
+	 *            commit that is modifying this region
+	 * @param start
+	 *            first line of this region (inclusive)
+	 * @param end
+	 *            last line of this region (non-inclusive!)
+	 */
+	public CacheRegion(String path, ObjectId commit,
+			int start, int end) {
+		allOrNoneNull(path, commit);
+		this.sourcePath = path;
+		this.sourceCommit = commit;
+		this.start = start;
+		this.end = end;
+	}
+
+	/**
+	 * First line of this region. Starting by 0, inclusive
+	 *
+	 * @return first line of this region.
+	 */
+	public int getStart() {
+		return start;
+	}
+
+	/**
+	 * One after last line in this region (or: last line non-inclusive)
+	 *
+	 * @return one after last line in this region.
+	 */
+	public int getEnd() {
+		return end;
+	}
+
+
+	/**
+	 * Path of the file this region belongs to
+	 *
+	 * @return path in the repo/commit
+	 */
+	public String getSourcePath() {
+		return sourcePath;
+	}
+
+	/**
+	 * Commit this region belongs to
+	 *
+	 * @return commit for this region
+	 */
+	public ObjectId getSourceCommit() {
+		return sourceCommit;
+	}
+
+	@Override
+	public int compareTo(CacheRegion o) {
+		return start - o.start;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		if (sourceCommit != null) {
+			sb.append(sourceCommit.name(), 0, 7).append(' ')
+					.append(" (")
+					.append(sourcePath).append(')');
+		} else {
+			sb.append("<unblamed region>");
+		}
+		sb.append(' ').append("start=").append(start).append(", count=")
+				.append(end - start);
+		return sb.toString();
+	}
+
+	private static void allOrNoneNull(String path, ObjectId commit) {
+		if (path != null && commit != null) {
+			return;
+		}
+
+		if (path == null && commit == null) {
+			return;
+		}
+		throw new IllegalArgumentException(String.format(
+				"expected all null or none: %s, %s", path, commit));
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index 34dba0b..c650d6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -1037,7 +1037,12 @@ private void updateSmudgedEntries() throws IOException {
 		}
 	}
 
-	enum DirCacheVersion implements ConfigEnum {
+	/**
+	 * DirCache versions
+	 *
+	 * @since 7.2
+	 */
+	public enum DirCacheVersion implements ConfigEnum {
 
 		/** Minimum index version on-disk format that we support. */
 		DIRC_VERSION_MINIMUM(2),
@@ -1060,6 +1065,9 @@ private DirCacheVersion(int versionCode) {
 			this.version = versionCode;
 		}
 
+		/**
+		 * @return the version code for this version
+		 */
 		public int getVersionCode() {
 			return version;
 		}
@@ -1078,6 +1086,13 @@ public boolean matchConfigValue(String in) {
 			}
 		}
 
+		/**
+		 * Create DirCacheVersion from integer value of the version code.
+		 *
+		 * @param val
+		 *            integer value of the version code.
+		 * @return the DirCacheVersion instance of the version code.
+		 */
 		public static DirCacheVersion fromInt(int val) {
 			for (DirCacheVersion v : DirCacheVersion.values()) {
 				if (val == v.getVersionCode()) {
@@ -1098,9 +1113,8 @@ public DirCacheConfig(Config cfg) {
 			boolean manyFiles = cfg.getBoolean(
 					ConfigConstants.CONFIG_FEATURE_SECTION,
 					ConfigConstants.CONFIG_KEY_MANYFILES, false);
-			indexVersion = cfg.getEnum(DirCacheVersion.values(),
-					ConfigConstants.CONFIG_INDEX_SECTION, null,
-					ConfigConstants.CONFIG_KEY_VERSION,
+			indexVersion = cfg.getEnum(ConfigConstants.CONFIG_INDEX_SECTION,
+					null, ConfigConstants.CONFIG_KEY_VERSION,
 					manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
 							: DirCacheVersion.DIRC_VERSION_EXTENDED);
 			skipHash = cfg.getBoolean(ConfigConstants.CONFIG_INDEX_SECTION,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
index 28d2fb2..bddf3ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
@@ -46,6 +46,8 @@
  * See <a href=
  * "https://git-scm.com/docs/pack-format#_multi_pack_index_midx_files_have_the_following_format">multipack
  * index format spec</a>
+ *
+ * @since 7.2
  */
 public class MultiPackIndexWriter {
 
@@ -86,7 +88,8 @@ public void write(ProgressMonitor monitor, OutputStream outputStream,
 			if (expectedSize != out.length()) {
 				throw new IllegalStateException(String.format(
 						JGitText.get().multiPackIndexUnexpectedSize,
-						expectedSize, out.length()));
+						Long.valueOf(expectedSize),
+						Long.valueOf(out.length())));
 			}
 		} catch (InterruptedIOException e) {
 			throw new IOException(JGitText.get().multiPackIndexWritingCancelled,
@@ -287,12 +290,13 @@ private void writeRidx(WriteContext ctx) throws IOException {
 			MidxMutableEntry e = iterator.next();
 			OffsetPosition op = new OffsetPosition(e.getOffset(), midxPosition);
 			midxPosition++;
-			packOffsets.computeIfAbsent(e.getPackId(), k -> new ArrayList<>())
-					.add(op);
+			packOffsets.computeIfAbsent(Integer.valueOf(e.getPackId()),
+					k -> new ArrayList<>()).add(op);
 		}
 
 		for (int i = 0; i < ctx.data.getPackCount(); i++) {
-			List<OffsetPosition> offsetsForPack = packOffsets.get(i);
+			List<OffsetPosition> offsetsForPack = packOffsets
+					.get(Integer.valueOf(i));
 			if (offsetsForPack.isEmpty()) {
 				continue;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
index e15c7af..7921052 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -187,8 +187,7 @@ public boolean isRebase() {
 	 * @since 4.5
 	 */
 	public BranchRebaseMode getRebaseMode() {
-		return config.getEnum(BranchRebaseMode.values(),
-				ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
+		return config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
 				ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 6509398..3059f28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -32,10 +32,12 @@
  */
 public class DefaultTypedConfigGetter implements TypedConfigGetter {
 
+	@SuppressWarnings("boxed")
 	@Override
 	public boolean getBoolean(Config config, String section, String subsection,
 			String name, boolean defaultValue) {
-		return getBoolean(config, section, subsection, name, defaultValue);
+		return neverNull(getBoolean(config, section, subsection, name,
+				Boolean.valueOf(defaultValue)));
 	}
 
 	@Nullable
@@ -116,7 +118,8 @@ public <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
 	@Override
 	public int getInt(Config config, String section, String subsection,
 			String name, int defaultValue) {
-		return getInt(config, section, subsection, name, defaultValue);
+		return neverNull(getInt(config, section, subsection, name,
+				Integer.valueOf(defaultValue)));
 	}
 
 	@Nullable
@@ -144,8 +147,8 @@ public Integer getInt(Config config, String section, String subsection,
 	@Override
 	public int getIntInRange(Config config, String section, String subsection,
 			String name, int minValue, int maxValue, int defaultValue) {
-		return getIntInRange(config, section, subsection, name, minValue,
-				maxValue, defaultValue);
+		return neverNull(getIntInRange(config, section, subsection, name,
+				minValue, maxValue, Integer.valueOf(defaultValue)));
 	}
 
 	@Override
@@ -161,9 +164,9 @@ public Integer getIntInRange(Config config, String section,
 			return val;
 		}
 		if (subsection == null) {
-			throw new IllegalArgumentException(MessageFormat.format(
-					JGitText.get().integerValueNotInRange, section, name,
-					val, minValue, maxValue));
+			throw new IllegalArgumentException(
+					MessageFormat.format(JGitText.get().integerValueNotInRange,
+							section, name, val, minValue, maxValue));
 		}
 		throw new IllegalArgumentException(MessageFormat.format(
 				JGitText.get().integerValueNotInRangeSubSection, section,
@@ -173,7 +176,8 @@ public Integer getIntInRange(Config config, String section,
 	@Override
 	public long getLong(Config config, String section, String subsection,
 			String name, long defaultValue) {
-		return getLong(config, section, subsection, name, defaultValue);
+		return neverNull(getLong(config, section, subsection, name,
+				Long.valueOf(defaultValue)));
 	}
 
 	@Nullable
@@ -190,8 +194,9 @@ public Long getLong(Config config, String section, String subsection,
 			// Empty
 			return defaultValue;
 		} catch (NumberFormatException nfe) {
-			throw new IllegalArgumentException(MessageFormat.format(
-					JGitText.get().invalidIntegerValue, section, name, str),
+			throw new IllegalArgumentException(
+					MessageFormat.format(JGitText.get().invalidIntegerValue,
+							section, name, str),
 					nfe);
 		}
 	}
@@ -199,9 +204,8 @@ public Long getLong(Config config, String section, String subsection,
 	@Override
 	public long getTimeUnit(Config config, String section, String subsection,
 			String name, long defaultValue, TimeUnit wantUnit) {
-		Long v = getTimeUnit(config, section, subsection, name,
-				Long.valueOf(defaultValue), wantUnit);
-		return v == null ? defaultValue : v.longValue();
+		return neverNull(getTimeUnit(config, section, subsection, name,
+				Long.valueOf(defaultValue), wantUnit));
 	}
 
 	@Override
@@ -325,4 +329,14 @@ public List<RefSpec> getRefSpecs(Config config, String section,
 		}
 		return result;
 	}
+
+	// Trick for the checkers. When we use this, one is never null, but
+	// they don't know.
+	@NonNull
+	private static <T> T neverNull(T one) {
+		if (one == null) {
+			throw new IllegalArgumentException();
+		}
+		return one;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
index 76ed36a..23d16db 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
@@ -74,8 +74,7 @@ public String toConfigValue() {
 	 *            the config to read from
 	 */
 	public GpgConfig(Config config) {
-		keyFormat = config.getEnum(GpgFormat.values(),
-				ConfigConstants.CONFIG_GPG_SECTION, null,
+		keyFormat = config.getEnum(ConfigConstants.CONFIG_GPG_SECTION, null,
 				ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP);
 		signingKey = config.getString(ConfigConstants.CONFIG_USER_SECTION, null,
 				ConfigConstants.CONFIG_KEY_SIGNINGKEY);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index becc808..105cba7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -787,14 +787,14 @@ public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
 		IgnoreSubmoduleMode mode = repoConfig.getEnum(
 				IgnoreSubmoduleMode.values(),
 				ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
-				ConfigConstants.CONFIG_KEY_IGNORE, null);
+				ConfigConstants.CONFIG_KEY_IGNORE);
 		if (mode != null) {
 			return mode;
 		}
 		lazyLoadModulesConfig();
-		return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
-				ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
-				ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
+		return modulesConfig.getEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION,
+				getModuleName(), ConfigConstants.CONFIG_KEY_IGNORE,
+				IgnoreSubmoduleMode.NONE);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
index 73eddb8..f10b7bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -302,8 +302,7 @@ private void init(Config config, URIish uri) {
 		int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY,
 				1 * 1024 * 1024);
 		boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true);
-		HttpRedirectMode followRedirectsMode = config.getEnum(
-				HttpRedirectMode.values(), HTTP, null,
+		HttpRedirectMode followRedirectsMode = config.getEnum(HTTP, null,
 				FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL);
 		int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY,
 				MAX_REDIRECTS);
@@ -335,8 +334,8 @@ private void init(Config config, URIish uri) {
 					postBufferSize);
 			sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY,
 					sslVerifyFlag);
-			followRedirectsMode = config.getEnum(HttpRedirectMode.values(),
-					HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode);
+			followRedirectsMode = config.getEnum(HTTP, match,
+					FOLLOW_REDIRECTS_KEY, followRedirectsMode);
 			int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY,
 					redirectLimit);
 			if (newMaxRedirects >= 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index 4de6ff8..7b5842b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -82,7 +82,7 @@ public class URIish implements Serializable {
 	 * Part of a pattern which matches a relative path. Relative paths don't
 	 * start with slash or drive letters. Defines no capturing group.
 	 */
-	private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*[^\\\\/]+[\\\\/]*)"; //$NON-NLS-1$
+	private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*+[^\\\\/]*)"; //$NON-NLS-1$
 
 	/**
 	 * Part of a pattern which matches a relative or absolute path. Defines no
@@ -120,7 +120,7 @@ public class URIish implements Serializable {
 	 * path (maybe even containing windows drive-letters) or a relative path.
 	 */
 	private static final Pattern LOCAL_FILE = Pattern.compile("^" // //$NON-NLS-1$
-			+ "([\\\\/]?" + PATH_P + ")" // //$NON-NLS-1$ //$NON-NLS-2$
+			+ "([\\\\/]?+" + PATH_P + ")" // //$NON-NLS-1$ //$NON-NLS-2$
 			+ "$"); //$NON-NLS-1$
 
 	/**