Merge "Merge: Add diff3 style merge conflict formatter."
diff --git a/WORKSPACE b/WORKSPACE
index 1de71a2..5120ece 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -118,8 +118,8 @@
 
 maven_jar(
     name = "commons-codec",
-    artifact = "commons-codec:commons-codec:1.15",
-    sha1 = "49d94806b6e3dc933dacbd8acb0fdbab8ebd1e5d",
+    artifact = "commons-codec:commons-codec:1.16.0",
+    sha1 = "4e3eb3d79888d76b54e28b350915b5dc3919c9de",
 )
 
 maven_jar(
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
index eba283c..bf0aa3d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.17" sequenceNumber="1688222406">
+<target name="jgit-4.17" sequenceNumber="1690316787">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
index 0b4acdf..e168762 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.18" sequenceNumber="1688222406">
+<target name="jgit-4.18" sequenceNumber="1690316787">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
index f224ad2..df9670f 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.19-staging" sequenceNumber="1688222406">
+<target name="jgit-4.19-staging" sequenceNumber="1690316787">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
index fafedc8..ae12536 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.20" sequenceNumber="1688222406">
+<target name="jgit-4.20" sequenceNumber="1690316787">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
index 1cfc310..ebd551d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.21" sequenceNumber="1688222406">
+<target name="jgit-4.21" sequenceNumber="1690316787">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
index 70626ec..542afa5 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.22.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.22" sequenceNumber="1688222404">
+<target name="jgit-4.22" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.23.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.23.target
index 57662a3..4442d42 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.23.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.23.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.23" sequenceNumber="1688222404">
+<target name="jgit-4.23" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.24.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.24.target
index 17916c9..70c541b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.24.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.24.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.24" sequenceNumber="1688222405">
+<target name="jgit-4.24" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.25.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.25.target
index 79d81ab..311ac44 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.25.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.25.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.25" sequenceNumber="1688222404">
+<target name="jgit-4.25" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.26.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.26.target
index 65ae60a..3a8ee95 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.26.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.26.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.26" sequenceNumber="1688222404">
+<target name="jgit-4.26" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.27.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.27.target
index 0f00993..7953712 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.27.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.27.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.27" sequenceNumber="1688222404">
+<target name="jgit-4.27" sequenceNumber="1690316786">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.28.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.28.target
index 3fcb72a..268a3a6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.28.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.28.target
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.28" sequenceNumber="1688222404">
+<target name="jgit-4.28" sequenceNumber="1690316785">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20221112-0806"/>
@@ -249,7 +249,7 @@
     	<dependency>
     		<groupId>commons-codec</groupId>
     		<artifactId>commons-codec</artifactId>
-    		<version>1.15</version>
+    		<version>1.16.0</version>
     		<type>jar</type>
     	</dependency>
     	<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/maven/dependencies.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/maven/dependencies.tpd
index cc7ea61..c9fdb6e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/maven/dependencies.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/maven/dependencies.tpd
@@ -10,7 +10,7 @@
 	dependency {
 		groupId = "commons-codec"
 		artifactId = "commons-codec"
-		version = "1.15"
+		version = "1.16.0"
 	}
 	dependency {
 		groupId = "org.apache.commons"
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
index 9f1df89..eb55ec0 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java
@@ -466,7 +466,7 @@ public boolean hasNext() {
 
 				@Override
 				public KeyAgentIdentity next() {
-					if (hasElement == null && !hasNext()
+					if ((hasElement == null && !hasNext())
 							|| !hasElement.booleanValue()) {
 						throw new NoSuchElementException();
 					}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.corrupt.rev b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.corrupt.rev
new file mode 100644
index 0000000..74283a2
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.corrupt.rev
Binary files differ
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.rev b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.rev
new file mode 100644
index 0000000..6ac7d65
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.rev
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputedTest.java
index 4facd6e..ea5aaf5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputedTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputedTest.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.fail;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -92,6 +93,12 @@ public void testFindNextOffsetWrongOffset() {
 		}
 	}
 
+	@Test
+	public void testVerifyChecksum() throws PackMismatchException {
+		// ComputedReverseIndex doesn't have a file containing a checksum.
+		reverseIdx.verifyPackChecksum(null);
+	}
+
 	private long findFirstOffset() {
 		long min = Long.MAX_VALUE;
 		for (MutableEntry me : idx)
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
index 84fc58e..f8fb4c1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexTest.java
@@ -10,13 +10,52 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.junit.Test;
 
 public class PackReverseIndexTest {
+
+	@Test
+	public void open_fallbackToComputed() throws IOException {
+		String noRevFilePrefix = "pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.";
+		PackReverseIndex computed = PackReverseIndexFactory.openOrCompute(
+				getResourceFileFor(noRevFilePrefix, PackExt.REVERSE_INDEX), 7,
+				() -> PackIndex.open(
+						getResourceFileFor(noRevFilePrefix, PackExt.INDEX)));
+
+		assertTrue(computed instanceof PackReverseIndexComputed);
+	}
+
+	@Test
+	public void open_readGoodFile() throws IOException {
+		String hasRevFilePrefix = "pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.";
+		PackReverseIndex version1 = PackReverseIndexFactory.openOrCompute(
+				getResourceFileFor(hasRevFilePrefix, PackExt.REVERSE_INDEX), 6,
+				() -> PackIndex.open(
+						getResourceFileFor(hasRevFilePrefix, PackExt.INDEX)));
+
+		assertTrue(version1 instanceof PackReverseIndexV1);
+	}
+
+	@Test
+	public void open_readCorruptFile() {
+		String hasRevFilePrefix = "pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.";
+
+		assertThrows(IOException.class,
+				() -> PackReverseIndexFactory.openOrCompute(
+						getResourceFileFor(hasRevFilePrefix + "corrupt.",
+								PackExt.REVERSE_INDEX),
+						6, () -> PackIndex.open(getResourceFileFor(
+								hasRevFilePrefix, PackExt.INDEX))));
+	}
+
 	@Test
 	public void read_badMagic() {
 		byte[] badMagic = new byte[] { 'R', 'B', 'A', 'D', // magic
@@ -53,4 +92,9 @@ public void read_unsupportedVersion2() {
 		assertThrows(IOException.class,
 				() -> PackReverseIndexFactory.readFromFile(in, 0, () -> null));
 	}
+
+	private File getResourceFileFor(String packFilePrefix, PackExt ext) {
+		return JGitTestUtil
+				.getTestResourceFile(packFilePrefix + ext.getExtension());
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1Test.java
index b4849f9..38b28b5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1Test.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1Test.java
@@ -15,11 +15,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.transport.PackedObjectInfo;
@@ -147,6 +150,26 @@ public void read_objectCountTooLarge() {
 	}
 
 	@Test
+	public void read_incorrectChecksum() {
+		byte[] badChecksum = new byte[] { 'R', 'I', 'D', 'X', // magic
+				0x00, 0x00, 0x00, 0x01, // file version
+				0x00, 0x00, 0x00, 0x01, // oid version
+				// pack checksum to copy into at byte 12
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				// checksum
+				(byte) 0xf2, 0x1a, 0x1a, (byte) 0xaa, 0x32, 0x2d, (byte) 0xb9,
+				(byte) 0xfd, 0x0f, (byte) 0xa5, 0x4c, (byte) 0xea, (byte) 0xcf,
+				(byte) 0xbb, (byte) 0x99, (byte) 0xde, (byte) 0xd3, 0x4e,
+				(byte) 0xb1, (byte) 0xee, // would be 0x74 if correct
+		};
+		System.arraycopy(FAKE_PACK_CHECKSUM, 0, badChecksum, 12,
+				FAKE_PACK_CHECKSUM.length);
+		ByteArrayInputStream in = new ByteArrayInputStream(badChecksum);
+		assertThrows(CorruptObjectException.class,
+				() -> PackReverseIndexFactory.readFromFile(in, 0, () -> null));
+	}
+
+	@Test
 	public void findObject_noObjects() {
 		assertNull(emptyReverseIndex.findObject(0));
 	}
@@ -235,6 +258,26 @@ public void findObjectByPosition_badOffset() {
 				() -> smallReverseIndex.findObjectByPosition(10));
 	}
 
+	@Test
+	public void verifyChecksum_match() throws IOException {
+		smallReverseIndex.verifyPackChecksum("smallPackFilePath");
+	}
+
+	@Test
+	public void verifyChecksum_mismatch() throws IOException {
+		ByteArrayInputStream in = new ByteArrayInputStream(NO_OBJECTS);
+		PackIndex mockForwardIndex = mock(PackIndex.class);
+		when(mockForwardIndex.getChecksum()).thenReturn(
+				new byte[] { 'D', 'I', 'F', 'F', 'P', 'A', 'C', 'K', 'C', 'H',
+						'E', 'C', 'K', 'S', 'U', 'M', '7', '8', '9', '0', });
+		PackReverseIndex reverseIndex = PackReverseIndexFactory.readFromFile(in,
+				0,
+				() -> mockForwardIndex);
+
+		assertThrows(PackMismatchException.class,
+				() -> reverseIndex.verifyPackChecksum("packFilePath"));
+	}
+
 	private static PackedObjectInfo objectInfo(String objectId, int type,
 			long offset) {
 		PackedObjectInfo objectInfo = new PackedObjectInfo(
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java
index 113f3be..01f6a3a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java
@@ -16,76 +16,74 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.io.IOException;
 import java.util.List;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
 
 public class FooterLineTest extends RepositoryTestCase {
 	@Test
-	public void testNoFooters_EmptyBody() throws IOException {
-		final RevCommit commit = parse("");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_EmptyBody() {
+		String msg = buildMessage("");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_NewlineOnlyBody1() throws IOException {
-		final RevCommit commit = parse("\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_NewlineOnlyBody1() {
+		String msg = buildMessage("\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_NewlineOnlyBody5() throws IOException {
-		final RevCommit commit = parse("\n\n\n\n\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_NewlineOnlyBody5() {
+		String msg = buildMessage("\n\n\n\n\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_OneLineBodyNoLF() throws IOException {
-		final RevCommit commit = parse("this is a commit");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_OneLineBodyNoLF() {
+		String msg = buildMessage("this is a commit");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_OneLineBodyWithLF() throws IOException {
-		final RevCommit commit = parse("this is a commit\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_OneLineBodyWithLF() {
+		String msg = buildMessage("this is a commit\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_ShortBodyNoLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_ShortBodyNoLF() {
+		String msg = buildMessage("subject\n\nbody of commit");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testNoFooters_ShortBodyWithLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNoFooters_ShortBodyWithLF() {
+		String msg = buildMessage("subject\n\nbody of commit\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		assertNotNull(footers);
 		assertEquals(0, footers.size());
 	}
 
 	@Test
-	public void testSignedOffBy_OneUserNoLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Signed-off-by: A. U. Thor <a@example.com>");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testSignedOffBy_OneUserNoLF() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Signed-off-by: A. U. Thor <a@example.com>");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -98,10 +96,10 @@ public void testSignedOffBy_OneUserNoLF() throws IOException {
 	}
 
 	@Test
-	public void testSignedOffBy_OneUserWithLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Signed-off-by: A. U. Thor <a@example.com>\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testSignedOffBy_OneUserWithLF() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Signed-off-by: A. U. Thor <a@example.com>\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -114,14 +112,13 @@ public void testSignedOffBy_OneUserWithLF() throws IOException {
 	}
 
 	@Test
-	public void testSignedOffBy_IgnoreWhitespace() throws IOException {
+	public void testSignedOffBy_IgnoreWhitespace() {
 		// We only ignore leading whitespace on the value, trailing
 		// is assumed part of the value.
 		//
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Signed-off-by:   A. U. Thor <a@example.com>  \n");
-		final List<FooterLine> footers = commit.getFooterLines();
-		FooterLine f;
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Signed-off-by:   A. U. Thor <a@example.com>  \n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);		FooterLine f;
 
 		assertNotNull(footers);
 		assertEquals(1, footers.size());
@@ -133,10 +130,10 @@ public void testSignedOffBy_IgnoreWhitespace() throws IOException {
 	}
 
 	@Test
-	public void testEmptyValueNoLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Signed-off-by:");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testEmptyValueNoLF() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Signed-off-by:");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -149,10 +146,10 @@ public void testEmptyValueNoLF() throws IOException {
 	}
 
 	@Test
-	public void testEmptyValueWithLF() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Signed-off-by:\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testEmptyValueWithLF() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Signed-off-by:\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -165,10 +162,10 @@ public void testEmptyValueWithLF() throws IOException {
 	}
 
 	@Test
-	public void testShortKey() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "K:V\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testShortKey() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "K:V\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -181,10 +178,10 @@ public void testShortKey() throws IOException {
 	}
 
 	@Test
-	public void testNonDelimtedEmail() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Acked-by: re@example.com\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNonDelimtedEmail() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Acked-by: re@example.com\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -197,10 +194,10 @@ public void testNonDelimtedEmail() throws IOException {
 	}
 
 	@Test
-	public void testNotEmail() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n"
-				+ "Acked-by: Main Tain Er\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testNotEmail() {
+		String msg = buildMessage("subject\n\nbody of commit\n" + "\n"
+			+ "Acked-by: Main Tain Er\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -213,15 +210,15 @@ public void testNotEmail() throws IOException {
 	}
 
 	@Test
-	public void testSignedOffBy_ManyUsers() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n"
-				+ "Not-A-Footer-Line: this line must not be read as a footer\n"
-				+ "\n" // paragraph break, now footers appear in final block
-				+ "Signed-off-by: A. U. Thor <a@example.com>\n"
-				+ "CC:            <some.mailing.list@example.com>\n"
-				+ "Acked-by: Some Reviewer <sr@example.com>\n"
-				+ "Signed-off-by: Main Tain Er <mte@example.com>\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testSignedOffBy_ManyUsers() {
+		String msg = buildMessage("subject\n\nbody of commit\n"
+			+ "Not-A-Footer-Line: this line must not be read as a footer\n"
+			+ "\n" // paragraph break, now footers appear in final block
+			+ "Signed-off-by: A. U. Thor <a@example.com>\n"
+			+ "CC:            <some.mailing.list@example.com>\n"
+			+ "Acked-by: Some Reviewer <sr@example.com>\n"
+			+ "Signed-off-by: Main Tain Er <mte@example.com>\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -249,16 +246,16 @@ public void testSignedOffBy_ManyUsers() throws IOException {
 	}
 
 	@Test
-	public void testSignedOffBy_SkipNonFooter() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n"
-				+ "Not-A-Footer-Line: this line must not be read as a footer\n"
-				+ "\n" // paragraph break, now footers appear in final block
-				+ "Signed-off-by: A. U. Thor <a@example.com>\n"
-				+ "CC:            <some.mailing.list@example.com>\n"
-				+ "not really a footer line but we'll skip it anyway\n"
-				+ "Acked-by: Some Reviewer <sr@example.com>\n"
-				+ "Signed-off-by: Main Tain Er <mte@example.com>\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testSignedOffBy_SkipNonFooter() {
+		String msg = buildMessage("subject\n\nbody of commit\n"
+			+ "Not-A-Footer-Line: this line must not be read as a footer\n"
+			+ "\n" // paragraph break, now footers appear in final block
+			+ "Signed-off-by: A. U. Thor <a@example.com>\n"
+			+ "CC:            <some.mailing.list@example.com>\n"
+			+ "not really a footer line but we'll skip it anyway\n"
+			+ "Acked-by: Some Reviewer <sr@example.com>\n"
+			+ "Signed-off-by: Main Tain Er <mte@example.com>\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 		FooterLine f;
 
 		assertNotNull(footers);
@@ -282,15 +279,16 @@ public void testSignedOffBy_SkipNonFooter() throws IOException {
 	}
 
 	@Test
-	public void testFilterFootersIgnoreCase() throws IOException {
-		final RevCommit commit = parse("subject\n\nbody of commit\n"
-				+ "Not-A-Footer-Line: this line must not be read as a footer\n"
-				+ "\n" // paragraph break, now footers appear in final block
-				+ "Signed-Off-By: A. U. Thor <a@example.com>\n"
-				+ "CC:            <some.mailing.list@example.com>\n"
-				+ "Acked-by: Some Reviewer <sr@example.com>\n"
-				+ "signed-off-by: Main Tain Er <mte@example.com>\n");
-		final List<String> footers = commit.getFooterLines("signed-off-by");
+	public void testFilterFootersIgnoreCase() {
+		String msg = buildMessage("subject\n\nbody of commit\n"
+			+ "Not-A-Footer-Line: this line must not be read as a footer\n"
+			+ "\n" // paragraph break, now footers appear in final block
+			+ "Signed-Off-By: A. U. Thor <a@example.com>\n"
+			+ "CC:            <some.mailing.list@example.com>\n"
+			+ "Acked-by: Some Reviewer <sr@example.com>\n"
+			+ "signed-off-by: Main Tain Er <mte@example.com>\n");
+		List<String> footers = FooterLine.getValues(
+			FooterLine.fromMessage(msg), "signed-off-by");
 
 		assertNotNull(footers);
 		assertEquals(2, footers.size());
@@ -300,38 +298,33 @@ public void testFilterFootersIgnoreCase() throws IOException {
 	}
 
 	@Test
-	public void testMatchesBugId() throws IOException {
-		final RevCommit commit = parse("this is a commit subject for test\n"
-				+ "\n" // paragraph break, now footers appear in final block
-				+ "Simple-Bug-Id: 42\n");
-		final List<FooterLine> footers = commit.getFooterLines();
+	public void testMatchesBugId() {
+		String msg = buildMessage("this is a commit subject for test\n"
+			+ "\n" // paragraph break, now footers appear in final block
+			+ "Simple-Bug-Id: 42\n");
+		List<FooterLine> footers = FooterLine.fromMessage(msg);
 
 		assertNotNull(footers);
 		assertEquals(1, footers.size());
 
-		final FooterLine line = footers.get(0);
+		FooterLine line = footers.get(0);
 		assertNotNull(line);
 		assertEquals("Simple-Bug-Id", line.getKey());
 		assertEquals("42", line.getValue());
 
-		final FooterKey bugid = new FooterKey("Simple-Bug-Id");
+		FooterKey bugid = new FooterKey("Simple-Bug-Id");
 		assertTrue("matches Simple-Bug-Id", line.matches(bugid));
 		assertFalse("not Signed-off-by", line.matches(FooterKey.SIGNED_OFF_BY));
 		assertFalse("not CC", line.matches(FooterKey.CC));
 	}
 
-	private RevCommit parse(String msg) throws IOException {
-		final StringBuilder buf = new StringBuilder();
+	private String buildMessage(String msg) {
+		StringBuilder buf = new StringBuilder();
 		buf.append("tree " + ObjectId.zeroId().name() + "\n");
 		buf.append("author A. U. Thor <a@example.com> 1 +0000\n");
 		buf.append("committer A. U. Thor <a@example.com> 1 +0000\n");
 		buf.append("\n");
 		buf.append(msg);
-
-		try (RevWalk walk = new RevWalk(db)) {
-			RevCommit c = new RevCommit(ObjectId.zeroId());
-			c.parseCanonical(walk, Constants.encode(buf.toString()));
-			return c;
-		}
+		return buf.toString();
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
index c62136e..5203e3f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java
@@ -27,9 +27,17 @@ public class RevWalkFollowFilterTest extends RevWalkTestCase {
 	private static class DiffCollector extends RenameCallback {
 		List<DiffEntry> diffs = new ArrayList<>();
 
+		List<RevCommit> commits = new ArrayList<>();
+
 		@Override
 		public void renamed(DiffEntry diff) {
+			throw new UnsupportedOperationException("unimplemented");
+		}
+
+		@Override
+		public void renamed(DiffEntry diff, RevCommit commit) {
 			diffs.add(diff);
+			commits.add(commit);
 		}
 	}
 
@@ -77,6 +85,7 @@ public void testSingleRename() throws Exception {
 		assertNull(rw.next());
 
 		assertRenames("a->b");
+		assertRenameCommits(renameCommit);
 	}
 
 	@Test
@@ -108,6 +117,7 @@ public void testMultiRename() throws Exception {
 		assertNull(rw.next());
 
 		assertRenames("c->a", "b->c", "a->b");
+		assertRenameCommits(renameCommit3, renameCommit2, renameCommit1);
 	}
 
 	/**
@@ -136,6 +146,20 @@ protected void assertRenames(String... expectedRenames) {
 		}
 	}
 
+	protected void assertRenameCommits(RevCommit... expectedCommits) {
+		Assert.assertEquals(
+				"Unexpected number of rename commits. Expected: "
+						+ expectedCommits.length + ", actual: "
+						+ diffCollector.diffs.size(),
+				expectedCommits.length, diffCollector.diffs.size());
+
+		for (int i = 0; i < expectedCommits.length; i++) {
+			RevCommit renameCommit = diffCollector.commits.get(i);
+			Assert.assertNotNull(renameCommit);
+			Assert.assertEquals(expectedCommits[i], renameCommit);
+		}
+	}
+
 	protected void assertNoRenames() {
 		Assert.assertEquals("Found unexpected rename/copy diff", 0,
 				diffCollector.diffs.size());
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 4935032..84b2987 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -236,6 +236,7 @@
  javax.crypto,
  javax.management,
  javax.net.ssl,
+ org.apache.commons.codec.digest;version="1.15.0",
  org.slf4j;version="[1.7.0,2.0.0)",
  org.xml.sax,
  org.xml.sax.helpers
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 23f5d23..366d370 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -49,7 +49,7 @@
     <dependency>
       <groupId>commons-codec</groupId>
       <artifactId>commons-codec</artifactId>
-      <version>1.15</version>
+      <version>1.16.0</version>
     </dependency>
 
   </dependencies>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index bf8a1ef..c73d85f 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -223,6 +223,7 @@
 corruptObjectTruncatedInName=truncated in name
 corruptObjectTruncatedInObjectId=truncated in object id
 corruptObjectZeroId=entry points to null SHA-1
+corruptReverseIndexChecksumIncorrect=Reverse index checksum incorrect: written as {0} but digest was {1}
 corruptUseCnt=close() called when useCnt is already zero for {0}
 couldNotGetAdvertisedRef=Remote {0} did not advertise Ref for branch {1}. This Ref may not exist in the remote or may be hidden by permission settings.
 couldNotGetRepoStatistics=Could not get repository statistics
@@ -566,7 +567,7 @@
 operationCanceled=Operation {0} was canceled
 outputHasAlreadyBeenStarted=Output has already been started.
 overflowedReftableBlock=Overflowed reftable block
-packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: .pack has {1} whilst .idx has {2}
+packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: {1} has {2} whilst {3} has {4}
 packCorruptedWhileWritingToFilesystem=Pack corrupted while writing to filesystem
 packedRefsHandleIsStale=packed-refs handle is stale, {0}. retry
 packetSizeMustBeAtLeast=packet size {0} must be >= {1}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index a032d24..91d5322 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -252,6 +252,7 @@ public static JGitText get() {
 	/***/ public String corruptObjectTruncatedInName;
 	/***/ public String corruptObjectTruncatedInObjectId;
 	/***/ public String corruptObjectZeroId;
+	/***/ public String corruptReverseIndexChecksumIncorrect;
 	/***/ public String corruptPack;
 	/***/ public String corruptUseCnt;
 	/***/ public String couldNotFindTabInLine;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
index 9d1c339..c5cfe95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
@@ -48,6 +48,7 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.NB;
@@ -405,25 +406,27 @@ private BloomFilterChunks computeBloomFilterChunks(Stats stats)
 		data.write(scratch);
 		int dataHeaderSize = data.size();
 
-		for (RevCommit cmit : graphCommits) {
-			ChangedPathFilter cpf = cmit.getChangedPathFilter();
-			if (cpf != null) {
-				stats.changedPathFiltersReused++;
-			} else {
-				stats.changedPathFiltersComputed++;
-				Optional<HashSet<ByteBuffer>> paths = computeBloomFilterPaths(
-						graphCommits.getObjectReader(), cmit);
-				if (paths.isEmpty()) {
-					cpf = ChangedPathFilter.FULL;
+		try (RevWalk rw = new RevWalk(graphCommits.getObjectReader())) {
+			for (RevCommit cmit : graphCommits) {
+				ChangedPathFilter cpf = cmit.getChangedPathFilter(rw);
+				if (cpf != null) {
+					stats.changedPathFiltersReused++;
 				} else {
-					cpf = ChangedPathFilter.fromPaths(paths.get());
+					stats.changedPathFiltersComputed++;
+					Optional<HashSet<ByteBuffer>> paths = computeBloomFilterPaths(
+							graphCommits.getObjectReader(), cmit);
+					if (paths.isEmpty()) {
+						cpf = ChangedPathFilter.FULL;
+					} else {
+						cpf = ChangedPathFilter.fromPaths(paths.get());
+					}
 				}
+				cpf.writeTo(data);
+				NB.encodeInt32(scratch, 0, data.size() - dataHeaderSize);
+				index.write(scratch);
 			}
-			cpf.writeTo(data);
-			NB.encodeInt32(scratch, 0, data.size() - dataHeaderSize);
-			index.write(scratch);
+			return new BloomFilterChunks(index, data);
 		}
-		return new BloomFilterChunks(index, data);
 	}
 
 	private void writeExtraEdges(CancellableDigestOutputStream out)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
index 782cbea..2b5586a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
@@ -14,6 +14,7 @@
 
 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
 
 import java.io.EOFException;
 import java.io.File;
@@ -50,12 +51,14 @@
 import org.eclipse.jgit.errors.UnsupportedPackVersionException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.util.Hex;
 import org.eclipse.jgit.util.LongList;
 import org.eclipse.jgit.util.NB;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -177,10 +180,10 @@ private PackIndex idx() throws IOException {
 							throw new PackMismatchException(MessageFormat
 									.format(JGitText.get().packChecksumMismatch,
 											packFile.getPath(),
-											ObjectId.fromRaw(packChecksum)
-													.name(),
-											ObjectId.fromRaw(idx.packChecksum)
-													.name()));
+											PackExt.PACK.getExtension(),
+											Hex.toHexString(packChecksum),
+											PackExt.INDEX.getExtension(),
+											Hex.toHexString(idx.packChecksum)));
 						}
 						loadedIdx = idx;
 					} catch (InterruptedIOException e) {
@@ -765,11 +768,11 @@ private void onOpenPack() throws IOException {
 		fd.seek(length - 20);
 		fd.readFully(buf, 0, 20);
 		if (!Arrays.equals(buf, packChecksum)) {
-			throw new PackMismatchException(MessageFormat.format(
-					JGitText.get().packChecksumMismatch,
-					getPackFile(),
-					ObjectId.fromRaw(buf).name(),
-					ObjectId.fromRaw(idx.packChecksum).name()));
+			throw new PackMismatchException(
+					MessageFormat.format(JGitText.get().packChecksumMismatch,
+							getPackFile(), PackExt.PACK.getExtension(),
+							Hex.toHexString(buf), PackExt.INDEX.getExtension(),
+							Hex.toHexString(idx.packChecksum)));
 		}
 	}
 
@@ -1148,8 +1151,15 @@ synchronized PackBitmapIndex getBitmapIndex() throws IOException {
 	}
 
 	private synchronized PackReverseIndex getReverseIdx() throws IOException {
-		if (reverseIdx == null)
-			reverseIdx = PackReverseIndexFactory.computeFromIndex(idx());
+		if (invalid) {
+			throw new PackInvalidException(packFile, invalidatingCause);
+		}
+		if (reverseIdx == null) {
+			PackFile reverseIndexFile = packFile.create(REVERSE_INDEX);
+			reverseIdx = PackReverseIndexFactory.openOrCompute(reverseIndexFile,
+					getObjectCount(), () -> getIndex());
+			reverseIdx.verifyPackChecksum(getPackFile().getPath());
+		}
 		return reverseIdx;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
index 74ea890..ef9753c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
@@ -11,6 +11,7 @@
 package org.eclipse.jgit.internal.storage.file;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.lib.ObjectId;
 
 /**
@@ -35,6 +36,17 @@ public interface PackReverseIndex {
 	int VERSION_1 = 1;
 
 	/**
+	 * Verify that the pack checksum found in the reverse index matches that
+	 * from the pack file.
+	 *
+	 * @param packFilePath
+	 *            the path to display in event of a mismatch
+	 * @throws PackMismatchException
+	 *             if the checksums do not match
+	 */
+	void verifyPackChecksum(String packFilePath) throws PackMismatchException;
+
+	/**
 	 * Search for object id with the specified start offset in this pack
 	 * (reverse) index.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java
index d6eaa96..0b487a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java
@@ -12,6 +12,7 @@
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.lib.ObjectId;
@@ -144,6 +145,12 @@ final class PackReverseIndexComputed implements PackReverseIndex {
 	}
 
 	@Override
+	public void verifyPackChecksum(String packFilePath)
+			throws PackMismatchException {
+		// There is no file with a checksum.
+	}
+
+	@Override
 	public ObjectId findObject(long offset) {
 		final int ith = binarySearch(offset);
 		if (ith < 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java
index b16da5a..32830c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java
@@ -14,6 +14,8 @@
 import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.VERSION_1;
 
 import java.io.DataInput;
+import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.DigestInputStream;
@@ -23,12 +25,43 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.io.SilentFileInputStream;
 
 /**
  * Factory for creating instances of {@link PackReverseIndex}.
  */
 public final class PackReverseIndexFactory {
 	/**
+	 * Create an in-memory pack reverse index by reading it from the given file
+	 * if the file exists, or computing it from the given pack index if the file
+	 * doesn't exist.
+	 *
+	 * @param idxFile
+	 *            the file to read the pack file from, if it exists
+	 * @param objectCount
+	 *            the number of objects in the corresponding pack
+	 * @param packIndexSupplier
+	 *            a function to lazily get the corresponding forward index
+	 * @return the reverse index instance
+	 * @throws IOException
+	 *             if reading from the file fails
+	 */
+	static PackReverseIndex openOrCompute(File idxFile, long objectCount,
+			PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier)
+			throws IOException {
+		try (SilentFileInputStream fd = new SilentFileInputStream(idxFile)) {
+			return readFromFile(fd, objectCount, packIndexSupplier);
+		} catch (FileNotFoundException e) {
+			return computeFromIndex(packIndexSupplier.get());
+		} catch (IOException e) {
+			throw new IOException(
+					MessageFormat.format(JGitText.get().unreadablePackIndex,
+							idxFile.getAbsolutePath()),
+					e);
+		}
+	}
+
+	/**
 	 * Compute an in-memory pack reverse index from the in-memory pack forward
 	 * index. This computation uses insertion sort, which has a quadratic
 	 * runtime on average.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java
index 76f0793..c77a8eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java
@@ -16,10 +16,14 @@
 import java.io.UncheckedIOException;
 import java.security.DigestInputStream;
 import java.text.MessageFormat;
+import java.util.Arrays;
 
 import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.Hex;
 import org.eclipse.jgit.util.IO;
 
 /**
@@ -89,7 +93,20 @@ final class PackReverseIndexV1 implements PackReverseIndex {
 		parseChecksums();
 	}
 
-	private void parseBody() throws IOException {
+	@Override
+	public void verifyPackChecksum(String packFilePath)
+			throws PackMismatchException {
+		if (!Arrays.equals(packChecksum, getPackIndex().getChecksum())) {
+			throw new PackMismatchException(
+					MessageFormat.format(JGitText.get().packChecksumMismatch,
+							packFilePath, PackExt.INDEX.getExtension(),
+							Hex.toHexString(getPackIndex().getChecksum()),
+							PackExt.REVERSE_INDEX.getExtension(),
+							Hex.toHexString(packChecksum)));
+		}
+	}
+
+		private void parseBody() throws IOException {
 		for (int i = 0; i < objectCount; i++) {
 			indexPositionsSortedByOffset[i] = dataIn.readInt();
 		}
@@ -98,10 +115,19 @@ private void parseBody() throws IOException {
 	private void parseChecksums() throws IOException {
 		packChecksum = new byte[SHA1_BYTES];
 		IO.readFully(inputStream, packChecksum);
-		// TODO: verify checksum
+
+		// Take digest before reading the self checksum changes it.
+		byte[] observedSelfChecksum = inputStream.getMessageDigest().digest();
 
 		byte[] readSelfChecksum = new byte[SHA1_BYTES];
 		IO.readFully(inputStream, readSelfChecksum);
+
+		if (!Arrays.equals(readSelfChecksum, observedSelfChecksum)) {
+			throw new CorruptObjectException(MessageFormat.format(
+					JGitText.get().corruptReverseIndexChecksumIncorrect,
+					Hex.toHexString(readSelfChecksum),
+					Hex.toHexString(observedSelfChecksum)));
+		}
 	}
 
 	@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
index 676573c..eeaccc6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
@@ -11,6 +11,9 @@
 package org.eclipse.jgit.revwalk;
 
 import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -47,6 +50,108 @@ public final class FooterLine {
 	}
 
 	/**
+	 * Extract the footer lines from the given message.
+	 *
+	 * @param str
+	 *            the message to extract footers from.
+	 * @return ordered list of footer lines; empty list if no footers found.
+	 * @see RevCommit#getFooterLines()
+	 */
+	public static List<FooterLine> fromMessage(
+			String str) {
+		return fromMessage(str.getBytes());
+	}
+
+	/**
+	 * Extract the footer lines from the given message.
+	 *
+	 * @param raw
+	 *            the raw message to extract footers from.
+	 * @return ordered list of footer lines; empty list if no footers found.
+	 * @see RevCommit#getFooterLines()
+	 */
+	public static List<FooterLine> fromMessage(
+			byte[] raw) {
+		int ptr = raw.length - 1;
+		while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
+			ptr--;
+
+		int msgB = RawParseUtils.commitMessage(raw, 0);
+		ArrayList<FooterLine> r = new ArrayList<>(4);
+		Charset enc = RawParseUtils.guessEncoding(raw);
+		for (;;) {
+			ptr = RawParseUtils.prevLF(raw, ptr);
+			if (ptr <= msgB)
+				break; // Don't parse commit headers as footer lines.
+
+			int keyStart = ptr + 2;
+			if (raw[keyStart] == '\n')
+				break; // Stop at first paragraph break, no footers above it.
+
+			int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart);
+			if (keyEnd < 0)
+				continue; // Not a well formed footer line, skip it.
+
+			// Skip over the ': *' at the end of the key before the value.
+			//
+			int valStart = keyEnd + 1;
+			while (valStart < raw.length && raw[valStart] == ' ')
+				valStart++;
+
+			// Value ends at the LF, and does not include it.
+			//
+			int valEnd = RawParseUtils.nextLF(raw, valStart);
+			if (raw[valEnd - 1] == '\n')
+				valEnd--;
+
+			r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
+		}
+		Collections.reverse(r);
+		return r;
+	}
+
+	/**
+	 * Get the values of all footer lines with the given key.
+	 *
+	 * @param footers
+	 *            list of footers to find the values in.
+	 * @param keyName
+	 *            footer key to find values of, case-insensitive.
+	 * @return values of footers with key of {@code keyName}, ordered by their
+	 *         order of appearance. Duplicates may be returned if the same
+	 *         footer appeared more than once. Empty list if no footers appear
+	 *         with the specified key, or there are no footers at all.
+	 * @see #fromMessage
+	 */
+	public static List<String> getValues(List<FooterLine> footers, String keyName) {
+		return getValues(footers, new FooterKey(keyName));
+	}
+
+	/**
+	 * Get the values of all footer lines with the given key.
+	 *
+	 * @param footers
+	 *            list of footers to find the values in.
+	 * @param key
+	 *            footer key to find values of, case-insensitive.
+	 * @return values of footers with key of {@code keyName}, ordered by their
+	 *         order of appearance. Duplicates may be returned if the same
+	 *         footer appeared more than once. Empty list if no footers appear
+	 *         with the specified key, or there are no footers at all.
+	 * @see #fromMessage
+	 */
+	public static List<String> getValues(List<FooterLine> footers, FooterKey key) {
+		if (footers.isEmpty())
+			return Collections.emptyList();
+		ArrayList<String> r = new ArrayList<>(footers.size());
+		for (FooterLine f : footers) {
+			if (f.matches(key))
+				r.add(f.getValue());
+		}
+		return r;
+	}
+
+	/**
 	 * Whether keys match
 	 *
 	 * @param key
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
index ba3399c..9856f2c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
@@ -23,8 +23,30 @@ public abstract class RenameCallback {
 	 * Called whenever a diff was found that is actually a rename or copy of a
 	 * file.
 	 *
+	 * <p>Subclass of this class have to override this to receive diffEntry for
+	 * the rename.
+	 *
 	 * @param entry
 	 *            the entry representing the rename/copy
 	 */
 	public abstract void renamed(DiffEntry entry);
+
+	/**
+	 * Called whenever a diff was found that is actually a rename or copy of a
+	 * file along with the commit that caused it.
+	 *
+	 * <p>Subclass of this class have an option to override this if it wants to
+	 * know what commit generated the diffEntry. Otherwise defaults to the
+	 * {@link RenameCallback#renamed(DiffEntry)} function.
+	 *
+	 * @param entry
+	 *            the entry representing the rename/copy
+	 * @param commit
+	 *            commit at which callback occurred
+	 *
+	 * @since 6.7
+	 */
+	public void renamed(DiffEntry entry, RevCommit commit) {
+		renamed(entry);
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index e0bdf3e..0392ea4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -11,15 +11,13 @@
 
 package org.eclipse.jgit.revwalk;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.util.RawParseUtils.guessEncoding;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 
 import org.eclipse.jgit.annotations.Nullable;
@@ -484,7 +482,8 @@ public final String getFullMessage() {
 		if (msgB < 0) {
 			return ""; //$NON-NLS-1$
 		}
-		return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
+		return RawParseUtils.decode(guessEncoding(buffer), raw, msgB,
+				raw.length);
 	}
 
 	/**
@@ -510,7 +509,8 @@ public final String getShortMessage() {
 		}
 
 		int msgE = RawParseUtils.endOfParagraph(raw, msgB);
-		String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+		String str = RawParseUtils.decode(guessEncoding(buffer), raw, msgB,
+				msgE);
 		if (hasLF(raw, msgB, msgE)) {
 			str = StringUtils.replaceLineBreaksWithSpace(str);
 		}
@@ -562,14 +562,6 @@ public final Charset getEncoding() {
 		return RawParseUtils.parseEncoding(buffer);
 	}
 
-	private Charset guessEncoding() {
-		try {
-			return getEncoding();
-		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
-			return UTF_8;
-		}
-	}
-
 	/**
 	 * Parse the footer lines (e.g. "Signed-off-by") for machine processing.
 	 * <p>
@@ -592,50 +584,14 @@ private Charset guessEncoding() {
 	 * @return ordered list of footer lines; empty list if no footers found.
 	 */
 	public final List<FooterLine> getFooterLines() {
-		final byte[] raw = buffer;
-		int ptr = raw.length - 1;
-		while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
-			ptr--;
-
-		final int msgB = RawParseUtils.commitMessage(raw, 0);
-		final ArrayList<FooterLine> r = new ArrayList<>(4);
-		final Charset enc = guessEncoding();
-		for (;;) {
-			ptr = RawParseUtils.prevLF(raw, ptr);
-			if (ptr <= msgB)
-				break; // Don't parse commit headers as footer lines.
-
-			final int keyStart = ptr + 2;
-			if (raw[keyStart] == '\n')
-				break; // Stop at first paragraph break, no footers above it.
-
-			final int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart);
-			if (keyEnd < 0)
-				continue; // Not a well formed footer line, skip it.
-
-			// Skip over the ': *' at the end of the key before the value.
-			//
-			int valStart = keyEnd + 1;
-			while (valStart < raw.length && raw[valStart] == ' ')
-				valStart++;
-
-			// Value ends at the LF, and does not include it.
-			//
-			int valEnd = RawParseUtils.nextLF(raw, valStart);
-			if (raw[valEnd - 1] == '\n')
-				valEnd--;
-
-			r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
-		}
-		Collections.reverse(r);
-		return r;
+		return FooterLine.fromMessage(buffer);
 	}
 
 	/**
 	 * Get the values of all footer lines with the given key.
 	 *
 	 * @param keyName
-	 *            footer key to find values of, case insensitive.
+	 *            footer key to find values of, case-insensitive.
 	 * @return values of footers with key of {@code keyName}, ordered by their
 	 *         order of appearance. Duplicates may be returned if the same
 	 *         footer appeared more than once. Empty list if no footers appear
@@ -643,30 +599,22 @@ public final List<FooterLine> getFooterLines() {
 	 * @see #getFooterLines()
 	 */
 	public final List<String> getFooterLines(String keyName) {
-		return getFooterLines(new FooterKey(keyName));
+		return FooterLine.getValues(getFooterLines(), keyName);
 	}
 
 	/**
 	 * Get the values of all footer lines with the given key.
 	 *
-	 * @param keyName
-	 *            footer key to find values of, case insensitive.
+	 * @param key
+	 *            footer key to find values of, case-insensitive.
 	 * @return values of footers with key of {@code keyName}, ordered by their
 	 *         order of appearance. Duplicates may be returned if the same
 	 *         footer appeared more than once. Empty list if no footers appear
 	 *         with the specified key, or there are no footers at all.
 	 * @see #getFooterLines()
 	 */
-	public final List<String> getFooterLines(FooterKey keyName) {
-		final List<FooterLine> src = getFooterLines();
-		if (src.isEmpty())
-			return Collections.emptyList();
-		final ArrayList<String> r = new ArrayList<>(src.size());
-		for (FooterLine f : src) {
-			if (f.matches(keyName))
-				r.add(f.getValue());
-		}
-		return r;
+	public final List<String> getFooterLines(FooterKey key) {
+		return FooterLine.getValues(getFooterLines(), key);
 	}
 
 	/**
@@ -694,10 +642,11 @@ int getGeneration() {
 	 * commit graph file, or the commit graph file was generated without changed
 	 * path filters.
 	 *
+	 * @param rw A revwalk to load the commit graph (if available)
 	 * @return the changed path filter
 	 * @since 6.7
 	 */
-	public ChangedPathFilter getChangedPathFilter() {
+	public ChangedPathFilter getChangedPathFilter(RevWalk rw) {
 		return null;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
index 8c81003..c7a0399 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
@@ -30,8 +30,6 @@ class RevCommitCG extends RevCommit {
 
 	private final int graphPosition;
 
-	private final ChangedPathFilter changedPathFilter;
-
 	private int generation = Constants.COMMIT_GENERATION_UNKNOWN;
 
 	/**
@@ -41,14 +39,10 @@ class RevCommitCG extends RevCommit {
 	 *            object name for the commit.
 	 * @param graphPosition
 	 *            the position in the commit-graph of the object.
-	 * @param changedPathFilter
-	 *            the changed path filter if one exists
 	 */
-	protected RevCommitCG(AnyObjectId id, int graphPosition,
-			ChangedPathFilter changedPathFilter) {
+	protected RevCommitCG(AnyObjectId id, int graphPosition) {
 		super(id);
 		this.graphPosition = graphPosition;
-		this.changedPathFilter = changedPathFilter;
 	}
 
 	@Override
@@ -110,7 +104,7 @@ int getGeneration() {
 
 	/** {@inheritDoc} */
 	@Override
-	public ChangedPathFilter getChangedPathFilter() {
-		return changedPathFilter;
+	public ChangedPathFilter getChangedPathFilter(RevWalk rw) {
+		return rw.commitGraph().getChangedPathFilter(graphPosition);
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index f4bf710..27a09f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -1713,8 +1713,7 @@ protected RevCommit createCommit(AnyObjectId id) {
 
 	private RevCommit createCommit(AnyObjectId id, int graphPos) {
 		if (graphPos >= 0) {
-			return new RevCommitCG(id, graphPos,
-					commitGraph().getChangedPathFilter(graphPos));
+			return new RevCommitCG(id, graphPos);
 		}
 		return new RevCommit(id);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
index 017a825..43571a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
@@ -133,7 +133,7 @@ public boolean include(RevWalk walker, RevCommit c)
 			int chgs = 0, adds = 0;
 			boolean changedPathFilterUsed = false;
 			boolean mustCalculateChgs = true;
-			ChangedPathFilter cpf = c.getChangedPathFilter();
+			ChangedPathFilter cpf = c.getChangedPathFilter(walker);
 			if (cpf != null) {
 				Optional<Set<byte[]>> paths = pathFilter.getFilter()
 						.getPathsBestEffort();
@@ -185,7 +185,8 @@ public boolean include(RevWalk walker, RevCommit c)
 				// commit. We need to update our filter to its older
 				// name, if we can discover it. Find out what that is.
 				//
-				updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg);
+				updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg,
+						c);
 			}
 			return true;
 		} else if (nParents == 0) {
@@ -287,6 +288,7 @@ public boolean requiresCommitBody() {
 	 * path was changed in a commit, for statistics gathering purposes.
 	 *
 	 * @return count of true positives
+	 * @since 6.7
 	 */
 	public long getChangedPathFilterTruePositive() {
 		return changedPathFilterTruePositive;
@@ -297,6 +299,7 @@ public long getChangedPathFilterTruePositive() {
 	 * was changed in a commit, for statistics gathering purposes.
 	 *
 	 * @return count of false positives
+	 * @since 6.7
 	 */
 	public long getChangedPathFilterFalsePositive() {
 		return changedPathFilterFalsePositive;
@@ -308,12 +311,14 @@ public long getChangedPathFilterFalsePositive() {
 	 * gathering purposes.
 	 *
 	 * @return count of negatives
+	 * @since 6.7
 	 */
 	public long getChangedPathFilterNegative() {
 		return changedPathFilterNegative;
 	}
 
-	private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg)
+	private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg,
+			RevCommit commit)
 			throws MissingObjectException, IncorrectObjectTypeException,
 			CorruptObjectException, IOException {
 		TreeWalk tw = pathFilter;
@@ -332,7 +337,7 @@ private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg)
 				newFilter = FollowFilter.create(ent.getOldPath(), cfg);
 				RenameCallback callback = oldFilter.getRenameCallback();
 				if (callback != null) {
-					callback.renamed(ent);
+					callback.renamed(ent, commit);
 					// forward the callback to the new follow filter
 					((FollowFilter) newFilter).setRenameCallback(callback);
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 0e8e9b3..1c98336 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -868,6 +868,25 @@ public static Charset parseEncoding(byte[] b) {
 	}
 
 	/**
+	 * Parse the "encoding " header into a character set reference.
+	 * <p>
+	 * If unsuccessful, return UTF-8.
+	 *
+	 * @param buffer
+	 *            buffer to scan.
+	 * @return the Java character set representation. Never null. Default to
+	 *            UTF-8.
+	 * @see #parseEncoding(byte[])
+	 */
+	public static Charset guessEncoding(byte[] buffer) {
+		try {
+			return parseEncoding(buffer);
+		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+			return UTF_8;
+		}
+	}
+
+	/**
 	 * Parse a name string (e.g. author, committer, tagger) into a PersonIdent.
 	 * <p>
 	 * Leading spaces won't be trimmed from the string, i.e. will show up in the