Merge branch 'stable-7.1' into stable-7.2

* stable-7.1:
  Ensure pack files are closed after git.close()
  util.Iterators: suppress warning about object arrays
  util.Iterators: private constructor for utility class
  Prevent CommitGraphWriter.write() from closing its stream
  Make CancellableDigestOutputStream extend FilterOutputStream

Change-Id: I74567dfef7689a217dc05b94f5e0a08cba352828
diff --git a/.bazelrc b/.bazelrc
index 74601dc..70322dd 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -37,5 +37,6 @@
 test --build_tests_only
 test --test_output=errors
 test --flaky_test_attempts=3
+test --test_tag_filters=-ext
 
 import %workspace%/tools/remote-bazelrc
diff --git a/.mailmap b/.mailmap
index 7116ebb..1c77395 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,20 +1,33 @@
+Adithya Chakilam <quic_achakila@quicinc.com>                Adithya Chakilam <achakila@codeaurora.org>
 Chris Aniszczyk <caniszczyk@gmail.com>                      Chris Aniszczyk <zx@eclipsesource.com>
 Christian Halstrick <christian.halstrick@sap.com>           Christian Halstrick <christian.halstrick@gmail.com>
 Dani Megert <Daniel_Megert@ch.ibm.com>                      Daniel Megert <daniel_megert@ch.ibm.com>
+Dariusz Luksza <dariusz.luksza@gmail.com>                   Dariusz Luksza <dariusz@luksza.org>
+David Ostrovsky <david.ostrovsky@gmail.com>                 David Ostrovsky <david@ostrovsky.org>
 David Pursehouse <david.pursehouse@gmail.com>               David Pursehouse <david.pursehouse@sonymobile.com>
 Han-Wen Nienhuys <hanwen@google.com>                        Han-Wen NIenhuys <hanwen@google.com>
 Hector Oswaldo Caballero <hector.caballero@ericsson.com>    Hector Caballero <hector.caballero@ericsson.com>
+Jackson Toeniskoetter <jackdt@google.com>                   <jackdt@google.com>
 Lars Vogel <Lars.Vogel@vogella.com>                         Lars Vogel <Lars.Vogel@gmail.com>
 Mark Ingram <markdingram@gmail.com>                         markdingram <markdingram@gmail.com>
 Markus Duft <markus.duft@ssi-schaefer.com>                  Markus Duft <markus.duft@salomon.at>
+Martin Fick <mfick@nvidia.com>                              Martin Fick <mfick@codeaurora.org>
+Martin Fick <mfick@nvidia.com>                              Martin Fick <quic_mfick@quicinc.com>
 Michael Keppler <michael.keppler@gmx.de>                    Michael Keppler <Michael.Keppler@gmx.de>
+Nasser Grainawi <quic_nasserg@quicinc.com>                  Nasser Grainawi <nasser@codeaurora.org>
 Roberto Tyley <roberto.tyley@guardian.co.uk>                roberto <roberto.tyley@guardian.co.uk>
 Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <sasa.zivkov@sap.com>
 Saša Živkov <sasa.zivkov@sap.com>                           Saša Živkov <zivkov@gmail.com>
 Saša Živkov <sasa.zivkov@sap.com>                           Sasa Zivkov <zivkov@gmail.com>
 Sebastian Schuberth <sschuberth@gmail.com>                  Sebastian Schuberth <sebastian.schuberth@bosch.io>
+Sebastian Schuberth <sschuberth@gmail.com>                  <opensource@schuberth.dev>
 Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>                          Shawn Pearce <sop@google.com>
 Shawn Pearce <spearce@spearce.org>                          Shawn O. Pearce <spearce@spearce.org>
+Sven Selberg <sven.selberg@axis.com>                        Sven Selberg <sven.selberg@sonymobile.com>
 Terry Parker <tparker@google.com>                           tparker <tparker@google.com>
 Thomas Wolf <twolf@apache.org>                              Thomas Wolf <thomas.wolf@paranor.ch>
+Tomasz Zarna <tzarna@gmail.com>                             <Tomasz.Zarna@pl.ibm.com>
+Tomasz Zarna <tzarna@gmail.com>                             <tomasz.zarna@tasktop.com>
+Tobias Pfeifer <to.pfeifer@web.de>                          <to.pfeifer@sap.com>
+Yunjie Li <yunjieli@google.com>                             <yunjieli@google.com>
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
index eeb78ff..4dde8f8 100644
--- a/Documentation/config-options.md
+++ b/Documentation/config-options.md
@@ -55,9 +55,13 @@
 | `core.streamFileThreshold` | `50 MiB` | &#x20DE; | The size threshold beyond which objects must be streamed. |
 | `core.supportsAtomicFileCreation` | `true` | &#x20DE; | Whether the filesystem supports atomic file creation. |
 | `core.symlinks` | Auto detect if filesystem supports symlinks| &#x2705; | If false, symbolic links are checked out as small plain files that contain the link text. |
-| `core.trustFolderStat` | `true` | &#x20DE; | Whether to trust the pack folder's, packed-refs file's and loose-objects folder's file attributes (Java equivalent of stat command on *nix). When looking for pack files, if `false` JGit will always scan the `.git/objects/pack` folder and if set to `true` it assumes that pack files are unchanged if the file attributes of the pack folder are unchanged. When getting the list of packed refs, if `false` JGit will always read the packed-refs file and if set to `true` it uses the file attributes of the packed-refs file and will only read it if a file attribute has changed. When looking for loose objects, if `false` and if a loose object is not found, JGit will open and close a stream to `.git/objects` folder (which can refresh its directory listing, at least on some NFS clients) and retry looking for that loose object. Setting this option to `false` can help to workaround caching issues on NFS, but reduces performance. |
-| `core.trustPackedRefsStat` | `unset` | &#x20DE; | Whether to trust the file attributes (Java equivalent of stat command on *nix) of the packed-refs file. If `never` JGit will ignore the file attributes of the packed-refs file and always read it. If `always` JGit will trust the file attributes of the packed-refs file and will only read it if a file attribute has changed. `after_open` behaves the same as `always`, except that the packed-refs file is opened and closed before its file attributes are considered. An open/close of the packed-refs file is known to refresh its file attributes, at least on some NFS clients. If `unset`, JGit will use the behavior described in `trustFolderStat`. |
-| `core.trustLooseRefStat` | `always` | &#x20DE; | Whether to trust the file attributes (Java equivalent of stat command on *nix) of the loose ref. If `always` JGit will trust the file attributes of the loose ref and its parent directories. `after_open` behaves similar to `always`, except that all parent directories of the loose ref up to the repository root are opened and closed before its file attributes are considered. An open/close of these parent directories is known to refresh the file attributes, at least on some NFS clients. |
+| ~~`core.trustFolderStat`~~ | `true` | &#x20DE; | __Deprecated__, use `core.trustStat` instead. If set to `true` translated to `core.trustStat=always`, if `false` translated to `core.trustStat=never`, see below. If both `core.trustFolderStat` and `core.trustStat` are configured then `trustStat` takes precedence and `trustFolderStat` is ignored. |
+| `core.trustLooseRefStat` | `inherit` | &#x20DE; | Whether to trust the file attributes of loose refs and its fan-out parent directory. See `core.trustStat` for possible values. If `inherit`, JGit will use the behavior configured in `trustStat`. |
+| `core.trustPackedRefsStat` | `inherit` | &#x20DE; | Whether to trust the file attributes of the packed-refs file. See `core.trustStat` for possible values. If `inherit`, JGit will use the behavior configured in `core.trustStat`. |
+| `core.trustTablesListStat` | `inherit` | &#x20DE; | Whether to trust the file attributes of the `tables.list` file used by the reftable ref storage backend to store the list of reftable filenames. See `core.trustStat` for possible values. If `inherit`, JGit will use the behavior configured in `core.trustStat`.  The reftable backend is used if `extensions.refStorage = reftable`. |
+| `core.trustLooseObjectStat` | `inherit` | &#x20DE; | Whether to trust the file attributes of the loose object file and its fan-out parent directory. See `core.trustStat` for possible values. If `inherit`, JGit will use the behavior configured in `core.trustStat`. |
+| `core.trustPackStat` | `inherit` | &#x20DE; | Whether to trust the file attributes of the `objects/pack` directory. See `core.trustStat` for possible values. If `inherit`, JGit will use the behavior configured in `core.trustStat`. |
+| `core.trustStat` | `always` | &#x20DE; | Global option to configure whether to trust file attributes (Java equivalent of stat command on Unix) of files storing git objects. Can be overridden for specific files by configuring `core.trustLooseRefStat, core.trustPackedRefsStat, core.trustLooseObjectStat, core.trustPackStat,core.trustTablesListStat`. If `never` JGit will ignore the file attributes of the file and always read it. If `always` JGit will trust the file attributes and will only read it if a file attribute has changed. `after_open` behaves the same as `always`, but file attributes are only considered *after* the file itself and any transient parent directories have been opened and closed. An open/close of the file/directory is known to refresh its file attributes, at least on some NFS clients. |
 | `core.worktree` | Root directory of the working tree if it is not the parent directory of the `.git` directory | &#x2705; | The path to the root of the working tree. |
 
 ## __fetch__ options
@@ -131,6 +135,13 @@
 | `pack.window` | `10` | &#x2705; | Number of objects to try when looking for a delta base per thread searching for deltas. |
 | `pack.windowMemory` | `0` (unlimited) | &#x2705; | Maximum number of bytes to put into the delta search window. |
 
+## reftable options
+
+|  option | default | git option | description |
+|---------|---------|------------|-------------|
+| `reftable.autoRefresh` | `false` | &#x20DE; | Whether to auto-refresh the reftable stack if it is out of date. |
+
+
 ## __repack__ options
 
 |  option | default | git option | description |
diff --git a/WORKSPACE b/WORKSPACE
index e1adb8a..505141c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -73,12 +73,6 @@
 )
 
 maven_jar(
-    name = "eddsa",
-    artifact = "net.i2p.crypto:eddsa:0.3.0",
-    sha1 = "1901c8d4d8bffb7d79027686cfb91e704217c3e1",
-)
-
-maven_jar(
     name = "jsch",
     artifact = "com.jcraft:jsch:0.1.55",
     sha1 = "bbd40e5aa7aa3cfad5db34965456cee738a42a50",
@@ -108,44 +102,44 @@
     sha1 = "51cf043c87253c9f58b539c9f7e44c8894223850",
 )
 
-SSHD_VERS = "2.14.0"
+SSHD_VERS = "2.15.0"
 
 maven_jar(
     name = "sshd-osgi",
     artifact = "org.apache.sshd:sshd-osgi:" + SSHD_VERS,
-    sha1 = "6ef66228a088f8ac1383b2ff28f3102f80ebc01a",
+    sha1 = "aa76898fe47eab7da0878dd60e6f3be5631e076c",
 )
 
 maven_jar(
     name = "sshd-sftp",
     artifact = "org.apache.sshd:sshd-sftp:" + SSHD_VERS,
-    sha1 = "c070ac920e72023ae9ab0a3f3a866bece284b470",
+    sha1 = "2e226055ed060c64ed76256a9c45de6d0109eef8",
 )
 
-JNA_VERS = "5.15.0"
+JNA_VERS = "5.16.0"
 
 maven_jar(
     name = "jna",
     artifact = "net.java.dev.jna:jna:" + JNA_VERS,
-    sha1 = "01ee1d80ff44f08280188f7c0e740d57207841ac",
+    sha1 = "ebea09f91dc9f7048099f963fb8d6f919f0a4d9c",
 )
 
 maven_jar(
     name = "jna-platform",
     artifact = "net.java.dev.jna:jna-platform:" + JNA_VERS,
-    sha1 = "86b502cad57d45da172b5e3231c537b042e296ef",
+    sha1 = "b2a9065f97c166893d504b164706512338e3bbc2",
 )
 
 maven_jar(
     name = "commons-codec",
-    artifact = "commons-codec:commons-codec:1.17.1",
-    sha1 = "973638b7149d333563584137ebf13a691bb60579",
+    artifact = "commons-codec:commons-codec:1.18.0",
+    sha1 = "ee45d1cf6ec2cc2b809ff04b4dc7aec858e0df8f",
 )
 
 maven_jar(
     name = "commons-logging",
-    artifact = "commons-logging:commons-logging:1.3.4",
-    sha1 = "b9fc14968d63a8b8a8a2c1885fe3e90564239708",
+    artifact = "commons-logging:commons-logging:1.3.5",
+    sha1 = "a3fcc5d3c29b2b03433aa2d2f2d2c1b1638924a1",
 )
 
 maven_jar(
@@ -180,8 +174,8 @@
 
 maven_jar(
     name = "commons-io",
-    artifact = "commons-io:commons-io:2.17.0",
-    sha1 = "ddcc8433eb019fb48fe25207c0278143f3e1d7e2",
+    artifact = "commons-io:commons-io:2.18.0",
+    sha1 = "44084ef756763795b31c578403dd028ff4a22950",
 )
 
 maven_jar(
@@ -210,28 +204,28 @@
 
 maven_jar(
     name = "mockito",
-    artifact = "org.mockito:mockito-core:5.14.2",
-    sha1 = "f7bf936008d7664e2002c3faf0c02071c8d10e7c",
+    artifact = "org.mockito:mockito-core:5.15.2",
+    sha1 = "87be4b1e0cc5febc07ab3197a8ff3ede56b37a79",
 )
 
 maven_jar(
     name = "assertj-core",
-    artifact = "org.assertj:assertj-core:3.26.3",
-    sha1 = "0d26263eb7524252d98e602fc6942996a3195e29",
+    artifact = "org.assertj:assertj-core:3.27.3",
+    sha1 = "31f5d58a202bd5df4993fb10fa2cffd610c20d6f",
 )
 
-BYTE_BUDDY_VERSION = "1.15.10"
+BYTE_BUDDY_VERSION = "1.17.1"
 
 maven_jar(
     name = "bytebuddy",
     artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
-    sha1 = "635c873fadd853c084f84fdc3cbd58c5dd8537f9",
+    sha1 = "8b5205fad48196a88d3d66dddff5a7417bce3596",
 )
 
 maven_jar(
     name = "bytebuddy-agent",
     artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
-    sha1 = "0e8eb255b2c378b9a6c7341e7b0e12f0a5636377",
+    sha1 = "0669a13b59d5ffd8198a79e4dc99018a9278e457",
 )
 
 maven_jar(
@@ -242,86 +236,82 @@
 
 maven_jar(
     name = "gson",
-    artifact = "com.google.code.gson:gson:2.11.0",
-    sha1 = "527175ca6d81050b53bdd4c457a6d6e017626b0e",
+    artifact = "com.google.code.gson:gson:2.12.1",
+    sha1 = "4e773a317740b83b43cfc3d652962856041697cb",
 )
 
-JETTY_VER = "12.0.15"
+JETTY_VER = "12.0.16"
 
 maven_jar(
     name = "jetty-servlet",
     artifact = "org.eclipse.jetty.ee10:jetty-ee10-servlet:" + JETTY_VER,
-    sha1 = "a9362717fa1756f9f1f18e0ef2ce671e742b7afb",
+    sha1 = "022a746c00b1ac5c790fee65a398c707160a46d8",
 )
 
 maven_jar(
     name = "jetty-security",
     artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
-    sha1 = "e15efd84ed53277f8e893574edaed4734c161f44",
+    sha1 = "23b1a3abecf9d6f5498064a32d9145ae1d8330f9",
 )
 
 maven_jar(
     name = "jetty-server",
     artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
-    sha1 = "2bd3742c6831e42c6ebfa9c990386a8dca71dee8",
+    sha1 = "3e3638b4bfbee04c27b3ae68e4949fc43b40a042",
 )
 
 maven_jar(
     name = "jetty-session",
     artifact = "org.eclipse.jetty:jetty-session:" + JETTY_VER,
-    sha1 = "2819021282ff2f7fbaa53feb2fe063130bd4613c",
+    sha1 = "79cdedc7afebbdba4453f603dfe2f970baa35cc3",
 )
 
 maven_jar(
     name = "jetty-http",
     artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
-    sha1 = "a36fcfde8316b374102c5b43d7247ec501e906d8",
+    sha1 = "68019fa90e8420ae15c109bd8c8611cacbaf43e5",
 )
 
 maven_jar(
     name = "jetty-io",
     artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
-    sha1 = "e1657f842a0e362171a8a41f35d85cdba1a47872",
+    sha1 = "7a162c537a99bbaf35a074fec9a50815e6c81d9d",
 )
 
 maven_jar(
     name = "jetty-util",
     artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
-    sha1 = "9fbd3ac58607af034ad065b366798798bb5f7b5e",
+    sha1 = "e262e505363e5925df15618622d9888aefc1b0d0",
 )
 
 maven_jar(
     name = "jetty-util-ajax",
     artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER,
-    sha1 = "d5c2f0ea177c5c178874101c186cc9729cf7ea92",
+    sha1 = "60225034131e3f771b40bc75c15bd9cc4952302b",
 )
 
-BOUNCYCASTLE_VER = "1.79"
+BOUNCYCASTLE_VER = "1.80"
 
 maven_jar(
     name = "bcpg",
     artifact = "org.bouncycastle:bcpg-jdk18on:" + BOUNCYCASTLE_VER,
-    sha1 = "904dd8a8e1c9f7d58d1ffa7f4ca3fb00736a601f",
-    src_sha1 = "9e372826141edb213d5921131ee68dc276dc99ef",
+    sha1 = "163889a825393854dbe7dc52f1a8667e715e9859",
 )
 
 maven_jar(
     name = "bcprov",
     artifact = "org.bouncycastle:bcprov-jdk18on:" + BOUNCYCASTLE_VER,
-    sha1 = "4d8e2732bcee15f1db93df266c3f5b70ce5cac21",
-    src_sha1 = "8647816d667ee526a8e3a456229ac5f9f96d2315",
+    sha1 = "e22100b41042decf09cab914a5af8d2c57b5ac4a",
 )
 
 maven_jar(
     name = "bcutil",
     artifact = "org.bouncycastle:bcutil-jdk18on:" + BOUNCYCASTLE_VER,
-    sha1 = "ecfc5aef97cc7676ea0de5c53c407b9f533f0ad5",
-    src_sha1 = "00df03977fb0b80395da655623abca9d7d7dcb66",
+    sha1 = "b95726d1d49a0c65010c59a3e6640311d951bfd1",
 )
 
 maven_jar(
     name = "bcpkix",
     artifact = "org.bouncycastle:bcpkix-jdk18on:" + BOUNCYCASTLE_VER,
-    sha1 = "7693cec3b8779b74b35466dcaeeaac7409872954",
-    src_sha1 = "57a60d1d9f75320eef70a095dfae679d97ade1c2",
+    sha1 = "5277dfaaef2e92ce1d802499599a0ca7488f86e6",
 )
diff --git a/lib/BUILD b/lib/BUILD
index d26ccae..d236b3a 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -55,16 +55,6 @@
 )
 
 java_library(
-    name = "eddsa",
-    visibility = [
-        "//org.eclipse.jgit.ssh.apache:__pkg__",
-        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
-        "//org.eclipse.jgit.ssh.jsch.test:__pkg__",
-    ],
-    exports = ["@eddsa//jar"],
-)
-
-java_library(
     name = "gson",
     visibility = [
         "//org.eclipse.jgit.lfs:__pkg__",
@@ -218,6 +208,8 @@
     visibility = [
         "//org.eclipse.jgit.gpg.bc:__pkg__",
         "//org.eclipse.jgit.gpg.bc.test:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+        "//org.eclipse.jgit.ssh.jsch.test:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
     exports = ["@bcprov//jar"],
@@ -228,6 +220,8 @@
     visibility = [
         "//org.eclipse.jgit.gpg.bc:__pkg__",
         "//org.eclipse.jgit.gpg.bc.test:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+        "//org.eclipse.jgit.ssh.jsch.test:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
     exports = ["@bcutil//jar"],
@@ -237,6 +231,8 @@
     name = "bcpkix",
     visibility = [
         "//org.eclipse.jgit.gpg.bc:__pkg__",
+        "//org.eclipse.jgit.ssh.apache.test:__pkg__",
+        "//org.eclipse.jgit.ssh.jsch.test:__pkg__",
         "//org.eclipse.jgit.test:__pkg__",
     ],
     exports = ["@bcpkix//jar"],
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 3644186..025335e 100644
--- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
@@ -5,13 +5,13 @@
 Automatic-Module-Name: org.eclipse.jgit.ant.test
 Bundle-SymbolicName: org.eclipse.jgit.ant.test
 Bundle-Vendor: %Bundle-Vendor
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.ant.tasks;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)"
diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml
index a145760..bce6d43 100644
--- a/org.eclipse.jgit.ant.test/pom.xml
+++ b/org.eclipse.jgit.ant.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant.test</artifactId>
diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
index b22bdb9..d6fa5e2 100644
--- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF
@@ -3,13 +3,13 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ant
 Bundle-SymbolicName: org.eclipse.jgit.ant
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: org.apache.tools.ant,
-  org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)"
+  org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)"
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.ant;version="7.1.2",
- org.eclipse.jgit.ant.tasks;version="7.1.2";
+Export-Package: org.eclipse.jgit.ant;version="7.2.2",
+ org.eclipse.jgit.ant.tasks;version="7.2.2";
   uses:="org.apache.tools.ant,
    org.apache.tools.ant.types"
diff --git a/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
index 9fb41bb..c07a051 100644
--- a/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ant/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ant - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ant.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ant;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ant;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index 0376cef..38e9dce 100644
--- a/org.eclipse.jgit.ant/pom.xml
+++ b/org.eclipse.jgit.ant/pom.xml
@@ -15,7 +15,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ant</artifactId>
diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
index c9d4352..e2b4b8c 100644
--- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.archive
 Bundle-SymbolicName: org.eclipse.jgit.archive
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -13,18 +13,18 @@
  org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)",
  org.apache.commons.compress.compressors.xz;version="[1.4,2.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.osgi.framework;version="[1.3.0,2.0.0)",
  org.tukaani.xz
 Bundle-ActivationPolicy: lazy
 Bundle-Activator: org.eclipse.jgit.archive.FormatActivator
-Export-Package: org.eclipse.jgit.archive;version="7.1.2";
+Export-Package: org.eclipse.jgit.archive;version="7.2.2";
   uses:="org.apache.commons.compress.archivers,
    org.osgi.framework,
    org.eclipse.jgit.api,
    org.eclipse.jgit.lib",
- org.eclipse.jgit.archive.internal;version="7.1.2";x-internal:=true
+ org.eclipse.jgit.archive.internal;version="7.2.2";x-internal:=true
diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
index 9d11d98..91164e8 100644
--- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.archive - Sources
 Bundle-SymbolicName: org.eclipse.jgit.archive.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index f557b01..c7ea62c 100644
--- a/org.eclipse.jgit.archive/pom.xml
+++ b/org.eclipse.jgit.archive/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.archive</artifactId>
diff --git a/org.eclipse.jgit.benchmarks/pom.xml b/org.eclipse.jgit.benchmarks/pom.xml
index 60f83cb..bd5d904 100644
--- a/org.eclipse.jgit.benchmarks/pom.xml
+++ b/org.eclipse.jgit.benchmarks/pom.xml
@@ -16,7 +16,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.benchmarks</artifactId>
@@ -52,6 +52,10 @@
       <artifactId>org.eclipse.jgit.junit</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
@@ -79,7 +83,6 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>${maven-compiler-plugin-version}</version>
         <configuration>
           <encoding>UTF-8</encoding>
           <release>${java.version}</release>
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..44e862e 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
@@ -24,10 +24,12 @@
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.internal.storage.file.FileReftableDatabase;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
 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;
@@ -38,8 +40,10 @@
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FileUtils;
+import org.junit.Assume;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
 import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.OutputTimeUnit;
@@ -66,11 +70,14 @@ public static class BenchmarkState {
 		@Param({ "true", "false" })
 		boolean useRefTable;
 
-		@Param({ "100", "2500", "10000", "50000" })
+		@Param({ "true", "false" })
+		boolean autoRefresh;
+
+		@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);
 
@@ -81,10 +88,13 @@ public static class BenchmarkState {
 		@Setup
 		@SuppressWarnings("boxing")
 		public void setupBenchmark() throws IOException, GitAPIException {
+			// if we use RefDirectory skip autoRefresh = false
+			Assume.assumeTrue(useRefTable || autoRefresh);
+
 			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();
@@ -97,10 +107,13 @@ public void setupBenchmark() throws IOException, GitAPIException {
 				((FileRepository) git.getRepository()).convertRefStorage(
 						ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false,
 						false);
+				FileReftableDatabase refdb = (FileReftableDatabase) git
+						.getRepository().getRefDatabase();
+				refdb.setAutoRefresh(autoRefresh);
 			} 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 +125,8 @@ 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("- autoRefresh: \t\t" + autoRefresh);
+			System.out.println("- trustStat: \t" + trustStat);
 			System.out.println("- branches: \t\t" + numBranches);
 
 			BatchRefUpdate u = repo.getRefDatabase().newBatchUpdate();
@@ -152,7 +166,8 @@ 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)
+	@Fork(2)
 	public void testGetExactRef(Blackhole blackhole, BenchmarkState state)
 			throws IOException {
 		String branchName = state.branches
@@ -164,7 +179,8 @@ 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)
+	@Fork(2)
 	public void testGetRefsByPrefix(Blackhole blackhole, BenchmarkState state)
 			throws IOException {
 		String branchPrefix = "refs/heads/branch/" + branchIndex.nextInt(100)
diff --git a/org.eclipse.jgit.coverage/pom.xml b/org.eclipse.jgit.coverage/pom.xml
index da71786..448d19f 100644
--- a/org.eclipse.jgit.coverage/pom.xml
+++ b/org.eclipse.jgit.coverage/pom.xml
@@ -14,7 +14,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
@@ -27,88 +27,88 @@
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.archive</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.apache</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.server</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ui</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
 
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ant.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.http.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.pgm.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jgit</groupId>
       <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
   </dependencies>
 
diff --git a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
index c934a4f..6ef6065 100644
--- a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.gpg.bc.test
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -14,9 +14,9 @@
  org.bouncycastle.openpgp.operator;version="[1.79.0,2.0.0)",
  org.bouncycastle.openpgp.operator.jcajce;version="[1.79.0,2.0.0)",
  org.bouncycastle.util.encoders;version="[1.79.0,2.0.0)",
- org.eclipse.jgit.gpg.bc.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.gpg.bc.internal.keys;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.sha1;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.gpg.bc.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.gpg.bc.internal.keys;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.sha1;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13,5.0.0)"
diff --git a/org.eclipse.jgit.gpg.bc.test/pom.xml b/org.eclipse.jgit.gpg.bc.test/pom.xml
index d821f19..5b71a6e 100644
--- a/org.eclipse.jgit.gpg.bc.test/pom.xml
+++ b/org.eclipse.jgit.gpg.bc.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.gpg.bc.test</artifactId>
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
index 77fd134..7fb9ca2 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
@@ -3,10 +3,10 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.gpg.bc
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc;singleton:=true
-Fragment-Host: org.eclipse.jgit;bundle-version="[7.1.2,7.2.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[7.2.2,7.3.0)"
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/gpg_bc
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: org.bouncycastle.asn1;version="[1.79.0,2.0.0)",
  org.bouncycastle.asn1.x9;version="[1.79.0,2.0.0)",
@@ -26,5 +26,5 @@
  org.bouncycastle.openpgp.operator.jcajce;version="[1.79.0,2.0.0)",
  org.bouncycastle.util.encoders;version="[1.79.0,2.0.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
-Export-Package: org.eclipse.jgit.gpg.bc.internal;version="7.1.2";x-friends:="org.eclipse.jgit.gpg.bc.test",
- org.eclipse.jgit.gpg.bc.internal.keys;version="7.1.2";x-friends:="org.eclipse.jgit.gpg.bc.test"
+Export-Package: org.eclipse.jgit.gpg.bc.internal;version="7.2.2";x-friends:="org.eclipse.jgit.gpg.bc.test",
+ org.eclipse.jgit.gpg.bc.internal.keys;version="7.2.2";x-friends:="org.eclipse.jgit.gpg.bc.test"
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
index e8d547c..b8f19ca 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.gpg.bc - Sources
 Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.gpg.bc/pom.xml b/org.eclipse.jgit.gpg.bc/pom.xml
index e4c535d..4b3a713 100644
--- a/org.eclipse.jgit.gpg.bc/pom.xml
+++ b/org.eclipse.jgit.gpg.bc/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.gpg.bc</artifactId>
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 20d0e4d..18db66c 100644
--- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.apache
 Bundle-SymbolicName: org.eclipse.jgit.http.apache
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
@@ -26,11 +26,11 @@
  org.apache.http.impl.conn;version="[4.4.0,5.0.0)",
  org.apache.http.params;version="[4.3.0,5.0.0)",
  org.apache.http.ssl;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="7.1.2";
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)"
+Export-Package: org.eclipse.jgit.transport.http.apache;version="7.2.2";
   uses:="org.apache.http.client,
    org.eclipse.jgit.transport.http,
    org.apache.http.entity,
diff --git a/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
index 04bbe37..a4ec86e 100644
--- a/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.http.apache/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.http.apache - Sources
 Bundle-SymbolicName: org.eclipse.jgit.http.apache.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index 11d92fc..963f5a4 100644
--- a/org.eclipse.jgit.http.apache/pom.xml
+++ b/org.eclipse.jgit.http.apache/pom.xml
@@ -15,7 +15,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.apache</artifactId>
diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
index 2a262d1..418c2cb 100644
--- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.server
 Bundle-SymbolicName: org.eclipse.jgit.http.server
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.http.server;version="7.1.2",
- org.eclipse.jgit.http.server.glue;version="7.1.2";
+Export-Package: org.eclipse.jgit.http.server;version="7.2.2",
+ org.eclipse.jgit.http.server.glue;version="7.2.2";
   uses:="jakarta.servlet,
   	jakarta.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="7.1.2";
+ org.eclipse.jgit.http.server.resolver;version="7.2.2";
   uses:="jakarta.servlet.http
    org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.lib,
@@ -19,14 +19,14 @@
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: jakarta.servlet;version="[6.0.0,7.0.0)",
  jakarta.servlet.http;version="[6.0.0,7.0.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.parser;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.resolver;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)"
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)"
diff --git a/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
index 0d9322c..d1efc55 100644
--- a/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.http.server/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.http.server - Sources
 Bundle-SymbolicName: org.eclipse.jgit.http.server.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index ecf80a3..a3f91b2 100644
--- a/org.eclipse.jgit.http.server/pom.xml
+++ b/org.eclipse.jgit.http.server/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.server</artifactId>
diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
index ec2d986..618686f 100644
--- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.http.test
 Bundle-SymbolicName: org.eclipse.jgit.http.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -29,26 +29,26 @@
  org.eclipse.jetty.util.component;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.security;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.thread;version="[12.0.0,13.0.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.http.server;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.http.server.glue;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.http.server.resolver;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.resolver;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.http.server;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.http.server.glue;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.http.server.resolver;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml
index 13c9acb..fd9dbb8 100644
--- a/org.eclipse.jgit.http.test/pom.xml
+++ b/org.eclipse.jgit.http.test/pom.xml
@@ -18,7 +18,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 850e895..b0d17ad 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -1728,7 +1728,8 @@ public void testPush_CreateBranch() throws Exception {
 		assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId());
 		fsck(remoteRepository, Q);
 
-		final ReflogReader log = remoteRepository.getReflogReader(dstName);
+		final ReflogReader log = remoteRepository.getRefDatabase()
+				.getReflogReader(dstName);
 		assertNotNull("has log for " + dstName, log);
 
 		final ReflogEntry last = log.getLastEntry();
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index d60dbed..09008d5 100644
--- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit.http
 Bundle-SymbolicName: org.eclipse.jgit.junit.http
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
@@ -22,17 +22,17 @@
  org.eclipse.jetty.util.component;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.security;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.ssl;version="[12.0.0,13.0.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.http.server;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.resolver;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.http.server;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.slf4j.helpers;version="[1.7.0,3.0.0)"
-Export-Package: org.eclipse.jgit.junit.http;version="7.1.2";
+Export-Package: org.eclipse.jgit.junit.http;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    jakarta.servlet,
    jakarta.servlet.http,
diff --git a/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
index 32c5433..c9b0ae7 100644
--- a/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit.http/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit.http - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.http.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index 0ff0cd9..aab32f4 100644
--- a/org.eclipse.jgit.junit.http/pom.xml
+++ b/org.eclipse.jgit.junit.http/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.http</artifactId>
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
index 1f9dcf4..7f255d7 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -3,46 +3,46 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit.ssh
 Bundle-SymbolicName: org.eclipse.jgit.junit.ssh
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.apache.sshd.common;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.file.virtualfs;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.helpers;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.io;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.kex;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.keyprovider;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.session;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.signature;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.buffer;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.logging;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.security;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.threads;version="[2.14.0,2.15.0)",
- org.apache.sshd.core;version="[2.14.0,2.15.0)",
- org.apache.sshd.server;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.auth;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.auth.gss;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.auth.keyboard;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.auth.password;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.command;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.session;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.shell;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.subsystem;version="[2.14.0,2.15.0)",
- org.apache.sshd.sftp;version="[2.14.0,2.15.0)",
- org.apache.sshd.sftp.server;version="[2.14.0,2.15.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+Import-Package: org.apache.sshd.common;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.file.virtualfs;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.helpers;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.io;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.kex;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.keyprovider;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.session;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.signature;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.buffer;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.logging;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.security;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.threads;version="[2.15.0,2.16.0)",
+ org.apache.sshd.core;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.auth;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.auth.gss;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.auth.keyboard;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.auth.password;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.command;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.session;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.shell;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.subsystem;version="[2.15.0,2.16.0)",
+ org.apache.sshd.sftp;version="[2.15.0,2.16.0)",
+ org.apache.sshd.sftp.server;version="[2.15.0,2.16.0)",
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
-Export-Package: org.eclipse.jgit.junit.ssh;version="7.1.2"
+Export-Package: org.eclipse.jgit.junit.ssh;version="7.2.2"
diff --git a/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
index 2fb060d..eea35bc 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit.ssh - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.ssh.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
index 6595b5b..78afd40 100644
--- a/org.eclipse.jgit.junit.ssh/pom.xml
+++ b/org.eclipse.jgit.junit.ssh/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit.ssh</artifactId>
diff --git a/org.eclipse.jgit.junit/.settings/.api_filters b/org.eclipse.jgit.junit/.settings/.api_filters
new file mode 100644
index 0000000..2781530
--- /dev/null
+++ b/org.eclipse.jgit.junit/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.junit" version="2">
+    <resource path="src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java" type="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase">
+        <filter id="336658481">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.junit.LocalDiskRepositoryTestCase"/>
+                <message_argument value="testRoot"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index 396df4c..2a84d0c 100644
--- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
@@ -3,36 +3,36 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.junit
 Bundle-SymbolicName: org.eclipse.jgit.junit
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.dircache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.merge;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="7.1.2",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.time;version="[7.1.2,7.2.0)",
+Import-Package: org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.dircache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.merge;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="7.2.2",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.time;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13,5.0.0)",
  org.junit.runners.model;version="[4.13,5.0.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
-Export-Package: org.eclipse.jgit.junit;version="7.1.2";
+Export-Package: org.eclipse.jgit.junit;version="7.2.2";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -45,4 +45,4 @@
    org.junit.runners.model,
    org.junit.runner,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.junit.time;version="7.1.2";uses:="org.eclipse.jgit.util.time"
+ org.eclipse.jgit.junit.time;version="7.2.2";uses:="org.eclipse.jgit.util.time"
diff --git a/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
index 8724466..ccf55a1 100644
--- a/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.junit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.junit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.junit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index aab23c8..be2e8bd 100644
--- a/org.eclipse.jgit.junit/pom.xml
+++ b/org.eclipse.jgit.junit/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java
new file mode 100644
index 0000000..eb23bec
--- /dev/null
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * 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.junit;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toUnmodifiableList;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.file.PackIndex.EntriesIterator;
+import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Create indexes with predefined data
+ *
+ * @since 7.2
+ */
+public class FakeIndexFactory {
+
+	/**
+	 * An object for the fake index
+	 *
+	 * @param name
+	 *            a sha1
+	 * @param offset
+	 *            the (fake) position of the object in the pack
+	 */
+	public record IndexObject(String name, long offset) {
+		/**
+		 * Name (sha1) as an objectId
+		 *
+		 * @return name (a sha1) as an objectId.
+		 */
+		public ObjectId getObjectId() {
+			return ObjectId.fromString(name);
+		}
+	}
+
+	/**
+	 * Return an index populated with these objects
+	 *
+	 * @param objs
+	 *            objects to be indexed
+	 * @return a PackIndex implementation
+	 */
+	public static PackIndex indexOf(List<IndexObject> objs) {
+		return new FakePackIndex(objs);
+	}
+
+	/**
+	 * Return a reverse pack index with these objects
+	 *
+	 * @param objs
+	 *            objects to be indexed
+	 * @return a PackReverseIndex implementation
+	 */
+	public static PackReverseIndex reverseIndexOf(List<IndexObject> objs) {
+		return new FakeReverseIndex(objs);
+	}
+
+	private FakeIndexFactory() {
+	}
+
+	private static class FakePackIndex implements PackIndex {
+		private static final Comparator<IndexObject> SHA1_COMPARATOR = (o1,
+				o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.name(),
+						o2.name());
+
+		private final Map<String, IndexObject> idx;
+
+		private final List<IndexObject> sha1Ordered;
+
+		private final long offset64count;
+
+		FakePackIndex(List<IndexObject> objs) {
+			sha1Ordered = objs.stream().sorted(SHA1_COMPARATOR)
+					.collect(toUnmodifiableList());
+			idx = objs.stream().collect(toMap(IndexObject::name, identity()));
+			offset64count = objs.stream()
+					.filter(o -> o.offset > Integer.MAX_VALUE).count();
+		}
+
+		@Override
+		public Iterator<MutableEntry> iterator() {
+			return new FakeEntriesIterator(sha1Ordered);
+		}
+
+		@Override
+		public long getObjectCount() {
+			return sha1Ordered.size();
+		}
+
+		@Override
+		public long getOffset64Count() {
+			return offset64count;
+		}
+
+		@Override
+		public ObjectId getObjectId(long nthPosition) {
+			return ObjectId
+					.fromString(sha1Ordered.get((int) nthPosition).name());
+		}
+
+		@Override
+		public long getOffset(long nthPosition) {
+			return sha1Ordered.get((int) nthPosition).offset();
+		}
+
+		@Override
+		public long findOffset(AnyObjectId objId) {
+			IndexObject o = idx.get(objId.name());
+			if (o == null) {
+				return -1;
+			}
+			return o.offset();
+		}
+
+		@Override
+		public int findPosition(AnyObjectId objId) {
+			IndexObject o = idx.get(objId.name());
+			if (o == null) {
+				return -1;
+			}
+			return sha1Ordered.indexOf(o);
+		}
+
+		@Override
+		public long findCRC32(AnyObjectId objId) throws MissingObjectException {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public boolean hasCRC32Support() {
+			return false;
+		}
+
+		@Override
+		public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+				int matchLimit) throws IOException {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public byte[] getChecksum() {
+			return new byte[0];
+		}
+	}
+
+	private static class FakeReverseIndex implements PackReverseIndex {
+		private static final Comparator<IndexObject> OFFSET_COMPARATOR = Comparator
+				.comparingLong(IndexObject::offset);
+
+		private final List<IndexObject> byOffset;
+
+		private final Map<Long, IndexObject> ridx;
+
+		FakeReverseIndex(List<IndexObject> objs) {
+			byOffset = objs.stream().sorted(OFFSET_COMPARATOR)
+					.collect(toUnmodifiableList());
+			ridx = byOffset.stream()
+					.collect(toMap(IndexObject::offset, identity()));
+		}
+
+		@Override
+		public void verifyPackChecksum(String packFilePath) {
+			// Do nothing
+		}
+
+		@Override
+		public ObjectId findObject(long offset) {
+			IndexObject indexObject = ridx.get(offset);
+			if (indexObject == null) {
+				return null;
+			}
+			return ObjectId.fromString(indexObject.name());
+		}
+
+		@Override
+		public long findNextOffset(long offset, long maxOffset)
+				throws CorruptObjectException {
+			IndexObject o = ridx.get(offset);
+			if (o == null) {
+				throw new CorruptObjectException("Invalid offset"); //$NON-NLS-1$
+			}
+			int pos = byOffset.indexOf(o);
+			if (pos == byOffset.size() - 1) {
+				return maxOffset;
+			}
+			return byOffset.get(pos + 1).offset();
+		}
+
+		@Override
+		public int findPosition(long offset) {
+			IndexObject indexObject = ridx.get(offset);
+			return byOffset.indexOf(indexObject);
+		}
+
+		@Override
+		public ObjectId findObjectByPosition(int nthPosition) {
+			return byOffset.get(nthPosition).getObjectId();
+		}
+	}
+
+	private static class FakeEntriesIterator extends EntriesIterator {
+
+		private static final byte[] buffer = new byte[Constants.OBJECT_ID_LENGTH];
+
+		private final Iterator<IndexObject> it;
+
+		FakeEntriesIterator(List<IndexObject> objs) {
+			super(objs.size());
+			it = objs.iterator();
+		}
+
+		@Override
+		protected void readNext() {
+			IndexObject next = it.next();
+			next.getObjectId().copyRawTo(buffer, 0);
+			setIdBuffer(buffer, 0);
+			setOffset(next.offset());
+		}
+	}
+}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
index 407290a..0d20f64 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java
@@ -20,19 +20,19 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.time.Instant;
+import java.time.ZoneId;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.internal.util.ShutdownHook;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -47,6 +47,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.junit.rules.TestName;
 
 /**
@@ -84,6 +85,16 @@ public abstract class LocalDiskRepositoryTestCase {
 	protected MockSystemReader mockSystemReader;
 
 	private final Set<Repository> toClose = new HashSet<>();
+
+	/**
+	 * Temporary test root directory for files created by tests.
+	 * @since 7.2
+	 */
+	@Rule
+	public TemporaryFolder testRoot = new TemporaryFolder();
+
+	Random rand = new Random();
+
 	private File tmp;
 
 	private File homeDir;
@@ -114,11 +125,8 @@ private String getTestName() {
 	 */
 	@Before
 	public void setUp() throws Exception {
-		tmp = File.createTempFile("jgit_" + getTestName() + '_', "_tmp");
-		Cleanup.deleteOnShutdown(tmp);
-		if (!tmp.delete() || !tmp.mkdir()) {
-			throw new IOException("Cannot create " + tmp);
-		}
+		tmp = testRoot.newFolder(getTestName() + rand.nextInt());
+
 		mockSystemReader = new MockSystemReader();
 		SystemReader.setInstance(mockSystemReader);
 
@@ -219,12 +227,6 @@ public void tearDown() throws Exception {
 			System.gc();
 		}
 		FS.DETECTED.setUserHome(homeDir);
-		if (tmp != null) {
-			recursiveDelete(tmp, false, true);
-		}
-		if (tmp != null && !tmp.exists()) {
-			Cleanup.removed(tmp);
-		}
 		SystemReader.setInstance(null);
 	}
 
@@ -233,8 +235,8 @@ public void tearDown() throws Exception {
 	 */
 	protected void tick() {
 		mockSystemReader.tick(5 * 60);
-		final long now = mockSystemReader.getCurrentTime();
-		final int tz = mockSystemReader.getTimezone(now);
+		Instant now = mockSystemReader.now();
+		ZoneId tz = mockSystemReader.getTimeZoneId();
 
 		author = new PersonIdent(author, now, tz);
 		committer = new PersonIdent(committer, now, tz);
@@ -623,41 +625,4 @@ protected String read(File f) throws IOException {
 	private static HashMap<String, String> cloneEnv() {
 		return new HashMap<>(System.getenv());
 	}
-
-	private static final class Cleanup {
-		private static final Cleanup INSTANCE = new Cleanup();
-
-		static {
-			ShutdownHook.INSTANCE.register(() -> INSTANCE.onShutdown());
-		}
-
-		private final Set<File> toDelete = ConcurrentHashMap.newKeySet();
-
-		private Cleanup() {
-			// empty
-		}
-
-		static void deleteOnShutdown(File tmp) {
-			INSTANCE.toDelete.add(tmp);
-		}
-
-		static void removed(File tmp) {
-			INSTANCE.toDelete.remove(tmp);
-		}
-
-		private void onShutdown() {
-			// On windows accidentally open files or memory
-			// mapped regions may prevent files from being deleted.
-			// Suggesting a GC increases the likelihood that our
-			// test repositories actually get removed after the
-			// tests, even in the case of failure.
-			System.gc();
-			synchronized (this) {
-				boolean silent = false;
-				boolean failOnError = false;
-				for (File tmp : toDelete)
-					recursiveDelete(tmp, silent, failOnError);
-			}
-		}
-	}
 }
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
index c6cdfaf..2d00a85 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java
@@ -21,6 +21,7 @@
 import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.time.Instant;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -158,8 +159,8 @@ public TestRepository(R db, RevWalk rw, MockSystemReader reader)
 		this.pool = rw;
 		this.inserter = db.newObjectInserter();
 		this.mockSystemReader = reader;
-		long now = mockSystemReader.getCurrentTime();
-		int tz = mockSystemReader.getTimezone(now);
+		Instant now = mockSystemReader.now();
+		ZoneId tz = mockSystemReader.getTimeZoneAt(now);
 		defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz);
 		defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz);
 	}
@@ -198,7 +199,9 @@ public Git git() {
 	 *
 	 * @return current date.
 	 * @since 4.2
+	 * @deprecated Use {@link #getInstant()} instead.
 	 */
+	@Deprecated(since = "7.2")
 	public Date getDate() {
 		return new Date(mockSystemReader.getCurrentTime());
 	}
@@ -210,18 +213,31 @@ public Date getDate() {
 	 * @since 6.8
 	 */
 	public Instant getInstant() {
-		return Instant.ofEpochMilli(mockSystemReader.getCurrentTime());
+		return mockSystemReader.now();
 	}
 
 	/**
 	 * Get timezone
 	 *
 	 * @return timezone used for default identities.
+	 * @deprecated Use {@link #getTimeZoneId()} instead.
 	 */
+	@Deprecated(since = "7.2")
 	public TimeZone getTimeZone() {
 		return mockSystemReader.getTimeZone();
 	}
 
+
+	/**
+	 * Get timezone
+	 *
+	 * @return timezone used for default identities.
+	 * @since 7.2
+	 */
+	public ZoneId getTimeZoneId() {
+		return mockSystemReader.getTimeZoneId();
+	}
+
 	/**
 	 * Adjust the current time that will used by the next commit.
 	 *
@@ -233,14 +249,14 @@ public void tick(int secDelta) {
 	}
 
 	/**
-	 * Set the author and committer using {@link #getDate()}.
+	 * Set the author and committer using {@link #getInstant()}.
 	 *
 	 * @param c
 	 *            the commit builder to store.
 	 */
 	public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) {
-		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
-		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
+		c.setAuthor(new PersonIdent(defaultAuthor, getInstant()));
+		c.setCommitter(new PersonIdent(defaultCommitter, getInstant()));
 	}
 
 	/**
@@ -488,8 +504,8 @@ public ObjectId unparsedCommit(final int secDelta, final RevTree tree,
 		c = new org.eclipse.jgit.lib.CommitBuilder();
 		c.setTreeId(tree);
 		c.setParentIds(parents);
-		c.setAuthor(new PersonIdent(defaultAuthor, getDate()));
-		c.setCommitter(new PersonIdent(defaultCommitter, getDate()));
+		c.setAuthor(new PersonIdent(defaultAuthor, getInstant()));
+		c.setCommitter(new PersonIdent(defaultCommitter, getInstant()));
 		c.setMessage("");
 		ObjectId id;
 		try (ObjectInserter ins = inserter) {
@@ -529,7 +545,7 @@ public RevTag tag(String name, RevObject dst) throws Exception {
 		final TagBuilder t = new TagBuilder();
 		t.setObjectId(dst);
 		t.setTag(name);
-		t.setTagger(new PersonIdent(defaultCommitter, getDate()));
+		t.setTagger(new PersonIdent(defaultCommitter, getInstant()));
 		t.setMessage("");
 		ObjectId id;
 		try (ObjectInserter ins = inserter) {
@@ -798,7 +814,7 @@ public RevCommit cherryPick(AnyObjectId id) throws Exception {
 			b.setParentId(head);
 			b.setTreeId(merger.getResultTreeId());
 			b.setAuthor(commit.getAuthorIdent());
-			b.setCommitter(new PersonIdent(defaultCommitter, getDate()));
+			b.setCommitter(new PersonIdent(defaultCommitter, getInstant()));
 			b.setMessage(commit.getFullMessage());
 			ObjectId result;
 			try (ObjectInserter ins = inserter) {
@@ -1408,7 +1424,7 @@ public RevCommit create() throws Exception {
 					c.setAuthor(author);
 				if (committer != null) {
 					if (updateCommitterTime)
-						committer = new PersonIdent(committer, getDate());
+						committer = new PersonIdent(committer, getInstant());
 					c.setCommitter(committer);
 				}
 
diff --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index 53f87cb..f182089 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -26,24 +26,24 @@
  org.eclipse.jetty.util.component;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.security;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.thread;version="[12.0.0,13.0.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.server;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.test;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.server;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.test;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/pom.xml b/org.eclipse.jgit.lfs.server.test/pom.xml
index 1568471..d06a23f 100644
--- a/org.eclipse.jgit.lfs.server.test/pom.xml
+++ b/org.eclipse.jgit.lfs.server.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
diff --git a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
index 925ada7..54bdb66 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/MANIFEST.MF
@@ -3,19 +3,19 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.server
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs.server;version="7.1.2";
+Export-Package: org.eclipse.jgit.lfs.server;version="7.2.2";
   uses:="jakarta.servlet.http,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="7.1.2";
+ org.eclipse.jgit.lfs.server.fs;version="7.2.2";
   uses:="jakarta.servlet,
    jakarta.servlet.http,
    org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.internal;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="7.1.2";
+ org.eclipse.jgit.lfs.server.internal;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="7.2.2";
   uses:="org.eclipse.jgit.lfs.server,
    org.eclipse.jgit.lfs.lib"
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -24,15 +24,15 @@
  jakarta.servlet.annotation;version="[6.0.0,7.0.0)",
  jakarta.servlet.http;version="[6.0.0,7.0.0)",
  org.apache.http;version="[4.3.0,5.0.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
diff --git a/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
index 7f52aa9..6b1e70f 100644
--- a/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.lfs.server - Sources
 Bundle-SymbolicName: org.eclipse.jgit.lfs.server.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index 819450b..af5af36 100644
--- a/org.eclipse.jgit.lfs.server/pom.xml
+++ b/org.eclipse.jgit.lfs.server/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.server</artifactId>
diff --git a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
index 38bbaa2..ac4c493 100644
--- a/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.test/META-INF/MANIFEST.MF
@@ -3,28 +3,28 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs.test
 Bundle-SymbolicName: org.eclipse.jgit.lfs.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.attributes;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+Import-Package: org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.attributes;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.dfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.hamcrest.core;version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13,5.0.0)"
-Export-Package: org.eclipse.jgit.lfs.test;version="7.1.2";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="7.2.2";x-friends:="org.eclipse.jgit.lfs.server.test"
diff --git a/org.eclipse.jgit.lfs.test/pom.xml b/org.eclipse.jgit.lfs.test/pom.xml
index 813823e..087caa4 100644
--- a/org.eclipse.jgit.lfs.test/pom.xml
+++ b/org.eclipse.jgit.lfs.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs.test</artifactId>
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index ec18f81..fdcde49 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -3,32 +3,32 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.lfs
 Bundle-SymbolicName: org.eclipse.jgit.lfs
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs;version="7.1.2",
- org.eclipse.jgit.lfs.errors;version="7.1.2",
- org.eclipse.jgit.lfs.internal;version="7.1.2";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
- org.eclipse.jgit.lfs.lib;version="7.1.2"
+Export-Package: org.eclipse.jgit.lfs;version="7.2.2",
+ org.eclipse.jgit.lfs.errors;version="7.2.2",
+ org.eclipse.jgit.lfs.internal;version="7.2.2";x-friends:="org.eclipse.jgit.lfs.test,org.eclipse.jgit.lfs.server.fs,org.eclipse.jgit.lfs.server",
+ org.eclipse.jgit.lfs.lib;version="7.2.2"
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
  com.google.gson.stream;version="[2.8.2,3.0.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.attributes;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.diff;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.dircache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.hooks;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)"
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.attributes;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.diff;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.dircache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.hooks;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)"
diff --git a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
index fa88770..630559d 100644
--- a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.lfs - Sources
 Bundle-SymbolicName: org.eclipse.jgit.lfs.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index c435419..2eb7774 100644
--- a/org.eclipse.jgit.lfs/pom.xml
+++ b/org.eclipse.jgit.lfs/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.lfs</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
index 26a207f..462274a 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
index 6ea0915..3635bb2 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
index 47cc345..bbe0ad8 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.gpg.bc"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
index 87ec0ed..0ec9ab7 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.gpg.bc.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
index 3246780..a3d06d9 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.http.apache"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
index 1fc7cbf..ca96a64 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
index 8a83e3c..54051cd 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.junit"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -24,7 +24,7 @@
 
    <requires>
       <import plugin="com.jcraft.jsch"/>
-      <import plugin="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
index 3eeb973..bd15173 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
index 5eba1c5..1e4fd55 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.lfs"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
index 3e90454..794932d 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.lfs.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
index 5f0691e..30e92cb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.pgm"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -35,9 +35,9 @@
          version="0.0.0"/>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
-      <import feature="org.eclipse.jgit.lfs" version="7.1.2" match="equivalent"/>
-      <import feature="org.eclipse.jgit.ssh.apache" version="7.1.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit.lfs" version="7.2.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit.ssh.apache" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
index 949a9c8..04c0550 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
index de54772..eef699c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml
@@ -123,12 +123,6 @@
    <bundle id="org.eclipse.jetty.util.ajax.source">
       <category name="JGit-dependency-bundles"/>
    </bundle>
-   <bundle id="net.i2p.crypto.eddsa">
-      <category name="JGit-dependency-bundles"/>
-   </bundle>
-   <bundle id="net.i2p.crypto.eddsa.source">
-      <category name="JGit-dependency-bundles"/>
-   </bundle>
    <bundle id="org.apache.ant">
       <category name="JGit-dependency-bundles"/>
    </bundle>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
index adf4e15..6c36c39 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.repository</artifactId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
index b6b5339..bd49058 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.source"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
index d67b629..7ffa079 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
@@ -30,7 +30,7 @@
     <dependency>
       <groupId>org.eclipse.jgit.feature</groupId>
       <artifactId>org.eclipse.jgit</artifactId>
-      <version>7.1.2-SNAPSHOT</version>
+      <version>7.2.2-SNAPSHOT</version>
     </dependency>
   </dependencies>
 
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
index 6546e46..1b3d932 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.ssh.apache"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import feature="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import feature="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
index aa071c9..7e4fb07 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
index f795e44..ccac152 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.jgit.ssh.jsch"
       label="%featureName"
-      version="7.1.2.qualifier"
+      version="7.2.2.qualifier"
       provider-name="%providerName">
 
    <description url="http://www.eclipse.org/jgit/">
@@ -23,7 +23,7 @@
    </url>
 
    <requires>
-      <import plugin="org.eclipse.jgit" version="7.1.2" match="equivalent"/>
+      <import plugin="org.eclipse.jgit" version="7.2.2" match="equivalent"/>
    </requires>
 
    <plugin
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
index b93d527..d661464 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.jsch.feature/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>jgit.tycho.parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <groupId>org.eclipse.jgit.feature</groupId>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.32.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.32.target
index efae5a1..60baf0b 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.32.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.32.target
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.32" sequenceNumber="1731963692">
+<target name="jgit-4.32" sequenceNumber="1740521280">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jzlib" version="1.1.3.v20230916-1400"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.3.v20230916-1400"/>
-      <unit id="net.i2p.crypto.eddsa" version="0.3.0"/>
-      <unit id="net.i2p.crypto.eddsa.source" version="0.3.0"/>
       <unit id="org.apache.ant" version="1.10.14.v20230922-1200"/>
       <unit id="org.apache.ant.source" version="1.10.14.v20230922-1200"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.14"/>
@@ -63,13 +61,13 @@
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-osgi</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-sftp</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -79,7 +77,7 @@
         <dependency>
           <groupId>org.mockito</groupId>
           <artifactId>mockito-core</artifactId>
-          <version>5.14.2</version>
+          <version>5.15.2</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -89,13 +87,13 @@
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna-platform</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -105,49 +103,49 @@
         <dependency>
           <groupId>org.eclipse.jetty.ee10</groupId>
           <artifactId>jetty-ee10-servlet</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-http</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-io</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-security</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-server</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-session</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util-ajax</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -183,7 +181,7 @@
         <dependency>
           <groupId>com.google.code.gson</groupId>
           <artifactId>gson</artifactId>
-          <version>2.11.0</version>
+          <version>2.12.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -193,13 +191,13 @@
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy-agent</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -209,25 +207,25 @@
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpg-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcprov-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpkix-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcutil-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -237,7 +235,7 @@
         <dependency>
           <groupId>org.assertj</groupId>
           <artifactId>assertj-core</artifactId>
-          <version>3.26.3</version>
+          <version>3.27.3</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -257,7 +255,7 @@
         <dependency>
           <groupId>commons-codec</groupId>
           <artifactId>commons-codec</artifactId>
-          <version>1.17.1</version>
+          <version>1.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -275,13 +273,13 @@
         <dependency>
           <groupId>commons-io</groupId>
           <artifactId>commons-io</artifactId>
-          <version>2.17.0</version>
+          <version>2.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
-          <version>1.3.4</version>
+          <version>1.3.5</version>
           <type>jar</type>
         </dependency>
       </dependencies>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target
index 1578e4c..1558ad6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.target
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.33" sequenceNumber="1731963694">
+<target name="jgit-4.33" sequenceNumber="1740521283">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jzlib" version="1.1.3.v20230916-1400"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.3.v20230916-1400"/>
-      <unit id="net.i2p.crypto.eddsa" version="0.3.0"/>
-      <unit id="net.i2p.crypto.eddsa.source" version="0.3.0"/>
       <unit id="org.apache.ant" version="1.10.14.v20230922-1200"/>
       <unit id="org.apache.ant.source" version="1.10.14.v20230922-1200"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.14"/>
@@ -63,13 +61,13 @@
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-osgi</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-sftp</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -79,7 +77,7 @@
         <dependency>
           <groupId>org.mockito</groupId>
           <artifactId>mockito-core</artifactId>
-          <version>5.14.2</version>
+          <version>5.15.2</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -89,13 +87,13 @@
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna-platform</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -105,49 +103,49 @@
         <dependency>
           <groupId>org.eclipse.jetty.ee10</groupId>
           <artifactId>jetty-ee10-servlet</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-http</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-io</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-security</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-server</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-session</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util-ajax</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -183,7 +181,7 @@
         <dependency>
           <groupId>com.google.code.gson</groupId>
           <artifactId>gson</artifactId>
-          <version>2.11.0</version>
+          <version>2.12.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -193,13 +191,13 @@
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy-agent</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -209,25 +207,25 @@
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpg-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcprov-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpkix-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcutil-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -237,7 +235,7 @@
         <dependency>
           <groupId>org.assertj</groupId>
           <artifactId>assertj-core</artifactId>
-          <version>3.26.3</version>
+          <version>3.27.3</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -257,7 +255,7 @@
         <dependency>
           <groupId>commons-codec</groupId>
           <artifactId>commons-codec</artifactId>
-          <version>1.17.1</version>
+          <version>1.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -275,13 +273,13 @@
         <dependency>
           <groupId>commons-io</groupId>
           <artifactId>commons-io</artifactId>
-          <version>2.17.0</version>
+          <version>2.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
-          <version>1.3.4</version>
+          <version>1.3.5</version>
           <type>jar</type>
         </dependency>
       </dependencies>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.target
index 43678e7..bc35d2c 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.target
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.target
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.34" sequenceNumber="1731963696">
+<target name="jgit-4.34" sequenceNumber="1740521284">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jsch.source" version="0.1.55.v20230916-1400"/>
       <unit id="com.jcraft.jzlib" version="1.1.3.v20230916-1400"/>
       <unit id="com.jcraft.jzlib.source" version="1.1.3.v20230916-1400"/>
-      <unit id="net.i2p.crypto.eddsa" version="0.3.0"/>
-      <unit id="net.i2p.crypto.eddsa.source" version="0.3.0"/>
       <unit id="org.apache.ant" version="1.10.15.v20240901-1000"/>
       <unit id="org.apache.ant.source" version="1.10.15.v20240901-1000"/>
       <unit id="org.apache.httpcomponents.httpclient" version="4.5.14"/>
@@ -63,13 +61,13 @@
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-osgi</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.apache.sshd</groupId>
           <artifactId>sshd-sftp</artifactId>
-          <version>2.14.0</version>
+          <version>2.15.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -79,7 +77,7 @@
         <dependency>
           <groupId>org.mockito</groupId>
           <artifactId>mockito-core</artifactId>
-          <version>5.14.2</version>
+          <version>5.15.2</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -89,13 +87,13 @@
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.java.dev.jna</groupId>
           <artifactId>jna-platform</artifactId>
-          <version>5.15.0</version>
+          <version>5.16.0</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -105,49 +103,49 @@
         <dependency>
           <groupId>org.eclipse.jetty.ee10</groupId>
           <artifactId>jetty-ee10-servlet</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-http</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-io</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-security</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-server</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-session</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.eclipse.jetty</groupId>
           <artifactId>jetty-util-ajax</artifactId>
-          <version>12.0.15</version>
+          <version>12.0.16</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -183,7 +181,7 @@
         <dependency>
           <groupId>com.google.code.gson</groupId>
           <artifactId>gson</artifactId>
-          <version>2.11.0</version>
+          <version>2.12.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -193,13 +191,13 @@
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>net.bytebuddy</groupId>
           <artifactId>byte-buddy-agent</artifactId>
-          <version>1.15.10</version>
+          <version>1.17.1</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -209,25 +207,25 @@
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpg-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcprov-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcpkix-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>org.bouncycastle</groupId>
           <artifactId>bcutil-jdk18on</artifactId>
-          <version>1.79</version>
+          <version>1.80</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -237,7 +235,7 @@
         <dependency>
           <groupId>org.assertj</groupId>
           <artifactId>assertj-core</artifactId>
-          <version>3.26.3</version>
+          <version>3.27.3</version>
           <type>jar</type>
         </dependency>
       </dependencies>
@@ -257,7 +255,7 @@
         <dependency>
           <groupId>commons-codec</groupId>
           <artifactId>commons-codec</artifactId>
-          <version>1.17.1</version>
+          <version>1.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
@@ -275,13 +273,13 @@
         <dependency>
           <groupId>commons-io</groupId>
           <artifactId>commons-io</artifactId>
-          <version>2.17.0</version>
+          <version>2.18.0</version>
           <type>jar</type>
         </dependency>
         <dependency>
           <groupId>commons-logging</groupId>
           <artifactId>commons-logging</artifactId>
-          <version>1.3.4</version>
+          <version>1.3.5</version>
           <type>jar</type>
         </dependency>
       </dependencies>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.target
new file mode 100644
index 0000000..15cabc3
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.target
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?pde?>
+<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
+<target name="jgit-4.35" sequenceNumber="1740521286">
+  <locations>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
+      <unit id="com.jcraft.jsch.source" version="0.1.55.v20230916-1400"/>
+      <unit id="com.jcraft.jzlib" version="1.1.3.v20230916-1400"/>
+      <unit id="com.jcraft.jzlib.source" version="1.1.3.v20230916-1400"/>
+      <unit id="org.apache.ant" version="1.10.15.v20240901-1000"/>
+      <unit id="org.apache.ant.source" version="1.10.15.v20240901-1000"/>
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.14"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.14"/>
+      <unit id="org.apache.httpcomponents.httpcore" version="4.4.16"/>
+      <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.16"/>
+      <unit id="org.hamcrest.core" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.core.source" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.library" version="1.3.0.v20230809-1000"/>
+      <unit id="org.hamcrest.library.source" version="1.3.0.v20230809-1000"/>
+      <unit id="org.junit" version="4.13.2.v20240929-1000"/>
+      <unit id="org.junit.source" version="4.13.2.v20240929-1000"/>
+      <unit id="org.objenesis" version="3.4.0"/>
+      <unit id="org.objenesis.source" version="3.4.0"/>
+      <unit id="org.osgi.service.cm" version="1.6.1.202109301733"/>
+      <unit id="org.osgi.service.cm.source" version="1.6.1.202109301733"/>
+      <repository location="https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2025-03"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.eclipse.osgi" version="0.0.0"/>
+      <repository location="https://download.eclipse.org/staging/2025-03/"/>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="xz">
+      <dependencies>
+        <dependency>
+          <groupId>org.tukaani</groupId>
+          <artifactId>xz</artifactId>
+          <version>1.10</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="slf4j">
+      <dependencies>
+        <dependency>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-api</artifactId>
+          <version>1.7.36</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-simple</artifactId>
+          <version>1.7.36</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="sshd">
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.sshd</groupId>
+          <artifactId>sshd-osgi</artifactId>
+          <version>2.15.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.sshd</groupId>
+          <artifactId>sshd-sftp</artifactId>
+          <version>2.15.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="mockito">
+      <dependencies>
+        <dependency>
+          <groupId>org.mockito</groupId>
+          <artifactId>mockito-core</artifactId>
+          <version>5.15.2</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="jna">
+      <dependencies>
+        <dependency>
+          <groupId>net.java.dev.jna</groupId>
+          <artifactId>jna</artifactId>
+          <version>5.16.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>net.java.dev.jna</groupId>
+          <artifactId>jna-platform</artifactId>
+          <version>5.16.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="jetty">
+      <dependencies>
+        <dependency>
+          <groupId>org.eclipse.jetty.ee10</groupId>
+          <artifactId>jetty-ee10-servlet</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-http</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-io</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-security</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-server</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-session</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-util</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-util-ajax</artifactId>
+          <version>12.0.16</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>jakarta.servlet</groupId>
+          <artifactId>jakarta.servlet-api</artifactId>
+          <version>6.1.0</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="javaewah">
+      <dependencies>
+        <dependency>
+          <groupId>com.googlecode.javaewah</groupId>
+          <artifactId>JavaEWAH</artifactId>
+          <version>1.2.3</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="hamcrest">
+      <dependencies>
+        <dependency>
+          <groupId>org.hamcrest</groupId>
+          <artifactId>hamcrest</artifactId>
+          <version>2.2</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="gson">
+      <dependencies>
+        <dependency>
+          <groupId>com.google.code.gson</groupId>
+          <artifactId>gson</artifactId>
+          <version>2.12.1</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="bytebuddy">
+      <dependencies>
+        <dependency>
+          <groupId>net.bytebuddy</groupId>
+          <artifactId>byte-buddy</artifactId>
+          <version>1.17.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>net.bytebuddy</groupId>
+          <artifactId>byte-buddy-agent</artifactId>
+          <version>1.17.1</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="bouncycastle">
+      <dependencies>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcpg-jdk18on</artifactId>
+          <version>1.80</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcprov-jdk18on</artifactId>
+          <version>1.80</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcpkix-jdk18on</artifactId>
+          <version>1.80</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcutil-jdk18on</artifactId>
+          <version>1.80</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="assertj">
+      <dependencies>
+        <dependency>
+          <groupId>org.assertj</groupId>
+          <artifactId>assertj-core</artifactId>
+          <version>3.27.3</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="args4j">
+      <dependencies>
+        <dependency>
+          <groupId>args4j</groupId>
+          <artifactId>args4j</artifactId>
+          <version>2.37</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+    <location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="apache">
+      <dependencies>
+        <dependency>
+          <groupId>commons-codec</groupId>
+          <artifactId>commons-codec</artifactId>
+          <version>1.18.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-compress</artifactId>
+          <version>1.27.1</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-lang3</artifactId>
+          <version>3.17.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>commons-io</groupId>
+          <artifactId>commons-io</artifactId>
+          <version>2.18.0</version>
+          <type>jar</type>
+        </dependency>
+        <dependency>
+          <groupId>commons-logging</groupId>
+          <artifactId>commons-logging</artifactId>
+          <version>1.3.5</version>
+          <type>jar</type>
+        </dependency>
+      </dependencies>
+    </location>
+  </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.tpd
new file mode 100644
index 0000000..3c0646e
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.35.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.35" with source configurePhase
+
+include "orbit/orbit-4.35.tpd"
+include "maven/dependencies.tpd"
+
+location "https://download.eclipse.org/staging/2025-03/" {
+	org.eclipse.osgi lazy
+}
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 7b9a397..b292cf5 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.17.1"
+		version = "1.18.0"
 	}
 	dependency {
 		groupId = "org.apache.commons"
@@ -25,12 +25,12 @@
 	dependency {
 		groupId = "commons-io"
 		artifactId = "commons-io"
-		version = "2.17.0"
+		version = "2.18.0"
 	}
 	dependency {
 		groupId = "commons-logging"
 		artifactId = "commons-logging"
-		version = "1.3.4"
+		version = "1.3.5"
 	}
 }
 
@@ -56,7 +56,7 @@
 	dependency {
 		groupId = "org.assertj"
 		artifactId = "assertj-core"
-		version = "3.26.3"
+		version = "3.27.3"
 	}
 }
 
@@ -69,22 +69,22 @@
 	dependency {
 		groupId = "org.bouncycastle"
 		artifactId = "bcpg-jdk18on"
-		version = "1.79"
+		version = "1.80"
 	}
 	dependency {
 		groupId = "org.bouncycastle"
 		artifactId = "bcprov-jdk18on"
-		version = "1.79"
+		version = "1.80"
 	}
 	dependency {
 		groupId = "org.bouncycastle"
 		artifactId = "bcpkix-jdk18on"
-		version = "1.79"
+		version = "1.80"
 	}
 	dependency {
 		groupId = "org.bouncycastle"
 		artifactId = "bcutil-jdk18on"
-		version = "1.79"
+		version = "1.80"
 	}
 }
 
@@ -97,12 +97,12 @@
 	dependency {
 		groupId = "net.bytebuddy"
 		artifactId = "byte-buddy"
-		version = "1.15.10"
+		version = "1.17.1"
 	}
 	dependency {
 		groupId = "net.bytebuddy"
 		artifactId = "byte-buddy-agent"
-		version = "1.15.10"
+		version = "1.17.1"
 	}
 }
 
@@ -115,7 +115,7 @@
 	dependency {
 		groupId = "com.google.code.gson"
 		artifactId = "gson"
-		version = "2.11.0"
+		version = "2.12.1"
 	}
 }
 
@@ -154,42 +154,42 @@
 	dependency {
 		groupId = "org.eclipse.jetty.ee10"
 		artifactId = "jetty-ee10-servlet"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-http"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-io"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-security"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-server"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-session"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-util"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "org.eclipse.jetty"
 		artifactId = "jetty-util-ajax"
-		version = "12.0.15"
+		version = "12.0.16"
 	}
 	dependency {
 		groupId = "jakarta.servlet"
@@ -207,12 +207,12 @@
 	dependency {
 		groupId = "net.java.dev.jna"
 		artifactId = "jna"
-		version = "5.15.0"
+		version = "5.16.0"
 	}
 	dependency {
 		groupId = "net.java.dev.jna"
 		artifactId = "jna-platform"
-		version = "5.15.0"
+		version = "5.16.0"
 	}
 }
 
@@ -225,7 +225,7 @@
 	dependency {
 		groupId = "org.mockito"
 		artifactId = "mockito-core"
-		version = "5.14.2"
+		version = "5.15.2"
 	}
 }
 
@@ -238,12 +238,12 @@
 	dependency {
 		groupId = "org.apache.sshd"
 		artifactId = "sshd-osgi"
-		version = "2.14.0"
+		version = "2.15.0"
 	}
 	dependency {
 		groupId = "org.apache.sshd"
 		artifactId = "sshd-sftp"
-		version = "2.14.0"
+		version = "2.15.0"
 	}
 }
 
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd
deleted file mode 100644
index 22e2b01..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20200831200620-2020-09.tpd
+++ /dev/null
@@ -1,66 +0,0 @@
-target "R20200831200620-2020-09" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20200831200620/repository" {
-	com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
-	com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
-	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
-	org.apache.ant.source [1.10.8.v20200515-1239,1.10.8.v20200515-1239]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
-	org.apache.httpcomponents.httpclient.source [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
-	org.apache.httpcomponents.httpcore [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
-	org.apache.httpcomponents.httpcore.source [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.4.0.v20200318-1614,2.4.0.v20200318-1614]
-	org.apache.sshd.osgi.source [2.4.0.v20200318-1614,2.4.0.v20200318-1614]
-	org.apache.sshd.sftp [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
-	org.apache.sshd.sftp.source [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
-	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
-	org.slf4j.api.source [1.7.2.v20121108-1250,1.7.2.v20121108-1250]
-	org.slf4j.impl.log4j12 [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
-	org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
-	org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-	org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20201130205003-2020-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20201130205003-2020-12.tpd
deleted file mode 100644
index 08a0846..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20201130205003-2020-12.tpd
+++ /dev/null
@@ -1,66 +0,0 @@
-target "R20201130205003-2020-12" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20201130205003/repository" {
-	com.google.gson [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
-	com.google.gson.source [2.8.2.v20180104-1110,2.8.2.v20180104-1110]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
-	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
-	org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
-	org.apache.httpcomponents.httpclient.source [4.5.10.v20200830-2311,4.5.10.v20200830-2311]
-	org.apache.httpcomponents.httpcore [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
-	org.apache.httpcomponents.httpcore.source [4.4.12.v20200108-1212,4.4.12.v20200108-1212]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.4.0.v20200318-1614,2.4.0.v20200318-1614]
-	org.apache.sshd.osgi.source [2.4.0.v20200318-1614,2.4.0.v20200318-1614]
-	org.apache.sshd.sftp [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
-	org.apache.sshd.sftp.source [2.4.0.v20200319-1547,2.4.0.v20200319-1547]
-	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-	org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210223232630-2021-03.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210223232630-2021-03.tpd
deleted file mode 100644
index 605a43b..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210223232630-2021-03.tpd
+++ /dev/null
@@ -1,66 +0,0 @@
-target "R20210223232630-2021-03" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20210223232630/repository" {
-	com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626]
-	com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
-	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
-	org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.osgi.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.sftp [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.sftp.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-	org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd
deleted file mode 100644
index 83b5bb3..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd
+++ /dev/null
@@ -1,66 +0,0 @@
-target "R20210602031627-2021-06" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20210602031627/repository" {
-	com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626]
-	com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831]
-	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
-	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.10.v20210426-1926,1.10.10.v20210426-1926]
-	org.apache.ant.source [1.10.10.v20210426-1926,1.10.10.v20210426-1926]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.osgi.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.sftp [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.apache.sshd.sftp.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003]
-	org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926]
-	org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955]
-	org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514]
-	org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-	org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd
deleted file mode 100644
index 99f3520..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd
+++ /dev/null
@@ -1,73 +0,0 @@
-target "R20210825222808-2021-09" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20210825222808/repository" {
-	com.google.gson [2.8.7.v20210624-1215,2.8.7.v20210624-1215]
-	com.google.gson.source [2.8.7.v20210624-1215,2.8.7.v20210624-1215]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.12.v20210622-2206,1.1.12.v20210622-2206]
-	javaewah.source [1.1.12.v20210622-2206,1.1.12.v20210622-2206]
-	javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800]
-	javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323]
-	org.apache.ant [1.10.11.v20210720-1445,1.10.11.v20210720-1445]
-	org.apache.ant.source [1.10.11.v20210720-1445,1.10.11.v20210720-1445]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.20.0.v20210713-1928,1.20.0.v20210713-1928]
-	org.apache.commons.compress.source [1.20.0.v20210713-1928,1.20.0.v20210713-1928]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.osgi.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpg.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcprov [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcprov.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcutil [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcutil.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211122181901-2021-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211122181901-2021-12.tpd
deleted file mode 100644
index cd1d1c0..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211122181901-2021-12.tpd
+++ /dev/null
@@ -1,71 +0,0 @@
-target "R20211122181901-2021-12" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20211122181901/repository" {
-	com.google.gson [2.8.8.v20211029-0838,2.8.8.v20211029-0838]
-	com.google.gson.source [2.8.8.v20211029-0838,2.8.8.v20211029-0838]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	net.i2p.crypto.eddsa.source [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
-	org.apache.sshd.osgi [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.osgi.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpg.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcprov [1.69.0.v20210923-1401,1.69.0.v20210923-1401]
-	org.bouncycastle.bcprov.source [1.69.0.v20210923-1401,1.69.0.v20210923-1401]
-	org.bouncycastle.bcutil [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcutil.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211213173813-2021-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211213173813-2021-12.tpd
deleted file mode 100644
index 0c7c846..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20211213173813-2021-12.tpd
+++ /dev/null
@@ -1,69 +0,0 @@
-target "R20211213173813-2021-12" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20211213173813/repository" {
-	com.google.gson [2.8.8.v20211029-0838,2.8.8.v20211029-0838]
-	com.google.gson.source [2.8.8.v20211029-0838,2.8.8.v20211029-0838]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	net.i2p.crypto.eddsa.source [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225]
-	org.apache.sshd.osgi [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.osgi.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.apache.sshd.sftp.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpg.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcpkix.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcprov [1.69.0.v20210923-1401,1.69.0.v20210923-1401]
-	org.bouncycastle.bcprov.source [1.69.0.v20210923-1401,1.69.0.v20210923-1401]
-	org.bouncycastle.bcutil [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.bouncycastle.bcutil.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd
deleted file mode 100644
index fafc689..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220302172233-2022-03.tpd
+++ /dev/null
@@ -1,69 +0,0 @@
-target "R20220302172233" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20220302172233/repository" {
-	com.google.gson [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.google.gson.source [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	net.i2p.crypto.eddsa.source [0.3.0.v20210923-1401,0.3.0.v20210923-1401]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.httpcomponents.httpcore.source [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.sshd.osgi [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.osgi.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcpg.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcpkix [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcpkix.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcprov [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcprov.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcutil [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcutil.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220531185310-2022-06.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220531185310-2022-06.tpd
deleted file mode 100644
index 3c74497..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220531185310-2022-06.tpd
+++ /dev/null
@@ -1,69 +0,0 @@
-target "R20220531185310-2022-06" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20220531185310/repository" {
-	com.google.gson [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.google.gson.source [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.httpcomponents.httpcore.source [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.sshd.osgi [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.osgi.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.70.0.v20220507-1208,1.70.0.v20220507-1208]
-	org.bouncycastle.bcpg.source [1.70.0.v20220507-1208,1.70.0.v20220507-1208]
-	org.bouncycastle.bcpkix [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcpkix.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcprov [1.70.0.v20220507-1208,1.70.0.v20220507-1208]
-	org.bouncycastle.bcprov.source [1.70.0.v20220507-1208,1.70.0.v20220507-1208]
-	org.bouncycastle.bcutil [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.bouncycastle.bcutil.source [1.70.0.v20220105-1522,1.70.0.v20220105-1522]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220830213456-2022-09.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220830213456-2022-09.tpd
deleted file mode 100644
index 8db1018..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20220830213456-2022-09.tpd
+++ /dev/null
@@ -1,69 +0,0 @@
-target "R20220830213456-2022-09" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20220830213456/repository" {
-	com.google.gson [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.google.gson.source [2.8.9.v20220111-1409,2.8.9.v20220111-1409]
-	com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343]
-	com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534]
-	net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225]
-	org.apache.httpcomponents.httpcore [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.httpcomponents.httpcore.source [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.sshd.osgi [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.osgi.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.apache.sshd.sftp.source [2.8.0.v20211227-1750,2.8.0.v20211227-1750]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcpg.source [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcpkix [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcpkix.source [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcprov [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcprov.source [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcutil [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.bouncycastle.bcutil.source [1.71.0.v20220723-1943,1.71.0.v20220723-1943]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642]
-	org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519]
-	org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.slf4j.binding.simple.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20221123021534-2022-12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20221123021534-2022-12.tpd
deleted file mode 100644
index 378b848..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20221123021534-2022-12.tpd
+++ /dev/null
@@ -1,69 +0,0 @@
-target "S20230101190934" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20221123021534/repository" {
-	com.google.gson [2.9.1.v20220915-1632,2.9.1.v20220915-1632]
-	com.google.gson.source [2.9.1.v20220915-1632,2.9.1.v20220915-1632]
-	com.jcraft.jsch [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jsch.source [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.sun.jna [5.12.1.v20221103-2317,5.12.1.v20221103-2317]
-	com.sun.jna.source [5.12.1.v20221103-2317,5.12.1.v20221103-2317]
-	com.sun.jna.platform [5.12.1.v20221103-2317,5.12.1.v20221103-2317]
-	com.sun.jna.platform.source [5.12.1.v20221103-2317,5.12.1.v20221103-2317]
-	javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839]
-	net.bytebuddy.byte-buddy [1.12.18.v20221114-2102,1.12.18.v20221114-2102]
-	net.bytebuddy.byte-buddy.source [1.12.18.v20221114-2102,1.12.18.v20221114-2102]
-	net.bytebuddy.byte-buddy-agent [1.12.18.v20221114-2102,1.12.18.v20221114-2102]
-	net.bytebuddy.byte-buddy-agent.source [1.12.18.v20221114-2102,1.12.18.v20221114-2102]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.commons.codec [1.14.0.v20221112-0806,1.14.0.v20221112-0806]
-	org.apache.commons.codec.source [1.14.0.v20221112-0806,1.14.0.v20221112-0806]
-	org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100]
-	org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502]
-	org.apache.httpcomponents.httpclient [4.5.13.v20221112-0806,4.5.13.v20221112-0806]
-	org.apache.httpcomponents.httpclient.source [4.5.13.v20221112-0806,4.5.13.v20221112-0806]
-	org.apache.httpcomponents.httpcore [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.httpcomponents.httpcore.source [4.4.15.v20220209-2345,4.4.15.v20220209-2345]
-	org.apache.sshd.osgi [2.9.2.v20221117-1942,2.9.2.v20221117-1942]
-	org.apache.sshd.osgi.source [2.9.2.v20221117-1942,2.9.2.v20221117-1942]
-	org.apache.sshd.sftp [2.9.2.v20221117-1942,2.9.2.v20221117-1942]
-	org.apache.sshd.sftp.source [2.9.2.v20221117-1942,2.9.2.v20221117-1942]
-	org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104]
-	org.bouncycastle.bcpg [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcpg.source [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcpkix [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcpkix.source [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcprov [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcprov.source [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcutil [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.bouncycastle.bcutil.source [1.72.0.v20221013-1810,1.72.0.v20221013-1810]
-	org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
-	org.mockito.mockito-core [4.8.1.v20221103-2317,4.8.1.v20221103-2317]
-	org.mockito.mockito-core.source [4.8.1.v20221103-2317,4.8.1.v20221103-2317]
-	org.objenesis [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-	org.objenesis.source [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-	org.slf4j.api [1.7.30.v20221112-0806,1.7.30.v20221112-0806]
-	org.slf4j.api.source [1.7.30.v20221112-0806,1.7.30.v20221112-0806]
-	org.slf4j.binding.simple [1.7.30.v20221112-0806,1.7.30.v20221112-0806]
-	org.slf4j.binding.simple.source [1.7.30.v20221112-0806,1.7.30.v20221112-0806]
-	org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-	org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230302014618-2023-03.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230302014618-2023-03.tpd
deleted file mode 100644
index 8578b2c..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230302014618-2023-03.tpd
+++ /dev/null
@@ -1,27 +0,0 @@
-target "R20230302014618-2023-03" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20230302014618/repository" {
-	com.jcraft.jsch [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jsch.source [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.httpcomponents.httpclient [4.5.14.v20221207-1049,4.5.14.v20221207-1049]
-	org.apache.httpcomponents.httpclient.source [4.5.14.v20221207-1049,4.5.14.v20221207-1049]
-	org.apache.httpcomponents.httpcore [4.4.16.v20221207-1049,4.4.16.v20221207-1049]
-	org.apache.httpcomponents.httpcore.source [4.4.16.v20221207-1049,4.4.16.v20221207-1049]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.mockito.mockito-core [4.8.1.v20221103-2317,4.8.1.v20221103-2317]
-	org.mockito.mockito-core.source [4.8.1.v20221103-2317,4.8.1.v20221103-2317]
-	org.objenesis [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-	org.objenesis.source [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230531010532-2023-06.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230531010532-2023-06.tpd
deleted file mode 100644
index 46055d3..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20230531010532-2023-06.tpd
+++ /dev/null
@@ -1,25 +0,0 @@
-target "R20230531010532-2023-06" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/downloads/drops/R20230531010532/repository" {
-	com.jcraft.jsch [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jsch.source [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.httpcomponents.httpclient [4.5.14.v20230516-1249,4.5.14.v20230516-1249]
-	org.apache.httpcomponents.httpclient.source [4.5.14.v20230516-1249,4.5.14.v20230516-1249]
-	org.apache.httpcomponents.httpcore [4.4.16.v20221207-1049,4.4.16.v20221207-1049]
-	org.apache.httpcomponents.httpcore.source [4.4.16.v20221207-1049,4.4.16.v20221207-1049]
-	org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519]
-	org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246]
-	org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956]
-	org.objenesis [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-	org.objenesis.source [3.3.0.v20221103-2317,3.3.0.v20221103-2317]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.29.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.29.tpd
deleted file mode 100644
index 70a17a1..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.29.tpd
+++ /dev/null
@@ -1,27 +0,0 @@
-target "orbit-4.29" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/release/4.29.0" {
-	com.jcraft.jsch [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jsch.source [0.1.55.v20221112-0806,0.1.55.v20221112-0806]
-	com.jcraft.jzlib [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	com.jcraft.jzlib.source [1.1.3.v20220502-1820,1.1.3.v20220502-1820]
-	net.i2p.crypto.eddsa [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	net.i2p.crypto.eddsa.source [0.3.0.v20220506-1020,0.3.0.v20220506-1020]
-	org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452]
-	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
-	org.apache.httpcomponents.httpclient.source [4.5.14,4.5.14]
-	org.apache.httpcomponents.httpcore [4.4.16,4.4.16]
-	org.apache.httpcomponents.httpcore.source [4.4.16,4.4.16]
-	org.hamcrest.core [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.core.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.library [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.library.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.junit [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.junit.source [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.objenesis [3.3,3.3]
-	org.objenesis.source [3.3,3.3]
-	org.osgi.service.cm [1.6.1.202109301733,1.6.1.202109301733]
-	org.osgi.service.cm.source [1.6.1.202109301733,1.6.1.202109301733]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd
deleted file mode 100644
index 9d00cb4..0000000
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.31.tpd
+++ /dev/null
@@ -1,27 +0,0 @@
-target "orbit-4.31" with source configurePhase
-// see https://download.eclipse.org/tools/orbit/downloads/
-
-location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2023-12" {
-	com.jcraft.jsch [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
-	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
-	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	net.i2p.crypto.eddsa [0.3.0,0.3.0]
-	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
-	org.apache.ant [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
-	org.apache.ant.source [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
-	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
-	org.apache.httpcomponents.httpclient.source [4.5.14,4.5.14]
-	org.apache.httpcomponents.httpcore [4.4.16,4.4.16]
-	org.apache.httpcomponents.httpcore.source [4.4.16,4.4.16]
-	org.hamcrest.core [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.core.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.library [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.hamcrest.library.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.junit [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.junit.source [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.objenesis [3.3,3.3]
-	org.objenesis.source [3.3,3.3]
-	org.osgi.service.cm [1.6.1.202109301733,1.6.1.202109301733]
-	org.osgi.service.cm.source [1.6.1.202109301733,1.6.1.202109301733]
-}
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.32.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.32.tpd
index 480e96e..59fcd87 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.32.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.32.tpd
@@ -6,8 +6,6 @@
 	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
 	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
 	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	net.i2p.crypto.eddsa [0.3.0,0.3.0]
-	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
 	org.apache.ant [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
 	org.apache.ant.source [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
 	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd
index 8dca4cb..2cfa0a8 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.33.tpd
@@ -6,8 +6,6 @@
 	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
 	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
 	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	net.i2p.crypto.eddsa [0.3.0,0.3.0]
-	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
 	org.apache.ant [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
 	org.apache.ant.source [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
 	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.34.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.34.tpd
index 15931db..d3e15bb 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.34.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.34.tpd
@@ -6,8 +6,6 @@
 	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
 	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
 	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	net.i2p.crypto.eddsa [0.3.0,0.3.0]
-	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
 	org.apache.ant [1.10.15.v20240901-1000,1.10.15.v20240901-1000]
 	org.apache.ant.source [1.10.15.v20240901-1000,1.10.15.v20240901-1000]
 	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.30.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.35.tpd
similarity index 68%
rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.30.tpd
rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.35.tpd
index 0554a85..ec6996e 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.30.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.35.tpd
@@ -1,15 +1,13 @@
-target "orbit-4.30" with source configurePhase
+target "orbit-4.35" with source configurePhase
 // see https://download.eclipse.org/tools/orbit/downloads/
 
-location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2023-12" {
+location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2025-03" {
 	com.jcraft.jsch [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
 	com.jcraft.jsch.source [0.1.55.v20230916-1400,0.1.55.v20230916-1400]
 	com.jcraft.jzlib [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
 	com.jcraft.jzlib.source [1.1.3.v20230916-1400,1.1.3.v20230916-1400]
-	net.i2p.crypto.eddsa [0.3.0,0.3.0]
-	net.i2p.crypto.eddsa.source [0.3.0,0.3.0]
-	org.apache.ant [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
-	org.apache.ant.source [1.10.14.v20230922-1200,1.10.14.v20230922-1200]
+	org.apache.ant [1.10.15.v20240901-1000,1.10.15.v20240901-1000]
+	org.apache.ant.source [1.10.15.v20240901-1000,1.10.15.v20240901-1000]
 	org.apache.httpcomponents.httpclient [4.5.14,4.5.14]
 	org.apache.httpcomponents.httpclient.source [4.5.14,4.5.14]
 	org.apache.httpcomponents.httpcore [4.4.16,4.4.16]
@@ -18,10 +16,10 @@
 	org.hamcrest.core.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
 	org.hamcrest.library [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
 	org.hamcrest.library.source [1.3.0.v20230809-1000,1.3.0.v20230809-1000]
-	org.junit [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.junit.source [4.13.2.v20230809-1000,4.13.2.v20230809-1000]
-	org.objenesis [3.3,3.3]
-	org.objenesis.source [3.3,3.3]
+	org.junit [4.13.2.v20240929-1000,4.13.2.v20240929-1000]
+	org.junit.source [4.13.2.v20240929-1000,4.13.2.v20240929-1000]
+	org.objenesis [3.4,3.4]
+	org.objenesis.source [3.4,3.4]
 	org.osgi.service.cm [1.6.1.202109301733,1.6.1.202109301733]
 	org.osgi.service.cm.source [1.6.1.202109301733,1.6.1.202109301733]
 }
diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml
index 37fe6e9..9a13ec8 100644
--- a/org.eclipse.jgit.packaging/pom.xml
+++ b/org.eclipse.jgit.packaging/pom.xml
@@ -16,7 +16,7 @@
 
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>jgit.tycho.parent</artifactId>
-  <version>7.1.2-SNAPSHOT</version>
+  <version>7.2.2-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>JGit Tycho Parent</name>
@@ -174,7 +174,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-enforcer-plugin</artifactId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
         <executions>
           <execution>
             <id>enforce-maven</id>
@@ -217,7 +217,7 @@
       <plugin>
         <groupId>org.cyclonedx</groupId>
         <artifactId>cyclonedx-maven-plugin</artifactId>
-        <version>2.8.0</version>
+        <version>2.9.1</version>
         <configuration>
           <projectType>library</projectType>
           <schemaVersion>1.4</schemaVersion>
@@ -246,7 +246,7 @@
       <plugin>
         <groupId>io.github.git-commit-id</groupId>
         <artifactId>git-commit-id-maven-plugin</artifactId>
-        <version>8.0.2</version>
+        <version>9.0.1</version>
         <executions>
           <execution>
             <id>get-the-git-infos</id>
@@ -286,7 +286,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
-          <version>3.4.1</version>
+          <version>3.4.2</version>
           <configuration>
             <archive>
               <manifestEntries>
@@ -394,7 +394,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.4.3</version>
+          <version>1.5.2</version>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
@@ -403,27 +403,27 @@
         </plugin>
         <plugin>
           <artifactId>maven-clean-plugin</artifactId>
-          <version>3.3.2</version>
+          <version>3.4.1</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-deploy-plugin</artifactId>
-          <version>3.1.2</version>
+          <version>3.1.3</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-install-plugin</artifactId>
-          <version>3.1.2</version>
+          <version>3.1.3</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-site-plugin</artifactId>
-          <version>4.0.0-M14</version>
+          <version>4.0.0-M16</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-artifact-plugin</artifactId>
-          <version>3.5.1</version>
+          <version>3.6.0</version>
           <configuration>
             <ignore>**/*cyclonedx.json</ignore>
             <reproducible>true</reproducible>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 7d3bc52..a787574 100644
--- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
@@ -3,30 +3,30 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.pgm.test
 Bundle-SymbolicName: org.eclipse.jgit.pgm.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.diff;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.dircache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.merge;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.pgm;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.pgm.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.pgm.opt;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)",
+Import-Package: org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.diff;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.dircache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.merge;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.pgm;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.pgm.opt;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)",
  org.hamcrest.core;bundle-version="[1.1.0,3.0.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml
index cca0d81..87ca920 100644
--- a/org.eclipse.jgit.pgm.test/pom.xml
+++ b/org.eclipse.jgit.pgm.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
index 6d6374f..a48fcbc 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Google Inc. and others
+ * Copyright (C) 2012, 2025 Google Inc. and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -12,7 +12,10 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
 
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.dircache.DirCache;
@@ -32,12 +35,7 @@ public void setUp() throws Exception {
 
 	@Test
 	public void testAddNothing() throws Exception {
-		try {
-			execute("git add");
-			fail("Must die");
-		} catch (Die e) {
-			// expected, requires argument
-		}
+		assertThrows(Die.class, () -> execute("git add"));
 	}
 
 	@Test
@@ -46,6 +44,17 @@ public void testAddUsage() throws Exception {
 	}
 
 	@Test
+	public void testAddInvalidOptionCombinations() throws Exception {
+		writeTrashFile("greeting", "Hello, world!");
+		assertThrows(Die.class, () -> execute("git add -u -A greeting"));
+		assertThrows(Die.class,
+				() -> execute("git add -u --ignore-removed greeting"));
+		// --renormalize implies -u
+		assertThrows(Die.class,
+				() -> execute("git add --renormalize --all greeting"));
+	}
+
+	@Test
 	public void testAddAFile() throws Exception {
 		writeTrashFile("greeting", "Hello, world!");
 		assertArrayEquals(new String[] { "" }, //
@@ -78,4 +87,34 @@ public void testAddAlreadyAdded() throws Exception {
 		assertNotNull(cache.getEntry("greeting"));
 		assertEquals(1, cache.getEntryCount());
 	}
+
+	@Test
+	public void testAddDeleted() throws Exception {
+		File greeting = writeTrashFile("greeting", "Hello, world!");
+		git.add().addFilepattern("greeting").call();
+		DirCache cache = db.readDirCache();
+		assertNotNull(cache.getEntry("greeting"));
+		assertEquals(1, cache.getEntryCount());
+		assertTrue(greeting.delete());
+		assertArrayEquals(new String[] { "" }, //
+				execute("git add greeting"));
+
+		cache = db.readDirCache();
+		assertEquals(0, cache.getEntryCount());
+	}
+
+	@Test
+	public void testAddDeleted2() throws Exception {
+		File greeting = writeTrashFile("greeting", "Hello, world!");
+		git.add().addFilepattern("greeting").call();
+		DirCache cache = db.readDirCache();
+		assertNotNull(cache.getEntry("greeting"));
+		assertEquals(1, cache.getEntryCount());
+		assertTrue(greeting.delete());
+		assertArrayEquals(new String[] { "" }, //
+				execute("git add -A"));
+
+		cache = db.readDirCache();
+		assertEquals(0, cache.getEntryCount());
+	}
 }
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
index a1fb9fb..c56cc6b 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
@@ -126,7 +126,7 @@ private RevCommit createSecondCommit() throws Exception {
 		JGitTestUtil.writeTrashFile(db, "Test.txt", "Some change");
 		git.add().addFilepattern("Test.txt").call();
 		return git.commit()
-				.setCommitter(new PersonIdent(this.committer, tr.getDate()))
+				.setCommitter(new PersonIdent(this.committer, tr.getInstant()))
 				.setMessage("Second commit").call();
 	}
 
@@ -134,7 +134,7 @@ private RevCommit createThirdCommit() throws Exception {
 		JGitTestUtil.writeTrashFile(db, "change.txt", "another change");
 		git.add().addFilepattern("change.txt").call();
 		return git.commit()
-				.setCommitter(new PersonIdent(this.committer, tr.getDate()))
+				.setCommitter(new PersonIdent(this.committer, tr.getInstant()))
 				.setMessage("Third commit").call();
 	}
 
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
index c785443..595767d 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java
@@ -123,6 +123,15 @@ public void testDescribeCommitMatch2() throws Exception {
 	}
 
 	@Test
+	public void testDescribeExclude() throws Exception {
+		initialCommitAndTag();
+		secondCommit();
+		git.tag().setName("v2.0").call();
+		assertArrayEquals(new String[] { "v1.0-1-g56f6ceb", "" },
+				execute("git describe --exclude v2.*"));
+	}
+
+	@Test
 	public void testDescribeCommitMultiMatch() throws Exception {
 		initialCommitAndTag();
 		secondCommit();
@@ -133,6 +142,17 @@ public void testDescribeCommitMultiMatch() throws Exception {
 	}
 
 	@Test
+	public void testDescribeCommitMultiExclude() throws Exception {
+		initialCommitAndTag();
+		secondCommit();
+		git.tag().setName("v2.0.0").call();
+		git.tag().setName("v2.1.1").call();
+		git.tag().setName("v2.2").call();
+		assertArrayEquals("git yields v2.2", new String[] { "v2.2", "" },
+				execute("git describe --exclude v2.0* --exclude v2.1.*"));
+	}
+
+	@Test
 	public void testDescribeCommitNoMatch() throws Exception {
 		initialCommitAndTag();
 		writeTrashFile("greeting", "Hello, world!");
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index b889686..4fc96e9 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.pgm
 Bundle-SymbolicName: org.eclipse.jgit.pgm
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -14,49 +14,50 @@
  org.eclipse.jetty.server.handler;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util;version="[12.0.0,13.0.0)",
  org.eclipse.jetty.util.component;version="[12.0.0,13.0.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.archive;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.awtui;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.blame;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.diff;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.dircache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.gitrepo;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.server;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.server.fs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs.server.s3;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.merge;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.notes;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revplot;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.resolver;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.sshd;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.archive;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.awtui;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.blame;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.diff;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.dircache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.gitrepo;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.io;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.midx;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.server;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.server.fs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs.server.s3;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.merge;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.notes;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revplot;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http.apache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.ssh.jsch;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.sshd;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)",
  org.kohsuke.args4j;version="[2.33.0,3.0.0)",
  org.kohsuke.args4j.spi;version="[2.33.0,3.0.0)"
-Export-Package: org.eclipse.jgit.console;version="7.1.2";
+Export-Package: org.eclipse.jgit.console;version="7.2.2";
  uses:="org.eclipse.jgit.transport,
   org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="7.1.2";
+ org.eclipse.jgit.pgm;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util.io,
    org.eclipse.jgit.awtui,
@@ -68,14 +69,14 @@
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.api,
    javax.swing",
- org.eclipse.jgit.pgm.debug;version="7.1.2";
+ org.eclipse.jgit.pgm.debug;version="7.2.2";
   uses:="org.eclipse.jgit.util.io,
    org.eclipse.jgit.pgm,
    org.eclipse.jetty.servlet",
- org.eclipse.jgit.pgm.internal;version="7.1.2";
+ org.eclipse.jgit.pgm.internal;version="7.2.2";
   x-friends:="org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="7.1.2";
+ org.eclipse.jgit.pgm.opt;version="7.2.2";
   uses:="org.kohsuke.args4j,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
index 9acb609..7491846 100644
--- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.pgm - Sources
 Bundle-SymbolicName: org.eclipse.jgit.pgm.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index 41b0091..6bf88d9 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -26,6 +26,7 @@
 org.eclipse.jgit.pgm.Merge
 org.eclipse.jgit.pgm.MergeBase
 org.eclipse.jgit.pgm.MergeTool
+org.eclipse.jgit.pgm.MultiPackIndex
 org.eclipse.jgit.pgm.PackRefs
 org.eclipse.jgit.pgm.Push
 org.eclipse.jgit.pgm.ReceivePack
diff --git a/org.eclipse.jgit.pgm/jgit.sh b/org.eclipse.jgit.pgm/jgit.sh
index a369220..3f241e8 100644
--- a/org.eclipse.jgit.pgm/jgit.sh
+++ b/org.eclipse.jgit.pgm/jgit.sh
@@ -110,9 +110,9 @@
 	LESS=${LESS:-FSRX}
 	export LESS
 
-	"$java" $java_args org.springframework.boot.loader.JarLauncher "$@" | $use_pager
+	"$java" $java_args org.springframework.boot.loader.launch.JarLauncher "$@" | $use_pager
 	exit
 else
-  exec "$java" $java_args org.springframework.boot.loader.JarLauncher "$@"
+  exec "$java" $java_args org.springframework.boot.loader.launch.JarLauncher "$@"
   exit 1
 fi
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index b1baa16..4585433 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.pgm</artifactId>
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
index d24b639..e9630e9 100644
--- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
+++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
@@ -12,6 +12,7 @@
 # default meta variable defined in the org.kohsuke.args4j.spi.OneArgumentOptionHandler
 N=N
 
+addIncompatibleOptions=--update/-u cannot be combined with --all/-A/--no-ignore-removal or --no-all/--ignore-removal. Note that --renormalize implies --update.
 alreadyOnBranch=Already on ''{0}''
 alreadyUpToDate=Already up-to-date.
 answerNo=n
@@ -255,7 +256,9 @@
 untrackedFiles=Untracked files:
 updating=Updating {0}..{1}
 usage_Abbrev=Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with a default of 7) of the abbreviated object name, use <n> digits, or as many digits as needed to form a unique object name. An <n> of 0 will suppress long format, only showing the closest tag.
-usage_addRenormalize=Apply the "clean" process freshly to tracked files to forcibly add them again to the index. This implies -u.
+usage_addRenormalize=Apply the "clean" process freshly to tracked files to forcibly add them again to the index. This implies --update/-u.
+usage_addStageDeletions=Add, modify, or remove index entries to match the working tree. Cannot be used with --update/-u.
+usage_addDontStageDeletions=Only add or modify index entries, but do not remove index entries for which there is no file. (Don''t stage deletions.) Cannot be used with --update/-u.
 usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
 usage_All=Pack all refs, except hidden refs, broken refs, and symbolic refs.
 usage_AlwaysFallback=Show uniquely abbreviated commit object as fallback
@@ -279,6 +282,7 @@
 usage_Describe=Show the most recent tag that is reachable from a commit
 usage_DiffAlgorithms=Test performance of jgit's diff algorithms
 usage_DisplayTheVersionOfJgit=Display the version of jgit
+usage_Exclude=Do not consider tags matching the given glob(7) pattern, excluding the "refs/tags/" prefix
 usage_Gc=Cleanup unnecessary files and optimize the local repository
 usage_Glog=View commit history as a graph
 usage_DiffGuiTool=When git-difftool is invoked with the -g or --gui option the default diff tool will be read from the configured diff.guitool variable instead of diff.tool.
@@ -300,6 +304,7 @@
 usage_Match=Only consider tags matching the given glob(7) pattern or patterns, excluding the "refs/tags/" prefix.
 usage_MergeBase=Find as good common ancestors as possible for a merge
 usage_MergesTwoDevelopmentHistories=Merges two development histories
+usage_MultiPackIndex=Operations over the multipack index
 usage_PackKeptObjects=Include objects in packs locked by a ".keep" file when repacking
 usage_PackRefs=Pack heads and tags for efficient repository access
 usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
index 2ebab5e..dc9d77d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Add.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> and others
+ * Copyright (C) 2010, 2025 Sasa Zivkov <sasa.zivkov@sap.com> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -16,6 +16,7 @@
 import org.eclipse.jgit.api.AddCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.pgm.internal.CLIText;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
@@ -28,17 +29,33 @@ class Add extends TextBuiltin {
 	@Option(name = "--update", aliases = { "-u" }, usage = "usage_onlyMatchAgainstAlreadyTrackedFiles")
 	private boolean update = false;
 
-	@Argument(required = true, metaVar = "metaVar_filepattern", usage = "usage_filesToAddContentFrom")
+	@Option(name = "--all", aliases = { "-A",
+			"--no-ignore-removal" }, usage = "usage_addStageDeletions")
+	private Boolean all;
+
+	@Option(name = "--no-all", aliases = {
+			"--ignore-removal" }, usage = "usage_addDontStageDeletions")
+	private void noAll(@SuppressWarnings("unused") boolean ignored) {
+		all = Boolean.FALSE;
+	}
+
+	@Argument(metaVar = "metaVar_filepattern", usage = "usage_filesToAddContentFrom")
 	private List<String> filepatterns = new ArrayList<>();
 
 	@Override
 	protected void run() throws Exception {
 		try (Git git = new Git(db)) {
-			AddCommand addCmd = git.add();
 			if (renormalize) {
 				update = true;
 			}
+			if (update && all != null) {
+				throw die(CLIText.get().addIncompatibleOptions);
+			}
+			AddCommand addCmd = git.add();
 			addCmd.setUpdate(update).setRenormalize(renormalize);
+			if (all != null) {
+				addCmd.setAll(all.booleanValue());
+			}
 			for (String p : filepatterns) {
 				addCmd.addFilepattern(p);
 			}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
index d2285ae..285fe2a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java
@@ -18,7 +18,7 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -91,7 +91,7 @@ void ignoreAllSpace(@SuppressWarnings("unused") boolean on) {
 
 	private final Map<RevCommit, String> abbreviatedCommits = new HashMap<>();
 
-	private SimpleDateFormat dateFmt;
+	private DateTimeFormatter dateFmt;
 
 	private int begin;
 
@@ -125,9 +125,9 @@ protected void run() {
 		}
 
 		if (showRawTimestamp) {
-			dateFmt = new SimpleDateFormat("ZZZZ"); //$NON-NLS-1$
+			dateFmt = DateTimeFormatter.ofPattern("ZZ"); //$NON-NLS-1$
 		} else {
-			dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZZ"); //$NON-NLS-1$
+			dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss ZZ"); //$NON-NLS-1$
 		}
 
 		try (ObjectReader reader = db.newObjectReader();
@@ -335,12 +335,14 @@ private String date(int line) {
 		if (author == null)
 			return ""; //$NON-NLS-1$
 
-		dateFmt.setTimeZone(author.getTimeZone());
-		if (!showRawTimestamp)
-			return dateFmt.format(author.getWhen());
+		if (!showRawTimestamp) {
+			return dateFmt.withZone(author.getZoneId())
+					.format(author.getWhenAsInstant());
+		}
 		return String.format("%d %s", //$NON-NLS-1$
-				Long.valueOf(author.getWhen().getTime() / 1000L),
-				dateFmt.format(author.getWhen()));
+				Long.valueOf(author.getWhenAsInstant().getEpochSecond()),
+				dateFmt.withZone(author.getZoneId())
+						.format(author.getWhenAsInstant()));
 	}
 
 	private String abbreviate(ObjectReader reader, RevCommit commit)
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
index 913d7c7..2633336 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Describe.java
@@ -44,6 +44,9 @@ class Describe extends TextBuiltin {
 	@Option(name = "--match", usage = "usage_Match", metaVar = "metaVar_pattern")
 	private List<String> patterns = new ArrayList<>();
 
+	@Option(name = "--exclude", usage = "usage_Exclude", metaVar = "metaVar_pattern")
+	private List<String> excludes = new ArrayList<>();
+
 	@Option(name = "--abbrev", usage = "usage_Abbrev")
 	private Integer abbrev;
 
@@ -59,6 +62,7 @@ protected void run() {
 			cmd.setTags(useTags);
 			cmd.setAlways(always);
 			cmd.setMatch(patterns.toArray(new String[0]));
+			cmd.setExclude(excludes.toArray(new String[0]));
 			if (abbrev != null) {
 				cmd.setAbbrev(abbrev.intValue());
 			}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java
new file mode 100644
index 0000000..1844223
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java
@@ -0,0 +1,107 @@
+/*
+ * 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.pgm;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+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;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexPrettyPrinter;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexWriter;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+@Command(common = true, usage = "usage_MultiPackIndex")
+@SuppressWarnings("nls")
+class MultiPackIndex extends TextBuiltin {
+	@Argument(index = 0, required = true, usage = "write, print")
+	private String command;
+
+	@Option(name = "--midx")
+	private String midxPath;
+
+	/** {@inheritDoc} */
+	@Override
+	protected void run() throws IOException {
+		switch (command) {
+		case "print":
+			printMultiPackIndex();
+			break;
+		case "write":
+			writeMultiPackIndex();
+			break;
+		default:
+			outw.println("Unknown command " + command);
+		}
+	}
+
+	private void printMultiPackIndex() {
+		if (midxPath == null || midxPath.isEmpty()) {
+			throw die("'print' requires the path of a multipack "
+					+ "index file with --midx option.");
+		}
+
+		try (FileInputStream is = new FileInputStream(midxPath)) {
+			PrintWriter pw = new PrintWriter(outw, true);
+			MultiPackIndexPrettyPrinter.prettyPrint(is.readAllBytes(), pw);
+		} catch (FileNotFoundException e) {
+			throw die(true, e);
+		} catch (IOException e) {
+			throw die(true, e);
+		}
+	}
+
+	private void writeMultiPackIndex() throws IOException {
+		if (!(db.getObjectDatabase() instanceof ObjectDirectory)) {
+			throw die("This repository object db doesn't have packs");
+		}
+
+		File midx;
+		if (midxPath == null || midxPath.isEmpty()) {
+			midx = new File(((ObjectDirectory) db.getObjectDatabase())
+					.getPackDirectory(), "multi-pack-index");
+		} else {
+			midx = new File(midxPath);
+		}
+
+		errw.println("Writing " + midx.getAbsolutePath());
+
+		ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
+
+		Map<String, PackIndex> indexes = new HashMap<>();
+		for (Pack pack : odb.getPacks()) {
+			PackFile packFile = pack.getPackFile().create(PackExt.INDEX);
+			try {
+				indexes.put(packFile.getName(), pack.getIndex());
+			} catch (IOException e) {
+				throw die("Cannot open index in pack", e);
+			}
+		}
+
+		MultiPackIndexWriter writer = new MultiPackIndexWriter();
+		try (FileOutputStream out = new FileOutputStream(midxPath)) {
+			writer.write(NullProgressMonitor.INSTANCE, out, indexes);
+		} catch (IOException e) {
+			throw die("Cannot write midx " + midxPath, e);
+		}
+	}
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
index 1576792..a3a6782 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
@@ -14,11 +14,10 @@
 
 import java.io.BufferedOutputStream;
 import java.io.IOException;
-import java.text.DateFormat;
 import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.Locale;
-import java.util.TimeZone;
 
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.diff.RawTextComparator;
@@ -51,9 +50,9 @@
 
 @Command(common = true, usage = "usage_show")
 class Show extends TextBuiltin {
-	private final TimeZone myTZ = TimeZone.getDefault();
+	private final ZoneId myTZ = ZoneId.systemDefault();
 
-	private final DateFormat fmt;
+	private final DateTimeFormatter fmt;
 
 	private DiffFormatter diffFmt;
 
@@ -157,7 +156,8 @@ void noPrefix(@SuppressWarnings("unused") boolean on) {
 	// END -- Options shared with Diff
 
 	Show() {
-		fmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy ZZZZZ", Locale.US); //$NON-NLS-1$
+		fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy ZZ", //$NON-NLS-1$
+				Locale.US);
 	}
 
 	@Override
@@ -232,15 +232,17 @@ private void show(RevTag tag) throws IOException {
 		outw.print(tag.getTagName());
 		outw.println();
 
-		final PersonIdent tagger = tag.getTaggerIdent();
+		PersonIdent tagger = tag.getTaggerIdent();
 		if (tagger != null) {
 			outw.println(MessageFormat.format(CLIText.get().taggerInfo,
 					tagger.getName(), tagger.getEmailAddress()));
 
-			final TimeZone taggerTZ = tagger.getTimeZone();
-			fmt.setTimeZone(taggerTZ != null ? taggerTZ : myTZ);
+			ZoneId taggerTZ = tagger.getZoneId();
+			String formattedTaggerTime = fmt
+					.withZone(taggerTZ != null ? taggerTZ : myTZ)
+					.format(tagger.getWhenAsInstant());
 			outw.println(MessageFormat.format(CLIText.get().dateInfo,
-					fmt.format(tagger.getWhen())));
+					formattedTaggerTime));
 		}
 
 		outw.println();
@@ -293,10 +295,12 @@ private void show(RevWalk rw, RevCommit c) throws IOException {
 		outw.println(MessageFormat.format(CLIText.get().authorInfo,
 				author.getName(), author.getEmailAddress()));
 
-		final TimeZone authorTZ = author.getTimeZone();
-		fmt.setTimeZone(authorTZ != null ? authorTZ : myTZ);
+		final ZoneId authorTZ = author.getZoneId();
+		String formattedAuthorTime = fmt
+				.withZone(authorTZ != null ? authorTZ : myTZ)
+				.format(author.getWhenAsInstant());
 		outw.println(MessageFormat.format(CLIText.get().dateInfo,
-				fmt.format(author.getWhen())));
+				formattedAuthorTime));
 
 		outw.println();
 		final String[] lines = c.getFullMessage().split("\n"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 2f96ef7..22d9e34 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -18,8 +18,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -166,7 +166,8 @@ private void recreateCommitGraph() throws IOException {
 
 					final CommitBuilder newc = new CommitBuilder();
 					newc.setTreeId(emptyTree);
-					newc.setAuthor(new PersonIdent(me, new Date(t.commitTime)));
+					newc.setAuthor(new PersonIdent(me,
+							Instant.ofEpochSecond(t.commitTime)));
 					newc.setCommitter(newc.getAuthor());
 					newc.setParentIds(newParents);
 					newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
index faa2bce..7aff2dd 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteReftable.java
@@ -24,6 +24,8 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -209,14 +211,15 @@ private static List<LogEntry> readLog(String logPath)
 				}
 				String ref = m.group(1);
 				double t = Double.parseDouble(m.group(2));
-				long time = ((long) t) * 1000L;
+				Instant time = Instant.ofEpochSecond((long) t);
 				long index = (long) (t * 1e6);
 				String user = m.group(3);
 				ObjectId oldId = parseId(m.group(4));
 				ObjectId newId = parseId(m.group(5));
 				String msg = m.group(6);
 				String email = user + "@gerrit"; //$NON-NLS-1$
-				PersonIdent who = new PersonIdent(user, email, time, -480);
+				PersonIdent who = new PersonIdent(user, email, time,
+						ZoneOffset.ofHours(-8));
 				log.add(new LogEntry(ref, index, who, oldId, newId, msg));
 			}
 		}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index b5bf6d2..bb1e950 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
- * Copyright (C) 2013, 2021 Obeo and others
+ * Copyright (C) 2013, 2025 Obeo 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
@@ -91,6 +91,7 @@ public static String fatalError(String message) {
 	}
 
 	// @formatter:off
+	/***/ public String addIncompatibleOptions;
 	/***/ public String alreadyOnBranch;
 	/***/ public String alreadyUpToDate;
 	/***/ public String answerNo;
diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
index 46319c9..2e7c78d 100644
--- a/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF
@@ -2,16 +2,16 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent;singleton:=true
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/agent
 Bundle-Vendor: %Bundle-Vendor
-Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[7.1.2,7.2.0)"
+Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[7.2.2,7.3.0)"
 Bundle-ActivationPolicy: lazy
 Automatic-Module-Name: org.eclipse.jgit.ssh.apache.agent
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.transport.sshd;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)"
+Import-Package: org.eclipse.jgit.transport.sshd;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)"
 Require-Bundle: com.sun.jna;bundle-version="[5.8.0,6.0.0)",
  com.sun.jna.platform;bundle-version="[5.8.0,6.0.0)"
-Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="7.1.2";x-internal:=true
+Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="7.2.2";x-internal:=true
diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
index 773bd02..7570bcc 100644
--- a/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.apache.agent - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache.agent/pom.xml b/org.eclipse.jgit.ssh.apache.agent/pom.xml
index 85ac2a9..229d2f6 100644
--- a/org.eclipse.jgit.ssh.apache.agent/pom.xml
+++ b/org.eclipse.jgit.ssh.apache.agent/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache.agent</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache.test/BUILD b/org.eclipse.jgit.ssh.apache.test/BUILD
index dfc059f..bf796c0 100644
--- a/org.eclipse.jgit.ssh.apache.test/BUILD
+++ b/org.eclipse.jgit.ssh.apache.test/BUILD
@@ -8,7 +8,9 @@
 )
 
 DEPS = [
-    "//lib:eddsa",
+    "//lib:bcpkix",
+    "//lib:bcprov",
+    "//lib:bcutil",
     "//lib:junit",
     "//lib:slf4j-api",
     "//lib:sshd-osgi",
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 21619de..37b4321 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -3,42 +3,47 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.apache.test
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Require-Bundle: org.hamcrest.core;bundle-version="[1.3.0,2.0.0)"
-Import-Package: org.apache.sshd.client.config.hosts;version="[2.14.0,2.15.0)",
- org.apache.sshd.common;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.auth;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.helpers;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.kex;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.keyprovider;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.session;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.signature;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.buffer;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.net;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.security;version="[2.14.0,2.15.0)",
- org.apache.sshd.core;version="[2.14.0,2.15.0)",
- org.apache.sshd.server;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.forward;version="[2.14.0,2.15.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.signing.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.sshd.proxy;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.sshd;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.sshd.agent;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+Import-Package: org.apache.sshd.certificate;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.config.hosts;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.auth;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.cipher;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.helpers;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.kex;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.keyprovider;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.session;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.signature;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.buffer;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.net;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.security;version="[2.15.0,2.16.0)",
+ org.apache.sshd.core;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.forward;version="[2.15.0,2.16.0)",
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.signing.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.sshd;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.sshd;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.sshd.agent;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
  org.junit.rules;version="[4.13.0,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)",
  org.junit.runners;version="[4.13.0,5.0.0)",
- org.slf4j;version="[1.7.0,2.0.0)"
+ org.slf4j;version="[1.7.0,3.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache.test/pom.xml b/org.eclipse.jgit.ssh.apache.test/pom.xml
index c53adb7..f0cd1d0 100644
--- a/org.eclipse.jgit.ssh.apache.test/pom.xml
+++ b/org.eclipse.jgit.ssh.apache.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java
index 90fde3f..84d8179 100644
--- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java
@@ -168,6 +168,14 @@ public void testValidLine() throws Exception {
 				"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
 				AllowedSigners.parseLine(
 						"*@a.com,*@b.a.com cert-authority namespaces=\"git\" valid-after=\"20240901\" valid-before=\"202409011200Z\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+		assertEquals(new AllowedSigners.AllowedEntry(
+				new String[] { "foo@a.com" },
+				false, new String[] { "git" },
+				Instant.parse("2024-09-01T03:30:00.00Z"),
+				Instant.parse("2024-09-01T12:00:00.00Z"),
+				"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxkz2AUld8eitmyIYlVV+Sot4jT3CigyBmvFRff0q4cSsKLx4x2TxGQeKKVueJEawtsUC2GNRV9FxXsTCUGcZU="),
+				AllowedSigners.parseLine(
+						"foo@a.com namespaces=\"git\" valid-after=\"20240901\" valid-before=\"202409011200Z\" ecdsa-sha2-nistp256   AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGxkz2AUld8eitmyIYlVV+Sot4jT3CigyBmvFRff0q4cSsKLx4x2TxGQeKKVueJEawtsUC2GNRV9FxXsTCUGcZU="));
 	}
 
 	@Test
@@ -183,6 +191,8 @@ public void testInvalidLine() {
 		assertThrows(Exception.class, () -> AllowedSigners.parseLine(
 				"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
 		assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+				"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO foo@bar.com"));
+		assertThrows(Exception.class, () -> AllowedSigners.parseLine(
 				"AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
 		assertThrows(Exception.class, () -> AllowedSigners.parseLine(
 				"a@a.com namespaces=\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
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.test/tst/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabaseTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabaseTest.java
new file mode 100644
index 0000000..6b61821
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabaseTest.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2025 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.certificate.OpenSshCertificateBuilder;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.sshd.ServerKeyDatabase;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Tests for {@link OpenSshServerKeyDatabase}.
+ */
+public class OpenSshServerKeyDatabaseTest {
+
+	private static final InetSocketAddress LOCAL = new InetSocketAddress(
+			InetAddress.getLoopbackAddress(), SshConstants.DEFAULT_PORT);
+
+	private static final InetSocketAddress LOCAL_29418 = new InetSocketAddress(
+			InetAddress.getLoopbackAddress(), 29418);
+
+	private static PublicKey rsa1024;
+	private static PublicKey rsa2048;
+	private static PublicKey ec256;
+	private static PublicKey ec384;
+	private static PublicKey caKey;
+	private static PublicKey certificate;
+
+	@BeforeClass
+	public static void initKeys() throws Exception {
+		// Generate a few keys that we can use
+		KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(KeyUtils.RSA_ALGORITHM);
+		gen.initialize(1024);
+		rsa1024 = gen.generateKeyPair().getPublic();
+		gen.initialize(2048);
+		rsa2048 = gen.generateKeyPair().getPublic();
+		gen = SecurityUtils.getKeyPairGenerator(KeyUtils.EC_ALGORITHM);
+		ECCurves curve = ECCurves.fromCurveSize(256);
+		gen.initialize(curve.getParameters());
+		ec256 = gen.generateKeyPair().getPublic();
+		PublicKey certKey = gen.generateKeyPair().getPublic();
+		curve = ECCurves.fromCurveSize(384);
+		gen.initialize(curve.getParameters());
+		ec384 = gen.generateKeyPair().getPublic();
+		// Generate a certificate for some key
+		gen.initialize(curve.getParameters());
+		KeyPair ca = gen.generateKeyPair();
+		caKey = ca.getPublic();
+		certificate = OpenSshCertificateBuilder
+				.hostCertificate()
+				.serial(System.currentTimeMillis())
+				.publicKey(certKey)
+				.id("test-host-cert")
+				.validBefore(
+						System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))
+				.principals(List.of("localhost", "127.0.0.1"))
+				.sign(ca, "ecdsa-sha2-nistp384");
+	}
+
+	@Rule
+	public TemporaryFolder tmp = new TemporaryFolder();
+
+	private Path knownHosts;
+	private Path knownHosts2;
+	private ServerKeyDatabase database;
+
+	@Before
+	public void setupDatabase() throws Exception {
+		Path root = tmp.getRoot().toPath();
+		knownHosts = root.resolve("known_hosts");
+		knownHosts2 = root.resolve("known_hosts2");
+		database = new OpenSshServerKeyDatabase(false, List.of(knownHosts, knownHosts2));
+	}
+
+	@Test
+	public void testFindInSecondFile() throws Exception {
+		Files.write(knownHosts,
+				List.of(
+						"some.other.host " + PublicKeyEntry.toString(rsa1024),
+						"some.other.com " + PublicKeyEntry.toString(ec384)));
+		Files.write(knownHosts2,
+				List.of(
+						"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+						"some.other.com " + PublicKeyEntry.toString(rsa2048)));
+		assertTrue(database.accept("localhost", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+		assertFalse(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testNoFirstFile() throws Exception {
+		Files.write(knownHosts2,
+				List.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+						"some.other.com " + PublicKeyEntry.toString(rsa2048)));
+		assertTrue(database.accept("localhost", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testFind() throws Exception {
+		Files.write(knownHosts,
+				List.of("localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(rsa1024),
+						"some.other.com " + PublicKeyEntry.toString(ec384)));
+		Files.write(knownHosts2,
+				List.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+						"some.other.com " + PublicKeyEntry.toString(rsa2048)));
+		assertTrue(database.accept("localhost", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+		assertTrue(database.accept("localhost:22", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+		assertTrue(database.accept("127.0.0.1", LOCAL, rsa1024,
+				new KnownHostsConfig(), null));
+		assertTrue(database.accept("[127.0.0.1]:22", LOCAL, rsa1024,
+				new KnownHostsConfig(), null));
+		assertFalse(database.accept("localhost:29418", LOCAL_29418, ec256,
+				new KnownHostsConfig(), null));
+		assertFalse(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testFindCertificate() throws Exception {
+		Files.write(knownHosts,
+				List.of("localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(rsa1024),
+						"some.other.com " + PublicKeyEntry.toString(ec384),
+						"@cert-authority localhost,127.0.0.1 "
+								+ PublicKeyEntry.toString(caKey)));
+		assertTrue(database.accept("localhost", LOCAL, certificate,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testCaKeyNotConsidered() throws Exception {
+		Files.write(knownHosts,
+				List.of("localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(rsa1024),
+						"some.other.com " + PublicKeyEntry.toString(ec384),
+						"@cert-authority localhost,127.0.0.1 "
+								+ PublicKeyEntry.toString(ec256)));
+		assertFalse(database.accept("localhost", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testkeyPlainAndCa() throws Exception {
+		Files.write(knownHosts, List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384),
+				"@cert-authority localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(ec256),
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256)));
+		// ec256 is a CA key, but also a valid direct host key for localhost
+		assertTrue(database.accept("localhost", LOCAL, ec256,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testLookupCertificate() throws Exception {
+		List<PublicKey> keys = database.lookup("localhost", LOCAL,
+				new KnownHostsConfig());
+		// Certificates or CA keys are not reported via lookup.
+		assertTrue(keys.isEmpty());
+	}
+
+	@Test
+	public void testCertificateNotAdded() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		assertFalse(database.accept("localhost", LOCAL, certificate,
+				new KnownHostsConfig(), null));
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertFalse(
+				database.accept("localhost", LOCAL, certificate,
+						new KnownHostsConfig(
+								KnownHostsConfig.StrictHostKeyChecking.ASK),
+						ui));
+		assertEquals(0, ui.invocations);
+		assertFile(knownHosts, initialKnownHosts);
+	}
+
+	@Test
+	public void testCertificateNotModified() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"@cert-authority localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(ec384),
+				"some.other.com " + PublicKeyEntry.toString(ec256));
+		Files.write(knownHosts, initialKnownHosts);
+		assertFalse(database.accept("localhost", LOCAL, certificate,
+				new KnownHostsConfig(), null));
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertFalse(
+				database.accept("localhost", LOCAL, certificate,
+						new KnownHostsConfig(
+								KnownHostsConfig.StrictHostKeyChecking.ASK),
+						ui));
+		assertEquals(0, ui.invocations);
+		assertFile(knownHosts, initialKnownHosts);
+	}
+
+	@Test
+	public void testModifyFile() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"some.other.host " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		assertFalse(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertFalse(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ACCEPT_NEW),
+				null));
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ACCEPT_ANY),
+				null));
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertFalse(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				null));
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, initialKnownHosts2);
+
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, false);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, initialKnownHosts2);
+
+		ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2, List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048)));
+		assertTrue(database.accept("127.0.0.1", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testModifyFirstFile() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List.of(
+				"some.other.host " + PublicKeyEntry.toString(ec256),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts,
+				List.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384),
+						"some.other.com " + PublicKeyEntry.toString(ec384)));
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testModifyMatchingKeyType() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, rsa2048,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts,
+				List.of("localhost,127.0.0.1 "
+						+ PublicKeyEntry.toString(rsa2048),
+						"some.other.com " + PublicKeyEntry.toString(ec384)));
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, rsa2048,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testModifyMatchingKeyType2() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts,
+				List.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384),
+						"some.other.com " + PublicKeyEntry.toString(ec384)));
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testModifySecondFile() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec256),
+				"some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts, initialKnownHosts);
+		assertFile(knownHosts2,
+				List.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384),
+						"some.other.com " + PublicKeyEntry.toString(rsa2048)));
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testAddNewKey() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"some.other.host " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec256));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List
+				.of("some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		List<String> expected = new ArrayList<>(initialKnownHosts);
+		expected.add("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384));
+		assertFile(knownHosts, expected);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testCreateNewFile() throws Exception {
+		List<String> initialKnownHosts2 = List
+				.of("some.other.com " + PublicKeyEntry.toString(ec256));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		assertFile(knownHosts, List
+				.of("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384)));
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testAddNewKey2() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"some.other.host " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec256));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List
+				.of("some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("127.0.0.1:29418", LOCAL_29418, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		List<String> expected = new ArrayList<>(initialKnownHosts);
+		expected.add("[127.0.0.1]:29418,[localhost]:29418 "
+				+ PublicKeyEntry.toString(ec384));
+		assertFile(knownHosts, expected);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("localhost:29418", LOCAL_29418, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testAddNewKey3() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"some.other.host " + PublicKeyEntry.toString(rsa1024),
+				"some.other.com " + PublicKeyEntry.toString(ec256));
+		Files.write(knownHosts, initialKnownHosts);
+		List<String> initialKnownHosts2 = List
+				.of("some.other.com " + PublicKeyEntry.toString(rsa2048));
+		Files.write(knownHosts2, initialKnownHosts2);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost:29418", LOCAL_29418, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		List<String> expected = new ArrayList<>(initialKnownHosts);
+		expected.add("[localhost]:29418,[127.0.0.1]:29418 "
+				+ PublicKeyEntry.toString(ec384));
+		assertFile(knownHosts, expected);
+		assertFile(knownHosts2, initialKnownHosts2);
+		assertTrue(database.accept("127.0.0.1:29418", LOCAL_29418, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	@Test
+	public void testUnknownKeyType() throws Exception {
+		List<String> initialKnownHosts = List.of(
+				"localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384)
+						.replace("ecdsa", "foo"),
+				"some.other.com " + PublicKeyEntry.toString(ec384));
+		Files.write(knownHosts, initialKnownHosts);
+		TestCredentialsProvider ui = new TestCredentialsProvider(true, true);
+		assertTrue(database.accept("localhost", LOCAL, ec384,
+				new KnownHostsConfig(
+						ServerKeyDatabase.Configuration.StrictHostKeyChecking.ASK),
+				ui));
+		assertEquals(1, ui.invocations);
+		// The "modified key" dialog has two questions; whereas the "add new
+		// key" is just a simple question.
+		assertEquals(2, ui.questions);
+		List<String> expected = new ArrayList<>(initialKnownHosts);
+		expected.add("localhost,127.0.0.1 " + PublicKeyEntry.toString(ec384));
+		assertFile(knownHosts, expected);
+		assertTrue(database.accept("127.0.0.1:22", LOCAL, ec384,
+				new KnownHostsConfig(), null));
+	}
+
+	private void assertFile(Path path, List<String> lines) throws Exception {
+		assertEquals(lines, Files.readAllLines(path).stream()
+				.filter(s -> !s.isBlank()).toList());
+	}
+
+	private static class TestCredentialsProvider extends CredentialsProvider {
+
+		private final boolean[] values;
+
+		int invocations = 0;
+
+		int questions = 0;
+
+		TestCredentialsProvider(boolean accept, boolean store) {
+			values = new boolean[] { accept, store };
+		}
+
+		@Override
+		public boolean isInteractive() {
+			return true;
+		}
+
+		@Override
+		public boolean supports(CredentialItem... items) {
+			return true;
+		}
+
+		@Override
+		public boolean get(URIish uri, CredentialItem... items)
+				throws UnsupportedCredentialItem {
+			invocations++;
+			int i = 0;
+			for (CredentialItem item : items) {
+				if (item instanceof CredentialItem.YesNoType) {
+					((CredentialItem.YesNoType) item)
+							.setValue(i < values.length && values[i++]);
+					questions++;
+				}
+			}
+			return true;
+		}
+	}
+
+	private static class KnownHostsConfig implements ServerKeyDatabase.Configuration {
+
+		@NonNull
+		private final StrictHostKeyChecking check;
+
+		KnownHostsConfig() {
+			this(StrictHostKeyChecking.REQUIRE_MATCH);
+		}
+
+		KnownHostsConfig(@NonNull StrictHostKeyChecking check) {
+			this.check = check;
+		}
+
+		@Override
+		public List<String> getUserKnownHostsFiles() {
+			return List.of();
+		}
+
+		@Override
+		public List<String> getGlobalKnownHostsFiles() {
+			return List.of();
+		}
+
+		@Override
+		public StrictHostKeyChecking getStrictHostKeyChecking() {
+			return check;
+		}
+
+		@Override
+		public boolean getHashKnownHosts() {
+			return false;
+		}
+
+		@Override
+		public String getUsername() {
+			return "user";
+		}
+
+	}
+}
diff --git a/org.eclipse.jgit.ssh.apache/BUILD b/org.eclipse.jgit.ssh.apache/BUILD
index fd88a8a..83709c3 100644
--- a/org.eclipse.jgit.ssh.apache/BUILD
+++ b/org.eclipse.jgit.ssh.apache/BUILD
@@ -12,7 +12,6 @@
     resource_strip_prefix = "org.eclipse.jgit.ssh.apache/resources",
     resources = RESOURCES,
     deps = [
-        "//lib:eddsa",
         "//lib:slf4j-api",
         "//lib:sshd-osgi",
         "//lib:sshd-sftp",
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index d12eed0..6a39ae0 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -6,10 +6,10 @@
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-ActivationPolicy: lazy
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.internal.signing.ssh;version="7.1.2";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.internal.transport.sshd;version="7.1.2";x-internal:=true;
+Export-Package: org.eclipse.jgit.internal.signing.ssh;version="7.2.2";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.internal.transport.sshd;version="7.2.2";x-friends:="org.eclipse.jgit.ssh.apache.test";
   uses:="org.apache.sshd.client,
    org.apache.sshd.client.auth,
    org.apache.sshd.client.auth.keyboard,
@@ -24,81 +24,79 @@
    org.apache.sshd.common.signature,
    org.apache.sshd.common.util.buffer,
    org.eclipse.jgit.transport",
- org.eclipse.jgit.internal.transport.sshd.agent;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.auth;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.pkcs11;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.proxy;version="7.1.2";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.signing.ssh;version="7.1.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.transport.sshd;version="7.1.2";
+ org.eclipse.jgit.internal.transport.sshd.agent;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.auth;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.pkcs11;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.internal.transport.sshd.proxy;version="7.2.2";x-friends:="org.eclipse.jgit.ssh.apache.test",
+ org.eclipse.jgit.signing.ssh;version="7.2.2";uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.transport.sshd;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.apache.sshd.client.config.hosts,
    org.apache.sshd.common.keyprovider,
    org.eclipse.jgit.util,
    org.apache.sshd.client.session,
    org.apache.sshd.client.keyverifier",
- org.eclipse.jgit.transport.sshd.agent;version="7.1.2",
- sun.security.x509
-Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)",
- org.apache.sshd.agent;version="[2.14.0,2.15.0)",
- org.apache.sshd.client;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.auth;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.auth.keyboard;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.auth.password;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.auth.pubkey;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.channel;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.config.hosts;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.config.keys;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.future;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.keyverifier;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.session;version="[2.14.0,2.15.0)",
- org.apache.sshd.client.session.forward;version="[2.14.0,2.15.0)",
- org.apache.sshd.common;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.auth;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.channel;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.cipher;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.compression;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys.loader;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.config.keys.u2f;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.digest;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.forward;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.future;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.helpers;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.io;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.kex;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.kex.extension;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.kex.extension.parser;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.keyprovider;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.mac;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.random;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.session;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.session.helpers;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.signature;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.buffer;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.buffer.keys;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.closeable;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.io;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.io.der;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.io.functors;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.io.resource;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.logging;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.net;version="[2.14.0,2.15.0)",
- org.apache.sshd.common.util.security;version="[2.14.0,2.15.0)",
- org.apache.sshd.core;version="[2.14.0,2.15.0)",
- org.apache.sshd.server.auth;version="[2.14.0,2.15.0)",
- org.apache.sshd.sftp;version="[2.14.0,2.15.0)",
- org.apache.sshd.sftp.client;version="[2.14.0,2.15.0)",
- org.apache.sshd.sftp.common;version="[2.14.0,2.15.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.fnmatch;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.transport.sshd.agent;version="7.2.2"
+Import-Package: org.apache.sshd.agent;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.auth;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.auth.keyboard;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.auth.password;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.auth.pubkey;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.channel;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.config.hosts;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.config.keys;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.future;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.keyverifier;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.session;version="[2.15.0,2.16.0)",
+ org.apache.sshd.client.session.forward;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.auth;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.channel;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.cipher;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.compression;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys.loader;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.config.keys.u2f;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.digest;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.forward;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.future;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.helpers;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.io;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.kex;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.kex.extension;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.kex.extension.parser;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.keyprovider;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.mac;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.random;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.session;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.session.helpers;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.signature;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.buffer;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.buffer.keys;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.closeable;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.io;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.io.der;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.io.functors;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.io.resource;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.logging;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.net;version="[2.15.0,2.16.0)",
+ org.apache.sshd.common.util.security;version="[2.15.0,2.16.0)",
+ org.apache.sshd.core;version="[2.15.0,2.16.0)",
+ org.apache.sshd.server.auth;version="[2.15.0,2.16.0)",
+ org.apache.sshd.sftp;version="[2.15.0,2.16.0)",
+ org.apache.sshd.sftp.client;version="[2.15.0,2.16.0)",
+ org.apache.sshd.sftp.common;version="[2.15.0,2.16.0)",
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.fnmatch;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
index 1a63852..a4c95d4 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.apache - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml
index 857f8d6..0ad70b5 100644
--- a/org.eclipse.jgit.ssh.apache/pom.xml
+++ b/org.eclipse.jgit.ssh.apache/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
@@ -30,7 +30,6 @@
   <properties>
     <translate-qualifier/>
     <source-bundle-manifest>${project.build.directory}/META-INF/SOURCE-MANIFEST.MF</source-bundle-manifest>
-    <eddsa-version>0.3.0</eddsa-version>
   </properties>
 
   <dependencies>
@@ -63,12 +62,6 @@
     </dependency>
 
     <dependency>
-      <groupId>net.i2p.crypto</groupId>
-      <artifactId>eddsa</artifactId>
-      <version>${eddsa-version}</version>
-    </dependency>
-
-    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
index 6048239..773c4b9 100644
--- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
+++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties
@@ -61,6 +61,7 @@
 The {0} key actually received has the fingerprints:\n\
 {5}\n\
 {6}
+knownHostsRevokedCertificateMsg=Host ''{0}'' sent a certificate with a CA key that is marked as revoked in the known hosts file {1}.
 knownHostsRevokedKeyMsg=Host ''{0}'' sent a key that is marked as revoked in the known hosts file {1}.
 knownHostsUnknownKeyMsg=The authenticity of host ''{0}'' cannot be established.
 knownHostsUnknownKeyPrompt=Accept and store this key, and continue connecting?
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java
index cfbe7a7..80b171f 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java
@@ -21,6 +21,7 @@
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
@@ -36,8 +37,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -64,8 +63,6 @@ final class AllowedSigners extends ModifiableFileWatcher {
 
 	private static final String VALID_BEFORE = "valid-before="; //$NON-NLS-1$
 
-	private static final String SSH_KEY_PREFIX = "ssh-"; //$NON-NLS-1$
-
 	private static final DateTimeFormatter SSH_DATE_FORMAT = new DateTimeFormatterBuilder()
 			.appendValue(ChronoField.YEAR, 4)
 			.appendValue(ChronoField.MONTH_OF_YEAR, 2)
@@ -323,8 +320,7 @@ static AllowedEntry parseLine(String line)
 				&& Character.isWhitespace(line.charAt(CERT_AUTHORITY.length())))
 				|| matches(line, NAMESPACES, 0)
 				|| matches(line, VALID_AFTER, 0)
-				|| matches(line, VALID_BEFORE, 0)
-				|| matches(line, SSH_KEY_PREFIX, 0)) {
+				|| matches(line, VALID_BEFORE, 0)) {
 			throw new StreamCorruptedException(
 					SshdText.get().signAllowedSignersNoIdentities);
 		}
@@ -449,7 +445,9 @@ static String parsePublicKey(String s, int from)
 					s.substring(start)));
 		}
 		String keyType = s.substring(start, endOfKeyType);
-		if (!keyType.startsWith(SSH_KEY_PREFIX)) {
+		String key = s.substring(startOfKey, i);
+		if (!key.startsWith("AAAA")) { //$NON-NLS-1$
+			// base64 encoded SSH keys always start with four 'A's.
 			throw new StreamCorruptedException(MessageFormat.format(
 					SshdText.get().signAllowedSignersPublicKeyParsing,
 					s.substring(start)));
@@ -482,12 +480,8 @@ static Instant parseDate(String input) {
 		if (isUTC) {
 			return time.atOffset(ZoneOffset.UTC).toInstant();
 		}
-		TimeZone tz = SystemReader.getInstance().getTimeZone();
-		// Since there are a few TimeZone IDs that are not recognized by ZoneId,
-		// use offsets.
-		return time.atOffset(ZoneOffset.ofTotalSeconds(
-				(int) TimeUnit.MILLISECONDS.toSeconds(tz.getRawOffset())))
-				.toInstant();
+		ZoneId tz = SystemReader.getInstance().getTimeZoneId();
+		return time.atZone(tz).toInstant();
 	}
 
 	// OpenSSH uses the backslash *only* to quote the double-quote.
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.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
index 2b4f7e5..acb77c5 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 2025 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
@@ -31,9 +31,11 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
@@ -45,10 +47,13 @@
 import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier.HostEntryPair;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
 import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
 import org.apache.sshd.common.config.keys.PublicKeyEntry;
 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.config.keys.UnsupportedSshPublicKey;
 import org.apache.sshd.common.digest.BuiltinDigests;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.util.io.ModifiableFileWatcher;
@@ -126,6 +131,9 @@ public class OpenSshServerKeyDatabase
 	/** Can be used to mark revoked known host lines. */
 	private static final String MARKER_REVOKED = "revoked"; //$NON-NLS-1$
 
+	/** Marks CA keys used for SSH certificates. */
+	private static final String MARKER_CA = "cert-authority"; //$NON-NLS-1$
+
 	private final boolean askAboutNewFile;
 
 	private final Map<Path, HostKeyFile> knownHostsFiles = new ConcurrentHashMap<>();
@@ -178,7 +186,10 @@ public List<PublicKey> lookup(@NonNull String connectAddress,
 		for (HostKeyFile file : filesToUse) {
 			for (HostEntryPair current : file.get()) {
 				KnownHostEntry entry = current.getHostEntry();
-				if (!isRevoked(entry)) {
+				if (current.getServerKey() instanceof UnsupportedSshPublicKey) {
+					continue;
+				}
+				if (!isRevoked(entry) && !isCertificateAuthority(entry)) {
 					for (SshdSocketAddress host : candidates) {
 						if (entry.isHostMatch(host.getHostName(),
 								host.getPort())) {
@@ -204,6 +215,7 @@ public boolean accept(@NonNull String connectAddress,
 		Collection<SshdSocketAddress> candidates = getCandidates(connectAddress,
 				remoteAddress);
 		for (HostKeyFile file : filesToUse) {
+			HostEntryPair lastModified = modified[0];
 			try {
 				if (find(candidates, serverKey, file.get(), modified)) {
 					return true;
@@ -212,24 +224,35 @@ public boolean accept(@NonNull String connectAddress,
 				ask.revokedKey(remoteAddress, serverKey, file.getPath());
 				return false;
 			}
-			if (path == null && modified[0] != null) {
+			if (modified[0] != lastModified) {
 				// Remember the file in which we might need to update the
 				// entry
 				path = file.getPath();
 			}
 		}
+		if (serverKey instanceof OpenSshCertificate) {
+			return false;
+		}
 		if (modified[0] != null) {
-			// We found an entry, but with a different key
+			// We found an entry, but with a different key.
 			AskUser.ModifiedKeyHandling toDo = ask.acceptModifiedServerKey(
 					remoteAddress, modified[0].getServerKey(),
 					serverKey, path);
 			if (toDo == AskUser.ModifiedKeyHandling.ALLOW_AND_STORE) {
-				try {
-					updateModifiedServerKey(serverKey, modified[0], path);
-					knownHostsFiles.get(path).resetReloadAttributes();
-				} catch (IOException e) {
-					LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
-							path));
+				if (modified[0]
+						.getServerKey() instanceof UnsupportedSshPublicKey) {
+					// Never update a line containing an unknown key type,
+					// always add.
+					addKeyToFile(filesToUse.get(0), candidates, serverKey, ask,
+							config);
+				} else {
+					try {
+						updateModifiedServerKey(serverKey, modified[0], path);
+						knownHostsFiles.get(path).resetReloadAttributes();
+					} catch (IOException e) {
+						LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
+								path));
+					}
 				}
 			}
 			if (toDo == AskUser.ModifiedKeyHandling.DENY) {
@@ -242,19 +265,8 @@ public boolean accept(@NonNull String connectAddress,
 			return true;
 		} else if (ask.acceptUnknownKey(remoteAddress, serverKey)) {
 			if (!filesToUse.isEmpty()) {
-				HostKeyFile toUpdate = filesToUse.get(0);
-				path = toUpdate.getPath();
-				try {
-					if (Files.exists(path) || !askAboutNewFile
-							|| ask.createNewFile(path)) {
-						updateKnownHostsFile(candidates, serverKey, path,
-								config);
-						toUpdate.resetReloadAttributes();
-					}
-				} catch (Exception e) {
-					LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate,
-							path), e);
-				}
+				addKeyToFile(filesToUse.get(0), candidates, serverKey, ask,
+						config);
 			}
 			return true;
 		}
@@ -265,39 +277,90 @@ private static class RevokedKeyException extends Exception {
 		private static final long serialVersionUID = 1L;
 	}
 
-	private boolean isRevoked(KnownHostEntry entry) {
+	private static boolean isRevoked(KnownHostEntry entry) {
 		return MARKER_REVOKED.equals(entry.getMarker());
 	}
 
+	private static boolean isCertificateAuthority(KnownHostEntry entry) {
+		return MARKER_CA.equals(entry.getMarker());
+	}
+
 	private boolean find(Collection<SshdSocketAddress> candidates,
 			PublicKey serverKey, List<HostEntryPair> entries,
 			HostEntryPair[] modified) throws RevokedKeyException {
+		PublicKey keyToCheck = serverKey;
+		boolean isCert = false;
+		String keyType = KeyUtils.getKeyType(keyToCheck);
+		String modifiedKeyType = null;
+		if (modified[0] != null) {
+			modifiedKeyType = modified[0].getHostEntry().getKeyEntry()
+					.getKeyType();
+		}
+		if (serverKey instanceof OpenSshCertificate) {
+			keyToCheck = ((OpenSshCertificate) serverKey).getCaPubKey();
+			isCert = true;
+		}
 		for (HostEntryPair current : entries) {
 			KnownHostEntry entry = current.getHostEntry();
-			for (SshdSocketAddress host : candidates) {
-				if (entry.isHostMatch(host.getHostName(), host.getPort())) {
-					boolean revoked = isRevoked(entry);
-					if (KeyUtils.compareKeys(serverKey,
-							current.getServerKey())) {
-						// Exact match
-						if (revoked) {
-							throw new RevokedKeyException();
-						}
+			if (candidates.stream().anyMatch(host -> entry
+					.isHostMatch(host.getHostName(), host.getPort()))) {
+				boolean revoked = isRevoked(entry);
+				boolean haveCert = isCertificateAuthority(entry);
+				if (KeyUtils.compareKeys(keyToCheck, current.getServerKey())) {
+					// Exact match
+					if (revoked) {
+						throw new RevokedKeyException();
+					}
+					if (haveCert == isCert) {
 						modified[0] = null;
 						return true;
-					} else if (!revoked) {
-						// Server sent a different key
-						modified[0] = current;
-						// Keep going -- maybe there's another entry for this
-						// host
 					}
-					break;
+				}
+				if (haveCert == isCert && !haveCert && !revoked) {
+					// Server sent a different key.
+					if (modifiedKeyType == null) {
+						modified[0] = current;
+						modifiedKeyType = entry.getKeyEntry().getKeyType();
+					} else if (!keyType.equals(modifiedKeyType)) {
+						String thisKeyType = entry.getKeyEntry().getKeyType();
+						if (isBetterMatch(keyType, thisKeyType,
+								modifiedKeyType)) {
+							// Since we may replace the modified[0] key,
+							// prefer to report a key of the same key type
+							// as having been modified.
+							modified[0] = current;
+							modifiedKeyType = keyType;
+						}
+					}
+					// Keep going -- maybe there's another entry for this
+					// host
 				}
 			}
 		}
 		return false;
 	}
 
+	private static boolean isBetterMatch(String keyType, String thisType,
+			String modifiedType) {
+		if (keyType.equals(thisType)) {
+			return true;
+		}
+		// EC keys are a bit special because they encode the curve in the key
+		// type. If we have no exactly matching EC key type in known_hosts, we
+		// still prefer to update an existing EC key type over some other key
+		// type.
+		if (!keyType.startsWith("ecdsa") || !thisType.startsWith("ecdsa")) { //$NON-NLS-1$ //$NON-NLS-2$
+			return false;
+		}
+		if (!modifiedType.startsWith("ecdsa")) { //$NON-NLS-1$
+			return true;
+		}
+		// All three are EC keys. thisType doesn't match the size of keyType
+		// (otherwise the two would have compared equal above already), so it is
+		// not better than modifiedType.
+		return false;
+	}
+
 	private List<HostKeyFile> addUserHostKeyFiles(List<String> fileNames) {
 		if (fileNames == null || fileNames.isEmpty()) {
 			return Collections.emptyList();
@@ -317,6 +380,21 @@ private List<HostKeyFile> addUserHostKeyFiles(List<String> fileNames) {
 		return userFiles;
 	}
 
+	private void addKeyToFile(HostKeyFile file,
+			Collection<SshdSocketAddress> candidates, PublicKey serverKey,
+			AskUser ask, Configuration config) {
+		Path path = file.getPath();
+		try {
+			if (Files.exists(path) || !askAboutNewFile
+					|| ask.createNewFile(path)) {
+				updateKnownHostsFile(candidates, serverKey, path, config);
+				file.resetReloadAttributes();
+			}
+		} catch (Exception e) {
+			LOG.warn(format(SshdText.get().knownHostsCouldNotUpdate, path), e);
+		}
+	}
+
 	private void updateKnownHostsFile(Collection<SshdSocketAddress> candidates,
 			PublicKey serverKey, Path path, Configuration config)
 			throws Exception {
@@ -453,15 +531,22 @@ public void revokedKey(SocketAddress remoteAddress, PublicKey serverKey,
 				return;
 			}
 			InetSocketAddress remote = (InetSocketAddress) remoteAddress;
+			boolean isCert = serverKey instanceof OpenSshCertificate;
+			PublicKey keyToReport = isCert
+					? ((OpenSshCertificate) serverKey).getCaPubKey()
+					: serverKey;
 			URIish uri = JGitUserInteraction.toURI(config.getUsername(),
 					remote);
 			String sha256 = KeyUtils.getFingerPrint(BuiltinDigests.sha256,
-					serverKey);
-			String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5, serverKey);
-			String keyAlgorithm = serverKey.getAlgorithm();
+					keyToReport);
+			String md5 = KeyUtils.getFingerPrint(BuiltinDigests.md5,
+					keyToReport);
+			String keyAlgorithm = keyToReport.getAlgorithm();
+			String msg = isCert
+					? SshdText.get().knownHostsRevokedCertificateMsg
+					: SshdText.get().knownHostsRevokedKeyMsg;
 			askUser(provider, uri, null, //
-					format(SshdText.get().knownHostsRevokedKeyMsg,
-							remote.getHostString(), path),
+					format(msg, remote.getHostString(), path),
 					format(SshdText.get().knownHostsKeyFingerprints,
 							keyAlgorithm),
 					md5, sha256);
@@ -594,7 +679,7 @@ private List<HostEntryPair> reload(Path path) throws IOException {
 					}
 					try {
 						PublicKey serverKey = keyPart.resolvePublicKey(null,
-								PublicKeyEntryResolver.IGNORING);
+								PublicKeyEntryResolver.UNSUPPORTED);
 						if (serverKey == null) {
 							LOG.warn(format(
 									SshdText.get().knownHostsUnknownKeyType,
@@ -625,7 +710,7 @@ private int parsePort(String s) {
 
 	private SshdSocketAddress toSshdSocketAddress(@NonNull String address) {
 		String host = null;
-		int port = 0;
+		int port = SshConstants.DEFAULT_PORT;
 		if (HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM == address
 				.charAt(0)) {
 			int end = address.indexOf(
@@ -665,12 +750,23 @@ private Collection<SshdSocketAddress> getCandidates(
 		if (address != null) {
 			candidates.add(address);
 		}
-		return candidates;
+		List<SshdSocketAddress> result = new ArrayList<>();
+		result.addAll(candidates);
+		if (!remoteAddress.isUnresolved()) {
+			SshdSocketAddress ip = new SshdSocketAddress(
+					remoteAddress.getAddress().getHostAddress(),
+					remoteAddress.getPort());
+			if (candidates.add(ip)) {
+				result.add(ip);
+			}
+		}
+		return result;
 	}
 
 	private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
 			PublicKey key, Configuration config) throws Exception {
 		StringBuilder result = new StringBuilder();
+		Set<String> knownNames = new HashSet<>();
 		if (config.getHashKnownHosts()) {
 			// SHA1 is the only algorithm for host name hashing known to OpenSSH
 			// or to Apache MINA sshd.
@@ -680,10 +776,10 @@ private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
 				prng = new SecureRandom();
 			}
 			byte[] salt = new byte[mac.getDefaultBlockSize()];
-			for (SshdSocketAddress address : patterns) {
-				if (result.length() > 0) {
-					result.append(',');
-				}
+			// For hashed hostnames, only one hashed pattern is allowed per
+			// https://man.openbsd.org/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT
+			if (!patterns.isEmpty()) {
+				SshdSocketAddress address = patterns.iterator().next();
 				prng.nextBytes(salt);
 				KnownHostHashValue.append(result, digester, salt,
 						KnownHostHashValue.calculateHashValue(
@@ -692,6 +788,10 @@ private String createHostKeyLine(Collection<SshdSocketAddress> patterns,
 			}
 		} else {
 			for (SshdSocketAddress address : patterns) {
+				String tgt = address.getHostName() + ':' + address.getPort();
+				if (!knownNames.add(tgt)) {
+					continue;
+				}
 				if (result.length() > 0) {
 					result.append(',');
 				}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
index 0533b65..e401378 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java
@@ -83,6 +83,7 @@ public static SshdText get() {
 	/***/ public String knownHostsModifiedKeyDenyMsg;
 	/***/ public String knownHostsModifiedKeyStorePrompt;
 	/***/ public String knownHostsModifiedKeyWarning;
+	/***/ public String knownHostsRevokedCertificateMsg;
 	/***/ public String knownHostsRevokedKeyMsg;
 	/***/ public String knownHostsUnknownKeyMsg;
 	/***/ public String knownHostsUnknownKeyPrompt;
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
index 8866976..3e1fab3 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java
@@ -17,8 +17,6 @@
 import java.net.PasswordAuthentication;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.util.Arrays;
 import java.util.concurrent.CancellationException;
 
@@ -113,13 +111,12 @@ public void process() throws Exception {
 	 */
 	protected void askCredentials() {
 		clearPassword();
-		PasswordAuthentication auth = AccessController.doPrivileged(
-				(PrivilegedAction<PasswordAuthentication>) () -> Authenticator
-						.requestPasswordAuthentication(proxy.getHostString(),
-								proxy.getAddress(), proxy.getPort(),
-								SshConstants.SSH_SCHEME,
-								SshdText.get().proxyPasswordPrompt, "Basic", //$NON-NLS-1$
-								null, RequestorType.PROXY));
+		PasswordAuthentication auth = Authenticator
+				.requestPasswordAuthentication(proxy.getHostString(),
+						proxy.getAddress(), proxy.getPort(),
+						SshConstants.SSH_SCHEME,
+						SshdText.get().proxyPasswordPrompt, "Basic", //$NON-NLS-1$
+						null, RequestorType.PROXY);
 		if (auth == null) {
 			user = ""; //$NON-NLS-1$
 			throw new CancellationException(
diff --git a/org.eclipse.jgit.ssh.apache/src/sun/security/x509/README.md b/org.eclipse.jgit.ssh.apache/src/sun/security/x509/README.md
deleted file mode 100644
index a84ee37..0000000
--- a/org.eclipse.jgit.ssh.apache/src/sun/security/x509/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This dummy package is used to fix the error
-"Missing requirement: net.i2p.crypto.eddsa 0.3.0 requires 'java.package; sun.security.x509 0.0.0'"
-raised since eddsa falsely requires this import
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.jsch.test/BUILD b/org.eclipse.jgit.ssh.jsch.test/BUILD
index 4a8b925..d4e6875 100644
--- a/org.eclipse.jgit.ssh.jsch.test/BUILD
+++ b/org.eclipse.jgit.ssh.jsch.test/BUILD
@@ -8,7 +8,9 @@
     srcs = glob(["tst/**/*.java"]),
     tags = ["jsch"],
     deps = [
-        "//lib:eddsa",
+        "//lib:bcpkix",
+        "//lib:bcprov",
+        "//lib:bcutil",
         "//lib:jsch",
         "//lib:junit",
         "//org.eclipse.jgit:jgit",
diff --git a/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
index 3f8233b..502f4f9 100644
--- a/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch.test/META-INF/MANIFEST.MF
@@ -3,20 +3,20 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.jsch.test
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Require-Bundle: org.hamcrest.core;bundle-version="[1.3.0,2.0.0)"
 Import-Package: com.jcraft.jsch;version="[0.1.54,0.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.ssh.jsch;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
  org.junit.runner;version="[4.13,5.0.0)"
diff --git a/org.eclipse.jgit.ssh.jsch.test/pom.xml b/org.eclipse.jgit.ssh.jsch.test/pom.xml
index f67b1bd..68fee6b 100644
--- a/org.eclipse.jgit.ssh.jsch.test/pom.xml
+++ b/org.eclipse.jgit.ssh.jsch.test/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.jsch.test</artifactId>
diff --git a/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
index 3a6e08c..2cae67a 100644
--- a/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch/META-INF/MANIFEST.MF
@@ -3,19 +3,19 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ssh.jsch
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch;singleton:=true
-Fragment-Host: org.eclipse.jgit;bundle-version="[7.1.2,7.2.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[7.2.2,7.3.0)"
 Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/jsch
 Bundle-ActivationPolicy: lazy
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="7.1.2"
+Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="7.2.2"
 Import-Package: com.jcraft.jsch;version="[0.1.37,0.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
diff --git a/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
index f4c12e6..964fb5d 100644
--- a/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.jsch/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ssh.jsch - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ssh.jsch.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.jsch/pom.xml b/org.eclipse.jgit.ssh.jsch/pom.xml
index 4427602..5a41c06 100644
--- a/org.eclipse.jgit.ssh.jsch/pom.xml
+++ b/org.eclipse.jgit.ssh.jsch/pom.xml
@@ -17,7 +17,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD
index 29f5b36..7755df0 100644
--- a/org.eclipse.jgit.test/BUILD
+++ b/org.eclipse.jgit.test/BUILD
@@ -53,6 +53,11 @@
     exclude = HELPERS + DATA + EXCLUDED,
 ))
 
+
+tests(tests = glob(["exttst/**/*.java"]),
+    srcprefix = "exttst/",
+    extra_tags = ["ext"])
+
 # Non abstract base classes used for tests by other test classes
 BASE = [
     PKG + "internal/storage/file/FileRepositoryBuilderTest.java",
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 77a7f39..58d072f 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.test
 Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -20,65 +20,68 @@
  org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
  org.apache.commons.io;version="[2.15.0,3.0.0)",
  org.apache.commons.io.output;version="[2.15.0,3.0.0)",
+ org.apache.commons.lang3;version="[3.17.0,4.0.0)",
  org.assertj.core.api;version="[3.14.0,4.0.0)",
- org.eclipse.jgit.annotations;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.api.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.archive;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.attributes;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.awtui;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.blame;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.diff;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.dircache;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.events;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.fnmatch;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.gitrepo;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.hooks;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.ignore;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.ignore.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.diff;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.fsck;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.commitgraph;version="7.1.2",
- org.eclipse.jgit.internal.storage.dfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.io;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.memory;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.connectivity;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.parser;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.junit.time;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lfs;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.logging;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.merge;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.notes;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.patch;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.pgm;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.pgm.internal;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revplot;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.storage.pack;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.submodule;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.http;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport.resolver;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.io;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util.sha1;version="[7.1.2,7.2.0)",
+ org.eclipse.jgit.annotations;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.api.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.archive;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.attributes;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.awtui;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.blame;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.blame.cache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.diff;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.dircache;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.events;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.fnmatch;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.gitrepo;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.hooks;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.ignore;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.ignore.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.diff;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.fsck;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.commitgraph;version="7.2.2",
+ org.eclipse.jgit.internal.storage.dfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.io;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.memory;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.midx;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.connectivity;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.junit.time;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lfs;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.logging;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.merge;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.notes;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.patch;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.pgm;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.pgm.internal;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revplot;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.file;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.storage.pack;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.submodule;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.http;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport.resolver;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.treewalk.filter;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.io;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util.sha1;version="[7.2.2,7.3.0)",
  org.junit;version="[4.13,5.0.0)",
  org.junit.experimental.theories;version="[4.13,5.0.0)",
  org.junit.function;version="[4.13.0,5.0.0)",
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
new file mode 100644
index 0000000..88f0806
--- /dev/null
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.Pack;
+import org.eclipse.jgit.internal.storage.file.PackFile;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.util.NB;
+import org.junit.Test;
+
+public class CgitMidxCompatibilityTest extends SampleDataRepositoryTestCase {
+
+	@Test
+	public void jgitMidx_verifyByCgit()
+			throws IOException, InterruptedException {
+		byte[] jgitMidxBytes = generateJGitMidx();
+		writeMidx(jgitMidxBytes);
+		assertEquals("cgit exit code", 0, run_cgit_multipackindex_verify());
+	}
+
+	@Test
+	public void compareBasicChunkSizes()
+			throws IOException, InterruptedException {
+		// We cannot compare byte-by-byte because there are optional chunks and
+		// it is not guaranteed what cgit and jgit will generate
+		byte[] jgitMidxBytes = generateJGitMidx();
+		assertEquals("cgit exit code", 0, run_cgit_multipackindex_write());
+		byte[] cgitMidxBytes = readCgitMidx();
+
+		RawMultiPackIndex jgitMidx = new RawMultiPackIndex(jgitMidxBytes);
+		RawMultiPackIndex cgitMidx = new RawMultiPackIndex(cgitMidxBytes);
+
+		// This is a fixed sized chunk
+		assertEquals(256 * 4, cgitMidx.getChunkSize(MIDX_CHUNKID_OIDFANOUT));
+		assertArrayEquals(cgitMidx.getRawChunk(MIDX_CHUNKID_OIDFANOUT),
+				jgitMidx.getRawChunk(MIDX_CHUNKID_OIDFANOUT));
+
+		assertArrayEquals(cgitMidx.getRawChunk(MIDX_CHUNKID_OIDLOOKUP),
+				jgitMidx.getRawChunk(MIDX_CHUNKID_OIDLOOKUP));
+
+		// The spec has changed from padding packnames to a multile of four, to
+		// move the packname chunk to the end of the file.
+		// git 2.48 pads the packs names to a multiple of 4
+		// jgit puts the chunk at the end
+		byte[] cgitPacknames = trimPadding(
+				cgitMidx.getRawChunk(MIDX_CHUNKID_PACKNAMES));
+		assertArrayEquals(cgitPacknames,
+				jgitMidx.getRawChunk(MIDX_CHUNKID_PACKNAMES));
+
+		assertArrayEquals(cgitMidx.getRawChunk(MIDX_CHUNKID_OBJECTOFFSETS),
+				jgitMidx.getRawChunk(MIDX_CHUNKID_OBJECTOFFSETS));
+
+	}
+
+	private byte[] generateJGitMidx() throws IOException {
+		Map<String, PackIndex> indexes = new HashMap<>();
+		for (Pack pack : db.getObjectDatabase().getPacks()) {
+			PackFile packFile = pack.getPackFile().create(PackExt.INDEX);
+			indexes.put(packFile.getName(), pack.getIndex());
+		}
+
+		MultiPackIndexWriter writer = new MultiPackIndexWriter();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		writer.write(NullProgressMonitor.INSTANCE, out, indexes);
+		return out.toByteArray();
+	}
+
+	private int run_cgit_multipackindex_write()
+			throws IOException, InterruptedException {
+		String[] command = new String[] { "git", "multi-pack-index", "write" };
+		Process proc = Runtime.getRuntime().exec(command, new String[0],
+				db.getDirectory());
+		return proc.waitFor();
+	}
+
+	private int run_cgit_multipackindex_verify()
+			throws IOException, InterruptedException {
+		String[] command = new String[] { "git", "multi-pack-index", "verify" };
+		Process proc = Runtime.getRuntime().exec(command, new String[0],
+				db.getDirectory());
+		return proc.waitFor();
+	}
+
+	private byte[] readCgitMidx() throws IOException {
+		File midx = getMIdxStandardLocation();
+		assertTrue("cgit multi-pack-index exists", midx.exists());
+		return Files.readAllBytes(midx.toPath());
+	}
+
+	private void writeMidx(byte[] midx) throws IOException {
+		File midxFile = getMIdxStandardLocation();
+		Files.write(midxFile.toPath(), midx);
+	}
+
+	private File getMIdxStandardLocation() {
+		return new File(db.getObjectDatabase().getPackDirectory(),
+				"multi-pack-index");
+	}
+
+	private byte[] trimPadding(byte[] data) {
+		// Chunk MUST have one \0, we want to remove any extra \0
+		int newEnd = data.length - 1;
+		while (newEnd - 1 >= 0 && data[newEnd - 1] == 0) {
+			newEnd--;
+		}
+
+		if (newEnd == data.length - 1) {
+			return data;
+		}
+		return Arrays.copyOfRange(data, 0, newEnd + 1);
+	}
+
+	private static class RawMultiPackIndex {
+		private final List<ChunkSegment> chunks;
+
+		private final byte[] midx;
+
+		private RawMultiPackIndex(byte[] midx) {
+			this.chunks = readChunks(midx);
+			this.midx = midx;
+		}
+
+		long getChunkSize(int chunkId) {
+			int chunkPos = findChunkPosition(chunks, chunkId);
+			return chunks.get(chunkPos + 1).offset
+					- chunks.get(chunkPos).offset;
+		}
+
+		long getOffset(int chunkId) {
+			return chunks.get(findChunkPosition(chunks, chunkId)).offset;
+		}
+
+		private long getNextOffset(int chunkId) {
+			return chunks.get(findChunkPosition(chunks, chunkId) + 1).offset;
+		}
+
+		byte[] getRawChunk(int chunkId) {
+			int start = (int) getOffset(chunkId);
+			int end = (int) getNextOffset(chunkId);
+			return Arrays.copyOfRange(midx, start, end);
+		}
+
+		private static int findChunkPosition(List<ChunkSegment> chunks,
+				int id) {
+			int chunkPos = -1;
+			for (int i = 0; i < chunks.size(); i++) {
+				if (chunks.get(i).id() == id) {
+					chunkPos = i;
+					break;
+				}
+			}
+			if (chunkPos == -1) {
+				throw new IllegalStateException("Chunk doesn't exist");
+			}
+			return chunkPos;
+		}
+
+		private List<ChunkSegment> readChunks(byte[] midx) {
+			// Read the number of "chunkOffsets" (1 byte)
+			int chunkCount = midx[6];
+			byte[] lookupBuffer = new byte[CHUNK_LOOKUP_WIDTH
+					* (chunkCount + 1)];
+			System.arraycopy(midx, 12, lookupBuffer, 0, lookupBuffer.length);
+
+			List<ChunkSegment> chunks = new ArrayList<>(chunkCount + 1);
+			for (int i = 0; i <= chunkCount; i++) {
+				// chunks[chunkCount] is just a marker, in order to record the
+				// length of the last chunk.
+				int id = NB.decodeInt32(lookupBuffer, i * 12);
+				long offset = NB.decodeInt64(lookupBuffer, i * 12 + 4);
+				chunks.add(new ChunkSegment(id, offset));
+			}
+			return chunks;
+		}
+	}
+
+	private record ChunkSegment(int id, long offset) {
+	}
+}
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 479050b..e514fd1 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.test</artifactId>
diff --git a/org.eclipse.jgit.test/tests.bzl b/org.eclipse.jgit.test/tests.bzl
index 170bf0c..41f76d0 100644
--- a/org.eclipse.jgit.test/tests.bzl
+++ b/org.eclipse.jgit.test/tests.bzl
@@ -1,11 +1,29 @@
+'''
+Expose each test as a bazel target
+'''
 load(
     "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
     "junit_tests",
 )
 
-def tests(tests):
+def tests(tests, srcprefix="tst/", extra_tags=[]):
+    '''
+    Create a target each of the tests
+
+    Each target is the full push (removing srcprefix) replacing directory
+    separators with underscores.
+
+    e.g. a test under tst/a/b/c/A.test will become the target
+    //org.eclipse.jgit.tests:a_b_c_A
+
+    Args:
+      tests: a glob of tests files
+      srcprefix: prefix between org.eclipse.jgit.tests and the package
+        start
+      extra_tags: additional tags to add to the generated targets
+    '''
     for src in tests:
-        name = src[len("tst/"):len(src) - len(".java")].replace("/", "_")
+        name = src[len(srcprefix):len(src) - len(".java")].replace("/", "_")
         labels = []
         timeout = "moderate"
         if name.startswith("org_eclipse_jgit_"):
@@ -20,6 +38,8 @@
         if "lib" not in labels:
             labels.append("lib")
 
+        labels.extend(extra_tags)
+
         # TODO(http://eclip.se/534285): Make this test pass reliably
         # and remove the flaky attribute.
         flaky = src.endswith("CrissCrossMergeTest.java")
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
index 1c2e995..2266772 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>
- * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2025 Christian Halstrick <christian.halstrick@sap.com> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -665,11 +665,13 @@ public void testAddRemovedFile() throws Exception {
 			FileUtils.delete(file);
 
 			// is supposed to do nothing
-			dc = git.add().addFilepattern("a.txt").call();
+			dc = git.add().addFilepattern("a.txt").setAll(false).call();
 			assertEquals(oid, dc.getEntry(0).getObjectId());
 			assertEquals(
 					"[a.txt, mode:100644, content:content]",
 					indexState(CONTENT));
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("", indexState(CONTENT));
 		}
 	}
 
@@ -690,11 +692,13 @@ public void testAddRemovedCommittedFile() throws Exception {
 			FileUtils.delete(file);
 
 			// is supposed to do nothing
-			dc = git.add().addFilepattern("a.txt").call();
+			dc = git.add().addFilepattern("a.txt").setAll(false).call();
 			assertEquals(oid, dc.getEntry(0).getObjectId());
 			assertEquals(
 					"[a.txt, mode:100644, content:content]",
 					indexState(CONTENT));
+			git.add().addFilepattern("a.txt").call();
+			assertEquals("", indexState(CONTENT));
 		}
 	}
 
@@ -964,7 +968,7 @@ public void testAddWithoutParameterUpdate() throws Exception {
 			// file sub/b.txt is deleted
 			FileUtils.delete(file2);
 
-			git.add().addFilepattern("sub").call();
+			git.add().addFilepattern("sub").setAll(false).call();
 			// change in sub/a.txt is staged
 			// deletion of sub/b.txt is not staged
 			// sub/c.txt is staged
@@ -973,6 +977,12 @@ public void testAddWithoutParameterUpdate() throws Exception {
 					"[sub/b.txt, mode:100644, content:content b]" +
 					"[sub/c.txt, mode:100644, content:content c]",
 					indexState(CONTENT));
+			git.add().addFilepattern("sub").call();
+			// deletion of sub/b.txt is staged
+			assertEquals(
+					"[sub/a.txt, mode:100644, content:modified content]"
+							+ "[sub/c.txt, mode:100644, content:content c]",
+					indexState(CONTENT));
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
index 3a4ea8e..9c2b16a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ArchiveCommandTest.java
@@ -267,7 +267,10 @@ private void archiveHeadAllFilesWithCompression(String fmt) throws Exception {
 			archive(git, archive, fmt, Map.of("compression-level", 9));
 			int sizeCompression9 = getNumBytes(archive);
 
-			assertTrue(sizeCompression1 > sizeCompression9);
+			assertTrue(
+					"Expected sizeCompression1 = " + sizeCompression1
+							+ " > sizeCompression9 = " + sizeCompression9,
+					sizeCompression1 > sizeCompression9);
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index be3b33a..3f5c5da 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -34,6 +34,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.merge.ContentMergeStrategy;
@@ -529,10 +530,11 @@ private void doCherryPickAndCheckResult(final Git git,
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
 		if (reason == null) {
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("cherry-pick: "));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("cherry-pick: "));
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
index 63ab809..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
@@ -182,7 +182,8 @@ private static boolean isLocalHead(Ref ref) {
 
 	private static boolean hasRefLog(Repository repo, Ref ref) {
 		try {
-			return repo.getReflogReader(ref.getName()).getLastEntry() != null;
+			return repo.getRefDatabase().getReflogReader(ref)
+					.getLastEntry() != null;
 		} catch (IOException ioe) {
 			throw new IllegalStateException(ioe);
 		}
@@ -647,7 +648,8 @@ public void testCloneRepositoryWithSubmodules() throws Exception {
 					new File(git.getRepository().getWorkTree(), walk.getPath()),
 					subRepo.getWorkTree());
 			assertEquals(new File(new File(git.getRepository().getDirectory(),
-					"modules"), walk.getPath()), subRepo.getDirectory());
+					"modules"), walk.getPath()).getCanonicalPath(),
+					subRepo.getDirectory().getCanonicalPath());
 		}
 
 		File directory = createTempDirectory("testCloneRepositoryWithSubmodules");
@@ -681,8 +683,8 @@ public void testCloneRepositoryWithSubmodules() throws Exception {
 					walk.getPath()), clonedSub1.getWorkTree());
 			assertEquals(
 					new File(new File(git2.getRepository().getDirectory(),
-							"modules"), walk.getPath()),
-					clonedSub1.getDirectory());
+							"modules"), walk.getPath()).getCanonicalPath(),
+					clonedSub1.getDirectory().getCanonicalPath());
 		}
 	}
 
@@ -770,8 +772,8 @@ public void testCloneRepositoryWithNestedSubmodules() throws Exception {
 						walk.getPath()), clonedSub1.getWorkTree());
 				assertEquals(
 						new File(new File(git2.getRepository().getDirectory(),
-								"modules"), walk.getPath()),
-						clonedSub1.getDirectory());
+								"modules"), walk.getPath()).getCanonicalPath(),
+						clonedSub1.getDirectory().getCanonicalPath());
 				status = new SubmoduleStatusCommand(clonedSub1);
 				statuses = status.call();
 			}
@@ -795,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();
@@ -811,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));
@@ -828,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/CommitAndLogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
index 57e5d49..4e5f44e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java
@@ -26,6 +26,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -69,10 +70,11 @@ public void testSomeCommits() throws Exception {
 				l--;
 			}
 			assertEquals(l, -1);
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(
 					reader.getLastEntry().getComment().startsWith("commit:"));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(
 					reader.getLastEntry().getComment().startsWith("commit:"));
 		}
@@ -248,10 +250,11 @@ public void testCommitAmend() throws Exception {
 				c++;
 			}
 			assertEquals(1, c);
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("commit (amend):"));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("commit (amend):"));
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
index e74e234..21cfcc4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java
@@ -19,9 +19,9 @@
 import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.List;
-import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
@@ -435,10 +435,12 @@ public void commitAfterSquashMerge() throws Exception {
 
 			assertEquals(1, squashedCommit.getParentCount());
 			assertNull(db.readSquashCommitMsg());
-			assertEquals("commit: Squashed commit of the following:", db
-					.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-			assertEquals("commit: Squashed commit of the following:", db
-					.getReflogReader(db.getBranch()).getLastEntry().getComment());
+			assertEquals("commit: Squashed commit of the following:",
+					db.getRefDatabase().getReflogReader(Constants.HEAD)
+							.getLastEntry().getComment());
+			assertEquals("commit: Squashed commit of the following:",
+					db.getRefDatabase().getReflogReader(db.getFullBranch())
+							.getLastEntry().getComment());
 		}
 	}
 
@@ -455,12 +457,15 @@ public void testReflogs() throws Exception {
 			git.commit().setMessage("c3").setAll(true)
 					.setReflogComment("testRl").call();
 
-			db.getReflogReader(Constants.HEAD).getReverseEntries();
+			db.getRefDatabase().getReflogReader(Constants.HEAD)
+					.getReverseEntries();
 
 			assertEquals("testRl;commit (initial): c1;", reflogComments(
-					db.getReflogReader(Constants.HEAD).getReverseEntries()));
+					db.getRefDatabase().getReflogReader(Constants.HEAD)
+							.getReverseEntries()));
 			assertEquals("testRl;commit (initial): c1;", reflogComments(
-					db.getReflogReader(db.getBranch()).getReverseEntries()));
+					db.getRefDatabase().getReflogReader(db.getFullBranch())
+							.getReverseEntries()));
 		}
 	}
 
@@ -486,11 +491,11 @@ public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime()
 			writeTrashFile("file1", "file1");
 			git.add().addFilepattern("file1").call();
 
-			final String authorName = "First Author";
-			final String authorEmail = "author@example.org";
-			final Date authorDate = new Date(1349621117000L);
+			String authorName = "First Author";
+			String authorEmail = "author@example.org";
+			Instant authorDate = Instant.ofEpochSecond(1349621117L);
 			PersonIdent firstAuthor = new PersonIdent(authorName, authorEmail,
-					authorDate, TimeZone.getTimeZone("UTC"));
+					authorDate, ZoneOffset.UTC);
 			git.commit().setMessage("initial commit").setAuthor(firstAuthor).call();
 
 			RevCommit amended = git.commit().setAmend(true)
@@ -499,7 +504,8 @@ public void commitAmendWithoutAuthorShouldSetOriginalAuthorAndAuthorTime()
 			PersonIdent amendedAuthor = amended.getAuthorIdent();
 			assertEquals(authorName, amendedAuthor.getName());
 			assertEquals(authorEmail, amendedAuthor.getEmailAddress());
-			assertEquals(authorDate.getTime(), amendedAuthor.getWhen().getTime());
+			assertEquals(authorDate.getEpochSecond(),
+					amendedAuthor.getWhenAsInstant().getEpochSecond());
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index ab87fa9..060e6d3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -12,6 +12,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -87,6 +88,9 @@ public void testDescribe() throws Exception {
 			assertEquals("alice-t1", describe(c2, "alice*"));
 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
 
+			assertNotEquals("alice-t1", describeExcluding(c2, "alice*"));
+			assertNotEquals("alice-t1", describeCommand(c2).setMatch("*").setExclude("alice*").call());
+
 			assertEquals("bob-t2", describe(c3));
 			assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
@@ -95,6 +99,15 @@ public void testDescribe() throws Exception {
 			assertEquals("bob-t2", describe(c3, "?ob*"));
 			assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
 
+			assertNotEquals("alice-t1-1-g44579eb", describeExcluding(c3, "alice*"));
+			assertNotEquals("alice-t1-1-g44579eb", describeCommand(c3).setMatch("*").setExclude("alice*").call());
+			assertNotEquals("alice-t1-1-g44579eb", describeExcluding(c3, "a??c?-t*"));
+			assertNotEquals("alice-t1-1-g44579eb", describeCommand(c3).setMatch("bob*").setExclude("a??c?-t*").call());
+			assertNotEquals("bob-t2", describeExcluding(c3, "bob*"));
+			assertNotEquals("bob-t2", describeCommand(c3).setMatch("alice*").setExclude("bob*"));
+			assertNotEquals("bob-t2", describeExcluding(c3, "?ob*"));
+			assertNotEquals("bob-t2", describeCommand(c3).setMatch("a??c?-t*").setExclude("?ob*"));
+
 			// the value verified with git-describe(1)
 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
 			assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
@@ -518,6 +531,15 @@ private String describe(ObjectId c1, String... patterns) throws Exception {
 				.setMatch(patterns).call();
 	}
 
+	private String describeExcluding(ObjectId c1, String... patterns) throws Exception {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setExclude(patterns).call();
+	}
+
+	private DescribeCommand describeCommand(ObjectId c1) throws Exception {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags);
+	}
+
 	private static void assertNameStartsWith(ObjectId c4, String prefix) {
 		assertTrue(c4.name(), c4.name().startsWith(prefix));
 	}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
index 3ec454c..3731347 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java
@@ -92,8 +92,8 @@ public void testFetchHasRefLogForRemoteRef() throws Exception {
 
 		assertTrue(remoteRef.getName().startsWith(Constants.R_REMOTES));
 		assertEquals(defaultBranchSha1, remoteRef.getObjectId());
-		assertNotNull(git.getRepository().getReflogReader(remoteRef.getName())
-				.getLastEntry());
+		assertNotNull(git.getRepository().getRefDatabase()
+				.getReflogReader(remoteRef.getName()).getLastEntry());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java
index f98db34..6090d5e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java
@@ -11,12 +11,11 @@
 
 import static org.junit.Assert.assertTrue;
 
-import java.util.Date;
+import java.time.Instant;
 import java.util.Properties;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.util.GitDateParser;
-import org.eclipse.jgit.util.SystemReader;
+import org.eclipse.jgit.util.GitTimeParser;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -36,9 +35,8 @@ public void setUp() throws Exception {
 
 	@Test
 	public void testGConeCommit() throws Exception {
-		Date expire = GitDateParser.parse("now", null, SystemReader
-				.getInstance().getLocale());
-		Properties res = git.gc().setExpire(expire).call();
+		Instant expireNow = GitTimeParser.parseInstant("now");
+		Properties res = git.gc().setExpire(expireNow).call();
 		assertTrue(res.size() == 8);
 	}
 
@@ -52,11 +50,8 @@ public void testGCmoreCommits() throws Exception {
 		writeTrashFile("b.txt", "a couple of words for gc to pack more 2");
 		writeTrashFile("c.txt", "a couple of words for gc to pack more 3");
 		git.commit().setAll(true).setMessage("commit3").call();
-		Properties res = git
-				.gc()
-				.setExpire(
-						GitDateParser.parse("now", null, SystemReader
-								.getInstance().getLocale())).call();
+		Instant expireNow = GitTimeParser.parseInstant("now");
+		Properties res = git.gc().setExpire(expireNow).call();
 		assertTrue(res.size() == 8);
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
index 7693434..e847e72 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
@@ -14,6 +14,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.time.Instant;
 
 import org.eclipse.jgit.api.ListBranchCommand.ListMode;
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -100,7 +101,7 @@ public void testClose() throws IOException, JGitInternalException,
 			GitAPIException {
 		File workTree = db.getWorkTree();
 		Git git = Git.open(workTree);
-		git.gc().setExpire(null).call();
+		git.gc().setExpire((Instant) null).call();
 		git.checkout().setName(git.getRepository().resolve("HEAD^").getName())
 				.call();
 		try {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
index 917b6c3..1ec5067 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
@@ -21,6 +21,9 @@
 import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Iterator;
 import java.util.regex.Pattern;
 
@@ -33,6 +36,7 @@
 import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.Sets;
@@ -45,6 +49,7 @@
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.GitDateFormatter;
 import org.eclipse.jgit.util.GitDateFormatter.Format;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.experimental.theories.DataPoints;
@@ -76,12 +81,12 @@ public void testMergeInItself() throws Exception {
 			assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus());
 		}
 		// no reflog entry written by merge
-		assertEquals("commit (initial): initial commit",
-				db
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("commit (initial): initial commit", refDb
 				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-		assertEquals("commit (initial): initial commit",
-				db
-				.getReflogReader(db.getBranch()).getLastEntry().getComment());
+		assertEquals("commit (initial): initial commit", refDb
+				.getReflogReader(db.getFullBranch()).getLastEntry()
+				.getComment());
 	}
 
 	@Test
@@ -96,10 +101,11 @@ public void testAlreadyUpToDate() throws Exception {
 			assertEquals(second, result.getNewHead());
 		}
 		// no reflog entry written by merge
-		assertEquals("commit: second commit", db
+		assertEquals("commit: second commit", db.getRefDatabase()
 				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-		assertEquals("commit: second commit", db
-				.getReflogReader(db.getBranch()).getLastEntry().getComment());
+		assertEquals("commit: second commit", db.getRefDatabase()
+				.getReflogReader(db.getFullBranch()).getLastEntry()
+				.getComment());
 	}
 
 	@Test
@@ -117,10 +123,13 @@ public void testFastForward() throws Exception {
 			assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
 			assertEquals(second, result.getNewHead());
 		}
+		RefDatabase refDb = db.getRefDatabase();
 		assertEquals("merge refs/heads/master: Fast-forward",
-				db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+				refDb.getReflogReader(Constants.HEAD)
+						.getLastEntry().getComment());
 		assertEquals("merge refs/heads/master: Fast-forward",
-				db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+				refDb.getReflogReader(db.getFullBranch())
+						.getLastEntry().getComment());
 	}
 
 	@Test
@@ -140,10 +149,12 @@ public void testFastForwardNoCommit() throws Exception {
 					result.getMergeStatus());
 			assertEquals(second, result.getNewHead());
 		}
-		assertEquals("merge refs/heads/master: Fast-forward", db
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("merge refs/heads/master: Fast-forward", refDb
 				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-		assertEquals("merge refs/heads/master: Fast-forward", db
-				.getReflogReader(db.getBranch()).getLastEntry().getComment());
+		assertEquals("merge refs/heads/master: Fast-forward", refDb
+				.getReflogReader(db.getFullBranch()).getLastEntry()
+				.getComment());
 	}
 
 	@Test
@@ -171,10 +182,12 @@ public void testFastForwardWithFiles() throws Exception {
 			assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus());
 			assertEquals(second, result.getNewHead());
 		}
-		assertEquals("merge refs/heads/master: Fast-forward",
-				db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
-		assertEquals("merge refs/heads/master: Fast-forward",
-				db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("merge refs/heads/master: Fast-forward", refDb
+				.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+		assertEquals("merge refs/heads/master: Fast-forward", refDb
+				.getReflogReader(db.getFullBranch()).getLastEntry()
+				.getComment());
 	}
 
 	@Test
@@ -229,14 +242,17 @@ public void testMergeSuccessAllStrategies(MergeStrategy mergeStrategy)
 					.include(db.exactRef(R_HEADS + MASTER)).call();
 			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 		}
+		RefDatabase refDb = db.getRefDatabase();
 		assertEquals(
 				"merge refs/heads/master: Merge made by "
 						+ mergeStrategy.getName() + ".",
-				db.getReflogReader(Constants.HEAD).getLastEntry().getComment());
+				refDb.getReflogReader(Constants.HEAD).getLastEntry()
+						.getComment());
 		assertEquals(
 				"merge refs/heads/master: Merge made by "
 						+ mergeStrategy.getName() + ".",
-				db.getReflogReader(db.getBranch()).getLastEntry().getComment());
+				refDb.getReflogReader(db.getFullBranch()).getLastEntry()
+						.getComment());
 	}
 
 	@Theory
@@ -662,14 +678,17 @@ public void testMultipleCreationsSameContent() throws Exception {
 					.setStrategy(MergeStrategy.RESOLVE).call();
 			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
 			assertEquals("1\nb(1)\n3\n", read(new File(db.getWorkTree(), "b")));
-			assertEquals("merge " + secondCommit.getId().getName()
-					+ ": Merge made by resolve.", db
-					.getReflogReader(Constants.HEAD)
-					.getLastEntry().getComment());
-			assertEquals("merge " + secondCommit.getId().getName()
-					+ ": Merge made by resolve.", db
-					.getReflogReader(db.getBranch())
-					.getLastEntry().getComment());
+			RefDatabase refDb = db.getRefDatabase();
+			assertEquals(
+					"merge " + secondCommit.getId().getName()
+							+ ": Merge made by resolve.",
+					refDb.getReflogReader(Constants.HEAD).getLastEntry()
+							.getComment());
+			assertEquals(
+					"merge " + secondCommit.getId().getName()
+							+ ": Merge made by resolve.",
+					refDb.getReflogReader(db.getFullBranch()).getLastEntry()
+							.getComment());
 		}
 	}
 
@@ -2086,6 +2105,94 @@ public void testMergeConflictWithMessageAndCommentCharAuto()
 		}
 	}
 
+	@Test
+	public void testMergeCaseInsensitiveRename() throws Exception {
+		Assume.assumeTrue(
+				"Test makes only sense on a case-insensitive file system",
+				db.isWorkTreeCaseInsensitive());
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "aaa");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// "Rename" "a" to "A"
+			git.rm().addFilepattern("a").call();
+			writeTrashFile("A", "aaa");
+			git.add().addFilepattern("A").call();
+			RevCommit master = git.commit().setMessage("rename to A").call();
+
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+
+			writeTrashFile("b", "bbb");
+			git.add().addFilepattern("b").call();
+			git.commit().setMessage("side").call();
+
+			// Merge master into side
+			MergeResult result = git.merge().include(master)
+					.setStrategy(MergeStrategy.RECURSIVE).call();
+			assertEquals(MergeStatus.MERGED, result.getMergeStatus());
+			assertTrue(new File(db.getWorkTree(), "A").isFile());
+			// Double check
+			boolean found = true;
+			try (DirectoryStream<Path> dir = Files
+					.newDirectoryStream(db.getWorkTree().toPath())) {
+				for (Path p : dir) {
+					found = "A".equals(p.getFileName().toString());
+					if (found) {
+						break;
+					}
+				}
+			}
+			assertTrue(found);
+		}
+	}
+
+	@Test
+	public void testMergeCaseInsensitiveRenameConflict() throws Exception {
+		Assume.assumeTrue(
+				"Test makes only sense on a case-insensitive file system",
+				db.isWorkTreeCaseInsensitive());
+		try (Git git = new Git(db)) {
+			writeTrashFile("a", "aaa");
+			git.add().addFilepattern("a").call();
+			RevCommit initialCommit = git.commit().setMessage("initial").call();
+			// "Rename" "a" to "A" and change it
+			git.rm().addFilepattern("a").call();
+			writeTrashFile("A", "yyy");
+			git.add().addFilepattern("A").call();
+			RevCommit master = git.commit().setMessage("rename to A").call();
+
+			createBranch(initialCommit, "refs/heads/side");
+			checkoutBranch("refs/heads/side");
+
+			writeTrashFile("a", "xxx");
+			git.add().addFilepattern("a").call();
+			git.commit().setMessage("side").call();
+
+			// Merge master into side
+			MergeResult result = git.merge().include(master)
+					.setStrategy(MergeStrategy.RECURSIVE).call();
+			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
+			File a = new File(db.getWorkTree(), "A");
+			assertTrue(a.isFile());
+			// Double check
+			boolean found = true;
+			try (DirectoryStream<Path> dir = Files
+					.newDirectoryStream(db.getWorkTree().toPath())) {
+				for (Path p : dir) {
+					found = "A".equals(p.getFileName().toString());
+					if (found) {
+						break;
+					}
+				}
+			}
+			assertTrue(found);
+			assertEquals(1, result.getConflicts().size());
+			assertTrue(result.getConflicts().containsKey("a"));
+			checkFile(a, "yyy");
+		}
+	}
+
 	private static void setExecutable(Git git, String path, boolean executable) {
 		FS.DETECTED.setExecute(
 				new File(git.getRepository().getWorkTree(), path), executable);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
index 70e990d..d1696d6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
@@ -22,6 +22,7 @@
 import java.io.PrintStream;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.util.Properties;
 
 import org.eclipse.jgit.api.errors.DetachedHeadException;
@@ -1146,7 +1147,7 @@ public void testPushAfterGC() throws Exception {
 			RevCommit commit2 = git2.commit().setMessage("adding a").call();
 
 			// run a gc to ensure we have a bitmap index
-			Properties res = git1.gc().setExpire(null).call();
+			Properties res = git1.gc().setExpire((Instant) null).call();
 			assertEquals(8, res.size());
 
 			// create another commit so we have something else to push
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 02e3a2e..4c8cf06 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -24,6 +24,8 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -55,6 +57,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RebaseTodoLine;
 import org.eclipse.jgit.lib.RebaseTodoLine.Action;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.ReflogEntry;
 import org.eclipse.jgit.lib.RepositoryState;
@@ -131,11 +134,12 @@ public void testFastForwardWithNewFile() throws Exception {
 		checkFile(file2, "file2");
 		assertEquals(Status.FAST_FORWARD, res.getStatus());
 
-		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
-		List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+		List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
 				.getReverseEntries();
-		List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+		List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries();
 		assertEquals("rebase finished: returning to refs/heads/topic", headLog
 				.get(0).getComment());
@@ -177,11 +181,12 @@ public void testFastForwardWithMultipleCommits() throws Exception {
 		checkFile(file2, "file2 new content");
 		assertEquals(Status.FAST_FORWARD, res.getStatus());
 
-		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
-		List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+		List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
 				.getReverseEntries();
-		List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+		List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries();
 		assertEquals("rebase finished: returning to refs/heads/topic", headLog
 				.get(0).getComment());
@@ -445,13 +450,14 @@ public void testRebaseShouldIgnoreMergeCommits()
 			assertEquals(a, rw.next());
 		}
 
-		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
-		List<ReflogEntry> sideLog = db.getReflogReader("refs/heads/side")
+		List<ReflogEntry> sideLog = refDb.getReflogReader("refs/heads/side")
 				.getReverseEntries();
-		List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+		List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
 				.getReverseEntries();
-		List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+		List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries();
 		assertEquals("rebase finished: returning to refs/heads/topic", headLog
 				.get(0).getComment());
@@ -766,9 +772,10 @@ public void testRebaseParentOntoHeadShouldBeUptoDate() throws Exception {
 		RebaseResult result = git.rebase().setUpstream(parent).call();
 		assertEquals(Status.UP_TO_DATE, result.getStatus());
 
-		assertEquals(2, db.getReflogReader(Constants.HEAD).getReverseEntries()
-				.size());
-		assertEquals(2, db.getReflogReader("refs/heads/master")
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(2, refDb.getReflogReader(Constants.HEAD)
+				.getReverseEntries().size());
+		assertEquals(2, refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries().size());
 	}
 
@@ -784,9 +791,10 @@ public void testUpToDate() throws Exception {
 		RebaseResult res = git.rebase().setUpstream(first).call();
 		assertEquals(Status.UP_TO_DATE, res.getStatus());
 
-		assertEquals(1, db.getReflogReader(Constants.HEAD).getReverseEntries()
-				.size());
-		assertEquals(1, db.getReflogReader("refs/heads/master")
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(1, refDb.getReflogReader(Constants.HEAD)
+				.getReverseEntries().size());
+		assertEquals(1, refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries().size());
 	}
 
@@ -844,11 +852,12 @@ public void testConflictFreeWithSingleFile() throws Exception {
 					db.resolve(Constants.HEAD)).getParent(0));
 		}
 		assertEquals(origHead, db.readOrigHead());
-		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> headLog = refDb.getReflogReader(Constants.HEAD)
 				.getReverseEntries();
-		List<ReflogEntry> topicLog = db.getReflogReader("refs/heads/topic")
+		List<ReflogEntry> topicLog = refDb.getReflogReader("refs/heads/topic")
 				.getReverseEntries();
-		List<ReflogEntry> masterLog = db.getReflogReader("refs/heads/master")
+		List<ReflogEntry> masterLog = refDb.getReflogReader("refs/heads/master")
 				.getReverseEntries();
 		assertEquals(2, masterLog.size());
 		assertEquals(3, topicLog.size());
@@ -896,8 +905,8 @@ public void testDetachedHead() throws Exception {
 					db.resolve(Constants.HEAD)).getParent(0));
 		}
 
-		List<ReflogEntry> headLog = db.getReflogReader(Constants.HEAD)
-				.getReverseEntries();
+		List<ReflogEntry> headLog = db.getRefDatabase()
+				.getReflogReader(Constants.HEAD).getReverseEntries();
 		assertEquals(8, headLog.size());
 		assertEquals("rebase: change file1 in topic", headLog.get(0)
 				.getComment());
@@ -1603,7 +1612,7 @@ public void testStopOnConflictFileCreationAndDeletion() throws Exception {
 	public void testAuthorScriptConverter() throws Exception {
 		// -1 h timezone offset
 		PersonIdent ident = new PersonIdent("Author name", "a.mail@some.com",
-				123456789123L, -60);
+				Instant.ofEpochMilli(123456789123L), ZoneOffset.ofHours(-1));
 		String convertedAuthor = git.rebase().toAuthorScript(ident);
 		String[] lines = convertedAuthor.split("\n");
 		assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
@@ -1615,12 +1624,14 @@ public void testAuthorScriptConverter() throws Exception {
 		assertEquals(ident.getName(), parsedIdent.getName());
 		assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
 		// this is rounded to the last second
-		assertEquals(123456789000L, parsedIdent.getWhen().getTime());
-		assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
+		assertEquals(123456789000L,
+				parsedIdent.getWhenAsInstant().toEpochMilli());
+		assertEquals(ident.getZoneId(), parsedIdent.getZoneId());
 
 		// + 9.5h timezone offset
 		ident = new PersonIdent("Author name", "a.mail@some.com",
-				123456789123L, +570);
+				Instant.ofEpochMilli(123456789123L),
+				ZoneOffset.ofHoursMinutes(9, 30));
 		convertedAuthor = git.rebase().toAuthorScript(ident);
 		lines = convertedAuthor.split("\n");
 		assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
@@ -1631,8 +1642,9 @@ public void testAuthorScriptConverter() throws Exception {
 				convertedAuthor.getBytes(UTF_8));
 		assertEquals(ident.getName(), parsedIdent.getName());
 		assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
-		assertEquals(123456789000L, parsedIdent.getWhen().getTime());
-		assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
+		assertEquals(123456789000L,
+				parsedIdent.getWhenAsInstant().toEpochMilli());
+		assertEquals(ident.getZoneId(), parsedIdent.getZoneId());
 	}
 
 	@Test
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/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
index 8a479a0..99873e1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java
@@ -36,11 +36,13 @@
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.util.FileUtils;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 
 public class ResetCommandTest extends RepositoryTestCase {
@@ -554,46 +556,73 @@ public void testHardResetOnUnbornBranch() throws Exception {
 		assertNull(db.resolve(Constants.HEAD));
 	}
 
+	@Test
+	public void testHardResetFileMode() throws Exception {
+		Assume.assumeTrue("Test must be able to set executable bit",
+				db.getFS().supportsExecute());
+		git = new Git(db);
+		File a = writeTrashFile("a.txt", "aaa");
+		File b = writeTrashFile("b.txt", "bbb");
+		db.getFS().setExecute(b, true);
+		assertFalse(db.getFS().canExecute(a));
+		assertTrue(db.getFS().canExecute(b));
+		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+		RevCommit commit = git.commit().setMessage("files created").call();
+		db.getFS().setExecute(a, true);
+		db.getFS().setExecute(b, false);
+		assertTrue(db.getFS().canExecute(a));
+		assertFalse(db.getFS().canExecute(b));
+		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
+		git.commit().setMessage("change exe bits").call();
+		Ref ref = git.reset().setRef(commit.getName()).setMode(HARD).call();
+		assertSameAsHead(ref);
+		assertEquals(commit.getId(), ref.getObjectId());
+		assertFalse(db.getFS().canExecute(a));
+		assertTrue(db.getFS().canExecute(b));
+	}
+
 	private void assertReflog(ObjectId prevHead, ObjectId head)
 			throws IOException {
 		// Check the reflog for HEAD
-		String actualHeadMessage = db.getReflogReader(Constants.HEAD)
+		RefDatabase refDb = db.getRefDatabase();
+		String actualHeadMessage = refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getComment();
 		String expectedHeadMessage = head.getName() + ": updating HEAD";
 		assertEquals(expectedHeadMessage, actualHeadMessage);
-		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+		assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getNewId().getName());
-		assertEquals(prevHead.getName(), db.getReflogReader(Constants.HEAD)
+		assertEquals(prevHead.getName(), refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getOldId().getName());
 
 		// The reflog for master contains the same as the one for HEAD
-		String actualMasterMessage = db.getReflogReader("refs/heads/master")
+		String actualMasterMessage = refDb.getReflogReader("refs/heads/master")
 				.getLastEntry().getComment();
 		String expectedMasterMessage = head.getName() + ": updating HEAD"; // yes!
 		assertEquals(expectedMasterMessage, actualMasterMessage);
-		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+		assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getNewId().getName());
-		assertEquals(prevHead.getName(), db
-				.getReflogReader("refs/heads/master").getLastEntry().getOldId()
-				.getName());
+		assertEquals(prevHead.getName(),
+				refDb.getReflogReader("refs/heads/master").getLastEntry()
+						.getOldId().getName());
 	}
 
 	private void assertReflogDisabled(ObjectId head)
 			throws IOException {
+		RefDatabase refDb = db.getRefDatabase();
 		// Check the reflog for HEAD
-		String actualHeadMessage = db.getReflogReader(Constants.HEAD)
+		String actualHeadMessage = refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getComment();
 		String expectedHeadMessage = "commit: adding a.txt and dir/b.txt";
 		assertEquals(expectedHeadMessage, actualHeadMessage);
-		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+		assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getOldId().getName());
 
 		// The reflog for master contains the same as the one for HEAD
-		String actualMasterMessage = db.getReflogReader("refs/heads/master")
+		String actualMasterMessage = refDb.getReflogReader("refs/heads/master")
 				.getLastEntry().getComment();
 		String expectedMasterMessage = "commit: adding a.txt and dir/b.txt";
 		assertEquals(expectedMasterMessage, actualMasterMessage);
-		assertEquals(head.getName(), db.getReflogReader(Constants.HEAD)
+		assertEquals(head.getName(), refDb.getReflogReader(Constants.HEAD)
 				.getLastEntry().getOldId().getName());
 	}
 	/**
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
index 4ebe994..89fdb32 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RevertCommandTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011, Robin Rosenberg and others
+ * Copyright (C) 2011, 2024 Robin Rosenberg 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
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@@ -59,7 +60,9 @@ public void testRevert() throws IOException, JGitInternalException,
 			writeTrashFile("a",
 					"first line\nsecond line\nthird line\nfourth line\n");
 			git.add().addFilepattern("a").call();
-			RevCommit fixingA = git.commit().setMessage("fixed a").call();
+			// Commit message with a non-empty second line on purpose
+			RevCommit fixingA = git.commit().setMessage("fixed a\nsecond line")
+					.call();
 
 			writeTrashFile("b", "first line\n");
 			git.add().addFilepattern("b").call();
@@ -78,16 +81,18 @@ public void testRevert() throws IOException, JGitInternalException,
 					+ "This reverts commit " + fixingA.getId().getName() + ".\n";
 			assertEquals(expectedMessage, revertCommit.getFullMessage());
 			assertEquals("fixed b", history.next().getFullMessage());
-			assertEquals("fixed a", history.next().getFullMessage());
+			assertEquals("fixed a\nsecond line",
+					history.next().getFullMessage());
 			assertEquals("enlarged a", history.next().getFullMessage());
 			assertEquals("create b", history.next().getFullMessage());
 			assertEquals("create a", history.next().getFullMessage());
 			assertFalse(history.hasNext());
 
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
 		}
@@ -167,10 +172,11 @@ public void testRevertMultiple() throws IOException, JGitInternalException,
 			assertEquals("add first", history.next().getFullMessage());
 			assertFalse(history.hasNext());
 
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
 		}
@@ -220,10 +226,11 @@ public void testRevertMultipleWithFail() throws IOException,
 			assertEquals("add first", history.next().getFullMessage());
 			assertFalse(history.hasNext());
 
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
-			reader = db.getReflogReader(db.getBranch());
+			reader = refDb.getReflogReader(db.getFullBranch());
 			assertTrue(reader.getLastEntry().getComment()
 					.startsWith("revert: Revert \""));
 		}
@@ -428,12 +435,13 @@ private void doRevertAndCheckResult(final Git git,
 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
 		if (reason == null) {
-			ReflogReader reader = db.getReflogReader(Constants.HEAD);
-			assertTrue(reader.getLastEntry().getComment()
-					.startsWith("revert: "));
-			reader = db.getReflogReader(db.getBranch());
-			assertTrue(reader.getLastEntry().getComment()
-					.startsWith("revert: "));
+			RefDatabase refDb = db.getRefDatabase();
+			ReflogReader reader = refDb.getReflogReader(Constants.HEAD);
+			assertTrue(
+					reader.getLastEntry().getComment().startsWith("revert: "));
+			reader = refDb.getReflogReader(db.getFullBranch());
+			assertTrue(
+					reader.getLastEntry().getComment().startsWith("revert: "));
 		}
 	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
index 5d0ab05..18cd21a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java
@@ -409,8 +409,8 @@ public void refLogIncludesCommitMessage() throws Exception {
 		assertEquals("content", read(committedFile));
 		validateStashedCommit(stashed);
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		ReflogEntry entry = reader.getLastEntry();
 		assertNotNull(entry);
 		assertEquals(ObjectId.zeroId(), entry.getOldId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
index c81731d..d937579 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java
@@ -92,8 +92,8 @@ public void dropSingleStashedCommit() throws Exception {
 		stashRef = git.getRepository().exactRef(Constants.R_STASH);
 		assertNull(stashRef);
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		assertNull(reader);
 	}
 
@@ -120,8 +120,8 @@ public void dropAll() throws Exception {
 		assertNull(git.stashDrop().setAll(true).call());
 		assertNull(git.getRepository().exactRef(Constants.R_STASH));
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		assertNull(reader);
 	}
 
@@ -150,8 +150,8 @@ public void dropFirstStashedCommit() throws Exception {
 		assertNotNull(stashRef);
 		assertEquals(firstStash, stashRef.getObjectId());
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		List<ReflogEntry> entries = reader.getReverseEntries();
 		assertEquals(1, entries.size());
 		assertEquals(ObjectId.zeroId(), entries.get(0).getOldId());
@@ -192,8 +192,8 @@ public void dropMiddleStashCommit() throws Exception {
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		List<ReflogEntry> entries = reader.getReverseEntries();
 		assertEquals(2, entries.size());
 		assertEquals(ObjectId.zeroId(), entries.get(1).getOldId());
@@ -250,8 +250,8 @@ public void dropBoundaryStashedCommits() throws Exception {
 		assertNotNull(stashRef);
 		assertEquals(thirdStash, stashRef.getObjectId());
 
-		ReflogReader reader = git.getRepository().getReflogReader(
-				Constants.R_STASH);
+		ReflogReader reader = git.getRepository().getRefDatabase()
+				.getReflogReader(Constants.R_STASH);
 		List<ReflogEntry> entries = reader.getReverseEntries();
 		assertEquals(2, entries.size());
 		assertEquals(ObjectId.zeroId(), entries.get(1).getOldId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/blame/BlameGeneratorTest.java
index 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/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
index f23469e..35b9533 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
@@ -26,6 +26,7 @@
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.junit.Before;
 import org.junit.Test;
@@ -230,10 +231,10 @@ private void assertAttributesNode(TreeWalk walk, String pathName,
 		else {
 
 			Attributes entryAttributes = new Attributes();
-			new AttributesHandler(walk).mergeAttributes(attributesNode,
-					pathName,
-					false,
-					entryAttributes);
+			new AttributesHandler(walk,
+					() -> walk.getTree(CanonicalTreeParser.class))
+							.mergeAttributes(attributesNode, pathName, false,
+									entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index 1fcfbaf..dbbcb75 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
@@ -20,6 +20,7 @@
 
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.junit.After;
 import org.junit.Test;
@@ -156,8 +157,9 @@ public void testDoubleAsteriskAtEnd() throws IOException {
 	private void assertAttribute(String path, AttributesNode node,
 			Attributes attrs) throws IOException {
 		Attributes attributes = new Attributes();
-		new AttributesHandler(DUMMY_WALK).mergeAttributes(node, path, false,
-				attributes);
+		new AttributesHandler(DUMMY_WALK,
+				() -> DUMMY_WALK.getTree(CanonicalTreeParser.class))
+						.mergeAttributes(node, path, false, attributes);
 		assertEquals(attrs, attributes);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
index 7b573e1..c6c9138 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
@@ -26,6 +26,7 @@
 import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
 import org.eclipse.jgit.treewalk.FileTreeIterator;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
@@ -194,9 +195,10 @@ private void assertAttributesNode(TreeWalk walk, String pathName,
 		else {
 
 			Attributes entryAttributes = new Attributes();
-			new AttributesHandler(walk).mergeAttributes(attributesNode,
-					pathName, false,
-					entryAttributes);
+			new AttributesHandler(walk,
+					() -> walk.getTree(CanonicalTreeParser.class))
+							.mergeAttributes(attributesNode, pathName, false,
+									entryAttributes);
 
 			if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
 				for (Attribute attribute : nodeAttrs) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java
new file mode 100644
index 0000000..65cac11
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameGeneratorCacheTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import static java.lang.String.join;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.blame.cache.BlameCache;
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class BlameGeneratorCacheTest extends RepositoryTestCase {
+	private static final String FILE = "file.txt";
+
+	/**
+	 * Simple history:
+	 *
+	 * <pre>
+	 *          C1    C2    C3    C4   C4 blame
+	 * lines ----------------------------------
+	 * L1    |  C1    C1    C1    C1     C1
+	 * L2    |  C1    C1   *C3   *C4     C4
+	 * L3    |  C1    C1   *C3    C3     C3
+	 * L4    |       *C2    C2   *C4     C4
+	 * </pre>
+	 *
+	 * @throws Exception any error
+	 */
+	@Test
+	public void blame_simple_correctRegions() throws Exception {
+		RevCommit c1, c2, c3, c4;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+			c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+			c3 = commit(r, lines("L1C1", "L2C3", "L3C3", "L4C2"), c2);
+			c4 = commit(r, lines("L1C1", "L2C4", "L3C3", "L4C4"), c3);
+		}
+
+		List<EmittedRegion> expectedRegions = Arrays.asList(
+				new EmittedRegion(c1, 0, 1),
+				new EmittedRegion(c4, 1, 2),
+				new EmittedRegion(c3, 2, 3),
+				new EmittedRegion(c4, 3, 4));
+
+		assertRegions(c4, null, expectedRegions, 4);
+		assertRegions(c4, emptyCache(), expectedRegions, 4);
+		assertRegions(c4, blameAndCache(c4), expectedRegions, 4);
+		assertRegions(c4, blameAndCache(c3), expectedRegions, 4);
+		assertRegions(c4, blameAndCache(c2), expectedRegions, 4);
+		assertRegions(c4, blameAndCache(c1), expectedRegions, 4);
+	}
+
+	@Test
+	public void blame_simple_cacheUsage() throws Exception {
+		RevCommit c1, c2, c3, c4;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+			c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+			c3 = commit(r, lines("L1C1", "L2C3", "L3C3", "L4C2"), c2);
+			c4 = commit(r, lines("L1C1", "L2C4", "L3C3", "L4C4"), c3);
+		}
+
+		assertCacheUsage(c4, null, false, 4);
+		assertCacheUsage(c4, emptyCache(), false, 4);
+		assertCacheUsage(c4, blameAndCache(c4), true, 1);
+		assertCacheUsage(c4, blameAndCache(c3), true, 2);
+		assertCacheUsage(c4, blameAndCache(c2), true, 3);
+		assertCacheUsage(c4, blameAndCache(c1), true, 4);
+	}
+
+	/**
+	 * Overwrite:
+	 *
+	 * <pre>
+	 *          C1    C2    C3    C3 blame
+	 * lines ----------------------------------
+	 * L1    |  C1    C1   *C3      C3
+	 * L2    |  C1    C1   *C3      C3
+	 * L3    |  C1    C1   *C3      C3
+	 * L4    |       *C2
+	 * </pre>
+	 *
+	 * @throws Exception any error
+	 */
+	@Test
+	public void blame_ovewrite_correctRegions() throws Exception {
+		RevCommit c1, c2, c3;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+			c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+			c3 = commit(r, lines("L1C3", "L2C3", "L3C3"), c2);
+		}
+
+		List<EmittedRegion> expectedRegions = Arrays.asList(
+				new EmittedRegion(c3, 0, 3));
+
+		assertRegions(c3, null, expectedRegions, 3);
+		assertRegions(c3, emptyCache(), expectedRegions, 3);
+		assertRegions(c3, blameAndCache(c3), expectedRegions, 3);
+		assertRegions(c3, blameAndCache(c2), expectedRegions, 3);
+		assertRegions(c3, blameAndCache(c1), expectedRegions, 3);
+	}
+
+	@Test
+	public void blame_overwrite_cacheUsage() throws Exception {
+		RevCommit c1, c2, c3;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commit(r, lines("L1C1", "L2C1", "L3C1"));
+			c2 = commit(r, lines("L1C1", "L2C1", "L3C1", "L4C2"), c1);
+			c3 = commit(r, lines("L1C3", "L2C3", "L3C3"), c2);
+		}
+
+		assertCacheUsage(c3, null, false, 1);
+		assertCacheUsage(c3, emptyCache(), false, 1);
+		assertCacheUsage(c3, blameAndCache(c3), true, 1);
+		assertCacheUsage(c3, blameAndCache(c2), false, 1);
+		assertCacheUsage(c3, blameAndCache(c1), false, 1);
+	}
+
+	/**
+	 * Merge:
+	 *
+	 * <pre>
+	 *                 root
+	 *                 ----
+	 *                 L1  -
+	 *                 L2  -
+	 *                 L3  -
+	 *               /     \
+	 *           sideA     sideB
+	 *           -----     -----
+	 *           *L1 a      L1 -
+	 *           *L2 a      L2 -
+	 *           *L3 a      L3 -
+	 *           *L4 a     *L4 b
+	 *            L5 -     *L5 b
+	 *            L6 -     *L6 b
+	 *            L7 -     *L7 b
+	 *              \       /
+	 *                merge
+	 *                -----
+	 *              L1-L4 a (from sideA)
+	 *              L5-L7 - (common, from root)
+	 *              L8-L11 b (from sideB)
+	 * </pre>
+	 *
+	 * @throws Exception any error
+	 */
+	@Test
+	public void blame_merge_correctRegions() throws Exception {
+		RevCommit root, sideA, sideB, mergedTip;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			root = commitAsLines(r, "---");
+			sideA = commitAsLines(r, "aaaa---", root);
+			sideB = commitAsLines(r, "---bbbb", root);
+			mergedTip = commitAsLines(r, "aaaa---bbbb", sideA, sideB);
+		}
+
+		List<EmittedRegion> expectedRegions = Arrays.asList(
+				new EmittedRegion(sideA, 0, 4),
+				new EmittedRegion(root, 4, 7),
+				new EmittedRegion(sideB, 7, 11));
+
+		assertRegions(mergedTip, null, expectedRegions, 11);
+		assertRegions(mergedTip, emptyCache(), expectedRegions, 11);
+		assertRegions(mergedTip, blameAndCache(root), expectedRegions, 11);
+		assertRegions(mergedTip, blameAndCache(sideA), expectedRegions, 11);
+		assertRegions(mergedTip, blameAndCache(sideB), expectedRegions, 11);
+		assertRegions(mergedTip, blameAndCache(mergedTip), expectedRegions, 11);
+	}
+
+	@Test
+	public void blame_merge_cacheUsage() throws Exception {
+		RevCommit root, sideA, sideB, mergedTip;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			root = commitAsLines(r, "---");
+			sideA = commitAsLines(r, "aaaa---", root);
+			sideB = commitAsLines(r, "---bbbb", root);
+			mergedTip = commitAsLines(r, "aaaa---bbbb", sideA, sideB);
+		}
+
+		assertCacheUsage(mergedTip, null, /* cacheUsed */ false,
+				/* candidates */ 4);
+		assertCacheUsage(mergedTip, emptyCache(), false, 4);
+		assertCacheUsage(mergedTip, blameAndCache(mergedTip), true, 1);
+
+		// While splitting unblamed regions to parents, sideA comes first
+		// and gets "aaaa----". Processing is by commit time, so sideB is
+		// explored first
+		assertCacheUsage(mergedTip, blameAndCache(sideA), true, 3);
+		assertCacheUsage(mergedTip, blameAndCache(sideB), true, 4);
+		assertCacheUsage(mergedTip, blameAndCache(root), true, 4);
+	}
+
+	/**
+	 * Moving block (insertion)
+	 *
+	 * <pre>
+	 *          C1    C2    C3    C3 blame
+	 * lines ----------------------------------
+	 * L1    |  C1    C1    C1      C1
+	 * L2    |  C1   *C2    C2      C2
+	 * L3    |        C1   *C3      C3
+	 * L4    |              C1      C1
+	 * </pre>
+	 *
+	 * @throws Exception any error
+	 */
+	@Test
+	public void blame_movingBlock_correctRegions() throws Exception {
+		RevCommit c1, c2, c3;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commit(r, lines("L1C1", "L2C1"));
+			c2 = commit(r, lines("L1C1", "middle", "L2C1"), c1);
+			c3 = commit(r, lines("L1C1", "middle", "extra", "L2C1"), c2);
+		}
+
+		List<EmittedRegion> expectedRegions = Arrays.asList(
+				new EmittedRegion(c1, 0, 1),
+				new EmittedRegion(c2, 1, 2),
+				new EmittedRegion(c3, 2, 3),
+				new EmittedRegion(c1, 3, 4));
+
+		assertRegions(c3, null, expectedRegions, 4);
+		assertRegions(c3, emptyCache(), expectedRegions, 4);
+		assertRegions(c3, blameAndCache(c3), expectedRegions, 4);
+		assertRegions(c3, blameAndCache(c2), expectedRegions, 4);
+		assertRegions(c3, blameAndCache(c1), expectedRegions, 4);
+	}
+
+	@Test
+	public void blame_movingBlock_cacheUsage() throws Exception {
+		RevCommit c1, c2, c3;
+		try (TestRepository<FileRepository> r = new TestRepository<>(db)) {
+			c1 = commitAsLines(r, "root---");
+			c2 = commitAsLines(r, "rootXXX---", c1);
+			c3 = commitAsLines(r, "rootYYYXXX---", c2);
+		}
+
+		assertCacheUsage(c3, null, false, 3);
+		assertCacheUsage(c3, emptyCache(), false, 3);
+		assertCacheUsage(c3, blameAndCache(c3), true, 1);
+		assertCacheUsage(c3, blameAndCache(c2), true, 2);
+		assertCacheUsage(c3, blameAndCache(c1), true, 3);
+	}
+
+	private void assertRegions(RevCommit commit, InMemoryBlameCache cache,
+			List<EmittedRegion> expectedRegions, int resultLineCount)
+			throws IOException {
+		try (BlameGenerator gen = new BlameGenerator(db, FILE, cache)) {
+			gen.push(null, db.parseCommit(commit));
+			List<EmittedRegion> regions = consume(gen);
+			assertRegionsEquals(expectedRegions, regions);
+			assertAllLinesCovered(/* lines= */ resultLineCount, regions);
+		}
+	}
+
+	private void assertCacheUsage(RevCommit commit, InMemoryBlameCache cache,
+			boolean useCache, int candidatesVisited) throws IOException {
+		try (BlameGenerator gen = new BlameGenerator(db, FILE, cache)) {
+			gen.push(null, db.parseCommit(commit));
+			consume(gen);
+			assertEquals(useCache, gen.getStats().isCacheHit());
+			assertEquals(candidatesVisited,
+					gen.getStats().getCandidatesVisited());
+		}
+	}
+
+	private static void assertAllLinesCovered(int lines,
+			List<EmittedRegion> regions) {
+		Collections.sort(regions);
+		assertEquals("Starts in first line", 0, regions.get(0).resultStart());
+		for (int i = 1; i < regions.size(); i++) {
+			assertEquals("No gaps", regions.get(i).resultStart(),
+					regions.get(i - 1).resultEnd());
+		}
+		assertEquals("Ends in last line", lines,
+				regions.get(regions.size() - 1).resultEnd());
+	}
+
+	private static void assertRegionsEquals(
+			List<EmittedRegion> expected, List<EmittedRegion> actual) {
+		assertEquals(expected.size(), actual.size());
+		Collections.sort(actual);
+		for (int i = 0; i < expected.size(); i++) {
+			assertEquals(String.format("List differ in element %d", i),
+					expected.get(i), actual.get(i));
+		}
+	}
+
+	private static InMemoryBlameCache emptyCache() {
+		return new InMemoryBlameCache("<empty>");
+	}
+
+	private List<EmittedRegion> consume(BlameGenerator generator)
+			throws IOException {
+		List<EmittedRegion> result = new ArrayList<>();
+		while (generator.next()) {
+			EmittedRegion genRegion = new EmittedRegion(
+					generator.getSourceCommit().toObjectId(),
+					generator.getResultStart(), generator.getResultEnd());
+			result.add(genRegion);
+		}
+		return result;
+	}
+
+	private InMemoryBlameCache blameAndCache(RevCommit commit)
+			throws IOException {
+		List<CacheRegion> regions;
+		try (BlameGenerator generator = new BlameGenerator(db, FILE)) {
+			generator.push(null, commit);
+			regions = consume(generator).stream()
+					.map(EmittedRegion::asCacheRegion)
+					.collect(Collectors.toUnmodifiableList());
+		}
+		InMemoryBlameCache cache = new InMemoryBlameCache("<x>");
+		cache.put(commit, FILE, regions);
+		return cache;
+	}
+
+	private static RevCommit commitAsLines(TestRepository<?> r,
+			String charPerLine, RevCommit... parents) throws Exception {
+		return commit(r, charPerLine.replaceAll("\\S", "$0\n"), parents);
+	}
+
+	private static RevCommit commit(TestRepository<?> r, String contents,
+			RevCommit... parents) throws Exception {
+		return r.commit(r.tree(r.file(FILE, r.blob(contents))), parents);
+	}
+
+	private static String lines(String... l) {
+		return join("\n", l);
+	}
+
+	private record EmittedRegion(ObjectId oid, int resultStart, int resultEnd)
+			implements Comparable<EmittedRegion> {
+		@Override
+		public int compareTo(EmittedRegion o) {
+			return resultStart - o.resultStart;
+		}
+
+		CacheRegion asCacheRegion() {
+			return new CacheRegion(FILE, oid, resultStart, resultEnd);
+		}
+	}
+
+	private static class InMemoryBlameCache implements BlameCache {
+
+		private final Map<Key, List<CacheRegion>> cache = new HashMap<>();
+
+		private final String description;
+
+		public InMemoryBlameCache(String description) {
+			this.description = description;
+		}
+
+		@Override
+		public List<CacheRegion> get(Repository repo, ObjectId commitId,
+									 String path) throws IOException {
+			return cache.get(new Key(commitId.name(), path));
+		}
+
+		public void put(ObjectId commitId, String path,
+						List<CacheRegion> cachedRegions) {
+			cache.put(new Key(commitId.name(), path), cachedRegions);
+		}
+
+		@Override
+		public String toString() {
+			return "InMemoryCache: " + description;
+		}
+
+		record Key(String commitId, String path) {
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java
new file mode 100644
index 0000000..1b28676
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/blame/BlameRegionMergerTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class BlameRegionMergerTest extends RepositoryTestCase {
+
+	private static final ObjectId O1 = ObjectId
+			.fromString("ff6dd8db6edc9aa0ac58fea1d14a55be46c3eb14");
+
+	private static final ObjectId O2 = ObjectId
+			.fromString("c3c7f680c6bee238617f25f6aa85d0b565fc8ecb");
+
+	private static final ObjectId O3 = ObjectId
+			.fromString("29e014aad0399fe8ede7c101d01b6e440ac9966b");
+
+	List<RevCommit> fakeCommits = List.of(new FakeRevCommit(O1),
+			new FakeRevCommit(O2), new FakeRevCommit(O3));
+
+	// In reverse order, so the code doesn't assume a sorted list
+	List<CacheRegion> cachedRegions = List.of(
+			new CacheRegion("README", O3, 20, 30),
+			new CacheRegion("README", O2, 10, 20),
+			new CacheRegion("README", O1, 0, 10));
+
+	BlameRegionMerger blamer = new BlameRegionMergerFakeCommits(fakeCommits,
+			cachedRegions);
+
+	@Test
+	public void intersectRegions_allInside() {
+		Region unblamed = new Region(15, 18, 10);
+		CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+		Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+		// Same lines in result and source
+		assertEquals(15, result.resultStart);
+		assertEquals(18, result.sourceStart);
+		assertEquals(10, result.length);
+		assertNull(result.next);
+	}
+
+	@Test
+	public void intersectRegions_startsBefore() {
+		// Intesecting [4, 14) with [10, 90)
+		Region unblamed = new Region(30, 4, 10);
+		CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+		Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+		// The unblamed region starting at 4 (sourceStart), starts at 30 in the
+		// original file (resultStart). e.g. some commit introduced
+		// lines. If we take the second portion of the region, we need to move
+		// the result start accordingly.
+		assertEquals(36, result.resultStart);
+		assertEquals(10, result.sourceStart);
+		assertEquals(4, result.length);
+		assertNull(result.next);
+	}
+
+	@Test
+	public void intersectRegions_endsAfter() {
+		// Intesecting [85, 95) with [10, 90)
+		Region unblamed = new Region(30, 85, 10);
+		CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+		Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+		assertEquals(30, result.resultStart);
+		assertEquals(85, result.sourceStart);
+		assertEquals(5, result.length);
+		assertNull(result.next);
+	}
+
+	@Test
+	public void intersectRegions_spillOverBothSides() {
+		// Intesecting [5, 100) with [10, 90)
+		Region unblamed = new Region(30, 5, 95);
+		CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+		Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+		assertEquals(35, result.resultStart);
+		assertEquals(10, result.sourceStart);
+		assertEquals(80, result.length);
+		assertNull(result.next);
+	}
+
+	@Test
+	public void intersectRegions_exactMatch() {
+		// Intesecting [5, 100) with [10, 90)
+		Region unblamed = new Region(30, 10, 80);
+		CacheRegion blamed = new CacheRegion("README", O1, 10, 90);
+
+		Region result = BlameRegionMerger.intersectRegions(unblamed, blamed);
+
+		assertEquals(30, result.resultStart);
+		assertEquals(10, result.sourceStart);
+		assertEquals(80, result.length);
+		assertNull(result.next);
+	}
+
+	@Test
+	public void findOverlaps_allInside() {
+		Region unblamed = new Region(0, 11, 4);
+		List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+		assertEquals(1, overlaps.size());
+		assertEquals(10, overlaps.get(0).getStart());
+		assertEquals(20, overlaps.get(0).getEnd());
+	}
+
+	@Test
+	public void findOverlaps_overTwoRegions() {
+		Region unblamed = new Region(0, 8, 4);
+		List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+		assertEquals(2, overlaps.size());
+		assertEquals(0, overlaps.get(0).getStart());
+		assertEquals(10, overlaps.get(0).getEnd());
+		assertEquals(10, overlaps.get(1).getStart());
+		assertEquals(20, overlaps.get(1).getEnd());
+	}
+
+	@Test
+	public void findOverlaps_overThreeRegions() {
+		Region unblamed = new Region(0, 8, 15);
+		List<CacheRegion> overlaps = blamer.findOverlaps(unblamed);
+		assertEquals(3, overlaps.size());
+		assertEquals(0, overlaps.get(0).getStart());
+		assertEquals(10, overlaps.get(0).getEnd());
+		assertEquals(10, overlaps.get(1).getStart());
+		assertEquals(20, overlaps.get(1).getEnd());
+		assertEquals(20, overlaps.get(2).getStart());
+		assertEquals(30, overlaps.get(2).getEnd());
+	}
+
+	@Test
+	public void blame_exactOverlap() throws IOException {
+		Region unblamed = new Region(0, 10, 10);
+		List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+
+		assertEquals(1, blamed.size());
+		Candidate c = blamed.get(0);
+		assertEquals(c.sourceCommit.name(), O2.name());
+		assertEquals(c.regionList.resultStart, unblamed.resultStart);
+		assertEquals(c.regionList.sourceStart, unblamed.sourceStart);
+		assertEquals(10, c.regionList.length);
+		assertNull(c.regionList.next);
+	}
+
+	@Test
+	public void blame_corruptedIndex() {
+		Region outOfRange = new Region(0, 43, 4);
+		// This region is out of the blamed area
+		assertThrows(IOException.class,
+				() -> blamer.mergeOneRegion(outOfRange));
+	}
+
+	@Test
+	public void blame_allInsideOneBlamedRegion() throws IOException {
+		Region unblamed = new Region(0, 5, 3);
+		// This region if fully blamed to O1
+		List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+		assertEquals(1, blamed.size());
+		Candidate c = blamed.get(0);
+		assertEquals(c.sourceCommit.name(), O1.name());
+		assertEquals(c.regionList.resultStart, unblamed.resultStart);
+		assertEquals(c.regionList.sourceStart, unblamed.sourceStart);
+		assertEquals(3, c.regionList.length);
+		assertNull(c.regionList.next);
+	}
+
+	@Test
+	public void blame_overTwoBlamedRegions() throws IOException {
+		Region unblamed = new Region(0, 8, 5);
+		// (8, 10) belongs go C1, (10, 13) to C2
+		List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+		assertEquals(2, blamed.size());
+		Candidate c = blamed.get(0);
+		assertEquals(c.sourceCommit.name(), O1.name());
+		assertEquals(unblamed.resultStart, c.regionList.resultStart);
+		assertEquals(unblamed.sourceStart, c.regionList.sourceStart);
+		assertEquals(2, c.regionList.length);
+		assertNull(c.regionList.next);
+
+		c = blamed.get(1);
+		assertEquals(c.sourceCommit.name(), O2.name());
+		assertEquals(2, c.regionList.resultStart);
+		assertEquals(10, c.regionList.sourceStart);
+		assertEquals(3, c.regionList.length);
+		assertNull(c.regionList.next);
+	}
+
+	@Test
+	public void blame_all() throws IOException {
+		Region unblamed = new Region(0, 0, 30);
+		List<Candidate> blamed = blamer.mergeOneRegion(unblamed);
+		assertEquals(3, blamed.size());
+		Candidate c = blamed.get(0);
+		assertEquals(c.sourceCommit.name(), O1.name());
+		assertEquals(unblamed.resultStart, c.regionList.resultStart);
+		assertEquals(unblamed.sourceStart, c.regionList.sourceStart);
+		assertEquals(10, c.regionList.length);
+		assertNull(c.regionList.next);
+
+		c = blamed.get(1);
+		assertEquals(c.sourceCommit.name(), O2.name());
+		assertEquals(10, c.regionList.resultStart);
+		assertEquals(10, c.regionList.sourceStart);
+		assertEquals(10, c.regionList.length);
+		assertNull(c.regionList.next);
+
+		c = blamed.get(2);
+		assertEquals(c.sourceCommit.name(), O3.name());
+		assertEquals(20, c.regionList.resultStart);
+		assertEquals(20, c.regionList.sourceStart);
+		assertEquals(10, c.regionList.length);
+		assertNull(c.regionList.next);
+	}
+
+	@Test
+	public void blame_fromCandidate() {
+		// We don't use anything from the candidate besides the
+		// regionList
+		Candidate c = new Candidate(null, null, null);
+		c.regionList = new Region(0, 8, 5);
+		c.regionList.next = new Region(22, 22, 4);
+
+		Candidate blamed = blamer.mergeCandidate(c);
+		// Three candidates
+		assertNotNull(blamed);
+		assertNotNull(blamed.queueNext);
+		assertNotNull(blamed.queueNext.queueNext);
+		assertNull(blamed.queueNext.queueNext.queueNext);
+
+		assertEquals(O1.name(), blamed.sourceCommit.name());
+
+		Candidate second = blamed.queueNext;
+		assertEquals(O2.name(), second.sourceCommit.name());
+
+		Candidate third = blamed.queueNext.queueNext;
+		assertEquals(O3.name(), third.sourceCommit.name());
+	}
+
+	@Test
+	public void blame_fromCandidate_twiceCandidateInOutput() {
+		Candidate c = new Candidate(null, null, null);
+		// This produces O1 and O2
+		c.regionList = new Region(0, 8, 5);
+		// This produces O2 and O3
+		c.regionList.next = new Region(20, 15, 7);
+
+		Candidate blamed = blamer.mergeCandidate(c);
+		assertCandidateSingleRegion(O1, 2, blamed);
+		blamed = blamed.queueNext;
+		assertCandidateSingleRegion(O2, 3, blamed);
+		// We do not merge candidates afterwards, so these are
+		// two different candidates to the same source
+		blamed = blamed.queueNext;
+		assertCandidateSingleRegion(O2, 5, blamed);
+		blamed = blamed.queueNext;
+		assertCandidateSingleRegion(O3, 2, blamed);
+		assertNull(blamed.queueNext);
+	}
+
+	private static void assertCandidateSingleRegion(ObjectId expectedOid,
+			int expectedLength, Candidate actual) {
+		assertNotNull("candidate", actual);
+		assertNotNull("region list not empty", actual.regionList);
+		assertNull("region list has only one element", actual.regionList.next);
+		assertEquals(expectedOid, actual.sourceCommit);
+		assertEquals(expectedLength, actual.regionList.length);
+	}
+
+	private static final class BlameRegionMergerFakeCommits
+			extends BlameRegionMerger {
+
+		private final Map<ObjectId, RevCommit> cache;
+
+		BlameRegionMergerFakeCommits(List<RevCommit> commits,
+				List<CacheRegion> blamedRegions) {
+			super(null, null, blamedRegions);
+			cache = commits.stream().collect(Collectors
+					.toMap(RevCommit::toObjectId, Function.identity()));
+		}
+
+		@Override
+		protected RevCommit parse(ObjectId oid) {
+			return cache.get(oid);
+		}
+	}
+
+	private static final class FakeRevCommit extends RevCommit {
+		FakeRevCommit(AnyObjectId id) {
+			super(id);
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index f9fbfe8..00a3760 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -17,10 +17,9 @@
 
 import java.io.IOException;
 import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
@@ -50,7 +49,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.storage.pack.PackConfig;
 import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.util.GitDateParser;
+import org.eclipse.jgit.util.GitTimeParser;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
 import org.junit.Before;
@@ -1294,23 +1293,22 @@ public void gitGCWithRefLogExpire() throws Exception {
 		DfsPackDescription t1 = odb.newPack(INSERT);
 		Ref next = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE,
 				"refs/heads/next", commit0.copy());
-		long currentDay = new Date().getTime();
-		GregorianCalendar cal = new GregorianCalendar(SystemReader
-				.getInstance().getTimeZone(), SystemReader.getInstance()
-				.getLocale());
-		long ten_days_ago = GitDateParser.parse("10 days ago",cal,SystemReader.getInstance()
-				.getLocale()).getTime() ;
-		long twenty_days_ago = GitDateParser.parse("20 days ago",cal,SystemReader.getInstance()
-				.getLocale()).getTime() ;
-		long thirty_days_ago = GitDateParser.parse("30 days ago",cal,SystemReader.getInstance()
-				.getLocale()).getTime() ;;
-		long fifty_days_ago = GitDateParser.parse("50 days ago",cal,SystemReader.getInstance()
-				.getLocale()).getTime() ;
-		PersonIdent who2 = new PersonIdent("J.Author", "authemail", currentDay, -8 * 60);
-		PersonIdent who3 = new PersonIdent("J.Author", "authemail", ten_days_ago, -8 * 60);
-		PersonIdent who4 = new PersonIdent("J.Author", "authemail", twenty_days_ago, -8 * 60);
-		PersonIdent who5 = new PersonIdent("J.Author", "authemail", thirty_days_ago, -8 * 60);
-		PersonIdent who6 = new PersonIdent("J.Author", "authemail", fifty_days_ago, -8 * 60);
+		Instant currentDay = Instant.now();
+		Instant ten_days_ago = GitTimeParser.parseInstant("10 days ago");
+		Instant twenty_days_ago = GitTimeParser.parseInstant("20 days ago");
+		Instant thirty_days_ago = GitTimeParser.parseInstant("30 days ago");
+		Instant fifty_days_ago = GitTimeParser.parseInstant("50 days ago");
+		final ZoneOffset offset = ZoneOffset.ofHours(-8);
+		PersonIdent who2 = new PersonIdent("J.Author", "authemail", currentDay,
+				offset);
+		PersonIdent who3 = new PersonIdent("J.Author", "authemail",
+				ten_days_ago, offset);
+		PersonIdent who4 = new PersonIdent("J.Author", "authemail",
+				twenty_days_ago, offset);
+		PersonIdent who5 = new PersonIdent("J.Author", "authemail",
+				thirty_days_ago, offset);
+		PersonIdent who6 = new PersonIdent("J.Author", "authemail",
+				fifty_days_ago, offset);
 
 		try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
 			ReftableWriter w = new ReftableWriter(out);
@@ -1332,7 +1330,7 @@ public void gitGCWithRefLogExpire() throws Exception {
 		gc = new DfsGarbageCollector(repo);
 		gc.setReftableConfig(new ReftableConfig());
 		// Expire ref log entries older than 30 days
-		gc.setRefLogExpire(Instant.ofEpochMilli(thirty_days_ago));
+		gc.setRefLogExpire(thirty_days_ago);
 		run(gc);
 
 		// Single GC pack present with all objects.
@@ -1360,9 +1358,7 @@ public void gitGCWithRefLogExpire() throws Exception {
 			assertEquals(lc.getRefName(),"refs/heads/branch2");
 			// Old entries are purged
 			assertFalse(lc.next());
-
 		}
-
 	}
 
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
index 1af42cb..a0afc3e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java
@@ -1263,7 +1263,7 @@ private Map<String, ReflogEntry> getLastReflogs(String... names)
 	}
 
 	private ReflogEntry getLastReflog(String name) throws IOException {
-		ReflogReader r = diskRepo.getReflogReader(name);
+		ReflogReader r = diskRepo.getRefDatabase().getReflogReader(name);
 		if (r == null) {
 			return null;
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
index 32342e3..5756b41 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -33,8 +34,15 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -51,6 +59,10 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
 import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.TemporaryBuffer;
 import org.junit.Test;
 
 public class FileReftableTest extends SampleDataRepositoryTestCase {
@@ -66,6 +78,30 @@ public void setUp() throws Exception {
 
 	@SuppressWarnings("boxing")
 	@Test
+	public void testReloadIfNecessary() throws Exception {
+		ObjectId id = db.resolve("master");
+		try (FileRepository repo1 = new FileRepository(db.getDirectory());
+				FileRepository repo2 = new FileRepository(db.getDirectory())) {
+			((FileReftableDatabase) repo1.getRefDatabase())
+					.setAutoRefresh(true);
+			((FileReftableDatabase) repo2.getRefDatabase())
+					.setAutoRefresh(true);
+			FileRepository repos[] = { repo1, repo2 };
+			for (int i = 0; i < 10; i++) {
+				for (int j = 0; j < 2; j++) {
+					FileRepository repo = repos[j];
+					RefUpdate u = repo.getRefDatabase().newUpdate(
+							String.format("branch%d", i * 10 + j), false);
+					u.setNewObjectId(id);
+					RefUpdate.Result r = u.update();
+					assertEquals(Result.NEW, r);
+				}
+			}
+		}
+	}
+
+	@SuppressWarnings("boxing")
+	@Test
 	public void testRacyReload() throws Exception {
 		ObjectId id = db.resolve("master");
 		int retry = 0;
@@ -87,13 +123,61 @@ public void testRacyReload() throws Exception {
 
 						u.setNewObjectId(id);
 						r = u.update();
-						assertEquals(r, Result.NEW);
+						assertEquals(Result.NEW, r);
 					}
 				}
 			}
 
 			// only the first one succeeds
-			assertEquals(retry, 19);
+			assertEquals(19, retry);
+		}
+	}
+
+	@Test
+	public void testConcurrentRacyReload() throws Exception {
+		ObjectId id = db.resolve("master");
+		final CyclicBarrier barrier = new CyclicBarrier(2);
+
+		class UpdateRef implements Callable<RefUpdate.Result> {
+
+			private RefUpdate u;
+
+			UpdateRef(FileRepository repo, String branchName)
+					throws IOException {
+				u = repo.getRefDatabase().newUpdate(branchName,
+						false);
+				u.setNewObjectId(id);
+			}
+
+			@Override
+			public RefUpdate.Result call() throws Exception {
+				barrier.await(); // wait for the other thread to prepare
+				return u.update();
+			}
+		}
+
+		ExecutorService pool = Executors.newFixedThreadPool(2);
+		try (FileRepository repo1 = new FileRepository(db.getDirectory());
+				FileRepository repo2 = new FileRepository(db.getDirectory())) {
+			((FileReftableDatabase) repo1.getRefDatabase())
+					.setAutoRefresh(true);
+			((FileReftableDatabase) repo2.getRefDatabase())
+					.setAutoRefresh(true);
+			for (int i = 0; i < 10; i++) {
+				String branchName = String.format("branch%d",
+						Integer.valueOf(i));
+				Future<RefUpdate.Result> ru1 = pool
+						.submit(new UpdateRef(repo1, branchName));
+				Future<RefUpdate.Result> ru2 = pool
+						.submit(new UpdateRef(repo2, branchName));
+				assertTrue((ru1.get() == Result.NEW
+						&& ru2.get() == Result.LOCK_FAILURE)
+						|| (ru1.get() == Result.LOCK_FAILURE
+								&& ru2.get() == Result.NEW));
+			}
+		} finally {
+			pool.shutdown();
+			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
 		}
 	}
 
@@ -105,13 +189,13 @@ public void testCompactFully() throws Exception {
 			RefUpdate u = db.updateRef("refs/heads/master");
 			u.setForceUpdate(true);
 			u.setNewObjectId((i%2) == 0 ? c1 : c2);
-			assertEquals(u.update(), FORCED);
+			assertEquals(FORCED, u.update());
 		}
 
 		File tableDir = new File(db.getDirectory(), Constants.REFTABLE);
 		assertTrue(tableDir.listFiles().length > 2);
 		((FileReftableDatabase)db.getRefDatabase()).compactFully();
-		assertEquals(tableDir.listFiles().length,2);
+		assertEquals(2, tableDir.listFiles().length);
 	}
 
 	@Test
@@ -171,9 +255,10 @@ public void testConvertToRefdirReflog() throws Exception {
 		v.update();
 
 		db.convertToPackedRefs(true, false);
-		List<ReflogEntry> logs = db.getReflogReader("refs/heads/master").getReverseEntries(2);
-		assertEquals(logs.get(0).getComment(), "banana");
-		assertEquals(logs.get(1).getComment(), "apple");
+		List<ReflogEntry> logs = db.getRefDatabase()
+				.getReflogReader("refs/heads/master").getReverseEntries(2);
+		assertEquals("banana", logs.get(0).getComment());
+		assertEquals("apple", logs.get(1).getComment());
 	}
 
 	@Test
@@ -185,8 +270,9 @@ public void testBatchrefUpdate() throws Exception {
 		ReceiveCommand rc1 = new ReceiveCommand(ObjectId.zeroId(), cur, "refs/heads/batch1");
 		ReceiveCommand rc2 = new ReceiveCommand(ObjectId.zeroId(), prev, "refs/heads/batch2");
 		String msg =  "message";
+		RefDatabase refDb = db.getRefDatabase();
 		try (RevWalk rw = new RevWalk(db)) {
-			db.getRefDatabase().newBatchUpdate()
+			refDb.newBatchUpdate()
 					.addCommand(rc1, rc2)
 					.setAtomic(true)
 					.setRefLogIdent(person)
@@ -194,15 +280,17 @@ public void testBatchrefUpdate() throws Exception {
 					.execute(rw, NullProgressMonitor.INSTANCE);
 		}
 
-		assertEquals(rc1.getResult(), ReceiveCommand.Result.OK);
-		assertEquals(rc2.getResult(), ReceiveCommand.Result.OK);
+		assertEquals(ReceiveCommand.Result.OK, rc1.getResult());
+		assertEquals(ReceiveCommand.Result.OK, rc2.getResult());
 
-		ReflogEntry e = db.getReflogReader("refs/heads/batch1").getLastEntry();
+		ReflogEntry e = refDb.getReflogReader("refs/heads/batch1")
+				.getLastEntry();
 		assertEquals(msg, e.getComment());
 		assertEquals(person, e.getWho());
 		assertEquals(cur, e.getNewId());
 
-		e = db.getReflogReader("refs/heads/batch2").getLastEntry();
+		e = refDb.getReflogReader("refs/heads/batch2")
+				.getLastEntry();
 		assertEquals(msg, e.getComment());
 		assertEquals(person, e.getWho());
 		assertEquals(prev, e.getNewId());
@@ -267,7 +355,7 @@ public void testDelete() throws Exception {
 		RefUpdate up = db.getRefDatabase().newUpdate("refs/heads/a", false);
 		up.setForceUpdate(true);
 		RefUpdate.Result res = up.delete();
-		assertEquals(res, FORCED);
+		assertEquals(FORCED, res);
 		assertNull(db.exactRef("refs/heads/a"));
 	}
 
@@ -309,7 +397,7 @@ public void testUpdateRefDetached() throws Exception {
 
 		// the branch HEAD referred to is left untouched
 		assertEquals(pid, db.resolve("refs/heads/master"));
-		ReflogReader reflogReader = db.getReflogReader("HEAD");
+		ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(ppid, e.getNewId());
 		assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
@@ -330,12 +418,13 @@ public void testWriteReflog() throws Exception {
 		updateRef.setForceUpdate(true);
 		RefUpdate.Result update = updateRef.update();
 		assertEquals(FORCED, update); // internal
-		ReflogReader r = db.getReflogReader("refs/heads/master");
+		ReflogReader r = db.getRefDatabase()
+				.getReflogReader("refs/heads/master");
 
 		ReflogEntry e = r.getLastEntry();
-		assertEquals(e.getNewId(), pid);
-		assertEquals(e.getComment(), "REFLOG!: FORCED");
-		assertEquals(e.getWho(), person);
+		assertEquals(pid, e.getNewId());
+		assertEquals("REFLOG!: FORCED", e.getComment());
+		assertEquals(person, e.getWho());
 	}
 
 	@Test
@@ -352,10 +441,11 @@ public void testLooseDelete() throws IOException {
 		ref = db.updateRef(newRef);
 		ref.setNewObjectId(db.resolve(Constants.HEAD));
 
-		assertEquals(ref.delete(), RefUpdate.Result.NO_CHANGE);
+		assertEquals(RefUpdate.Result.NO_CHANGE, ref.delete());
 
 		// Differs from RefupdateTest. Deleting a loose ref leaves reflog trail.
-		ReflogReader reader = db.getReflogReader("refs/heads/abc");
+		ReflogReader reader = db.getRefDatabase()
+				.getReflogReader("refs/heads/abc");
 		assertEquals(ObjectId.zeroId(), reader.getReverseEntry(1).getOldId());
 		assertEquals(nonZero, reader.getReverseEntry(1).getNewId());
 		assertEquals(nonZero, reader.getReverseEntry(0).getOldId());
@@ -382,8 +472,9 @@ public void testNoCacheObjectIdSubclass() throws IOException {
 		assertNotSame(newid, r.getObjectId());
 		assertSame(ObjectId.class, r.getObjectId().getClass());
 		assertEquals(newid, r.getObjectId());
-		List<ReflogEntry> reverseEntries1 = db.getReflogReader("refs/heads/abc")
-				.getReverseEntries();
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> reverseEntries1 = refDb
+				.getReflogReader("refs/heads/abc").getReverseEntries();
 		ReflogEntry entry1 = reverseEntries1.get(0);
 		assertEquals(1, reverseEntries1.size());
 		assertEquals(ObjectId.zeroId(), entry1.getOldId());
@@ -392,7 +483,7 @@ public void testNoCacheObjectIdSubclass() throws IOException {
 		assertEquals(new PersonIdent(db).toString(),
 				entry1.getWho().toString());
 		assertEquals("", entry1.getComment());
-		List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
+		List<ReflogEntry> reverseEntries2 = refDb.getReflogReader("HEAD")
 				.getReverseEntries();
 		assertEquals(0, reverseEntries2.size());
 	}
@@ -431,7 +522,7 @@ public void writeUnbornHead() throws Exception {
 
 		Ref head = db.exactRef("HEAD");
 		assertTrue(head.isSymbolic());
-		assertEquals(head.getTarget().getName(), "refs/heads/unborn");
+		assertEquals("refs/heads/unborn", head.getTarget().getName());
 	}
 
 	/**
@@ -455,7 +546,7 @@ public void testUpdateRefDetachedUnbornHead() throws Exception {
 
 		// the branch HEAD referred to is left untouched
 		assertNull(db.resolve("refs/heads/unborn"));
-		ReflogReader reflogReader = db.getReflogReader("HEAD");
+		ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(ObjectId.zeroId(), e.getOldId());
 		assertEquals(ppid, e.getNewId());
@@ -499,7 +590,7 @@ public void testRenameCurrentBranch() throws IOException {
 		names.add("refs/heads/new/name");
 
 		for (String nm : names) {
-			ReflogReader rd = db.getReflogReader(nm);
+			ReflogReader rd = db.getRefDatabase().getReflogReader(nm);
 			assertNotNull(rd);
 			ReflogEntry last = rd.getLastEntry();
 			ObjectId id = last.getNewId();
@@ -573,10 +664,10 @@ public void compactFully() throws Exception {
 			assertTrue(res == Result.NEW || res == FORCED);
 		}
 
-		assertEquals(refDb.exactRef(refName).getObjectId(), bId);
+		assertEquals(bId, refDb.exactRef(refName).getObjectId());
 		assertTrue(randomStr.equals(refDb.getReflogReader(refName).getReverseEntry(1).getComment()));
 		refDb.compactFully();
-		assertEquals(refDb.exactRef(refName).getObjectId(), bId);
+		assertEquals(bId, refDb.exactRef(refName).getObjectId());
 		assertTrue(randomStr.equals(refDb.getReflogReader(refName).getReverseEntry(1).getComment()));
 	}
 
@@ -644,6 +735,54 @@ public void testGetRefsWithPrefixExcludingOverlappingPrefixes() throws IOExcepti
 		checkContainsRef(refs, db.exactRef("HEAD"));
 	}
 
+	@Test
+	public void testExternalUpdate_bug_102() throws Exception {
+		((FileReftableDatabase) db.getRefDatabase()).setAutoRefresh(true);
+		assumeTrue(atLeastGitVersion(2, 45));
+		Git git = Git.wrap(db);
+		git.tag().setName("foo").call();
+		Ref ref = db.exactRef("refs/tags/foo");
+		assertNotNull(ref);
+		runGitCommand("tag", "--force", "foo", "e");
+		Ref e = db.exactRef("refs/heads/e");
+		Ref foo = db.exactRef("refs/tags/foo");
+		assertEquals(e.getObjectId(), foo.getObjectId());
+	}
+
+	private String toString(TemporaryBuffer b) throws IOException {
+		return RawParseUtils.decode(b.toByteArray());
+	}
+
+	private ExecutionResult runGitCommand(String... args)
+			throws IOException, InterruptedException {
+		FS fs = db.getFS();
+		ProcessBuilder pb = fs.runInShell("git", args);
+		pb.directory(db.getWorkTree());
+		System.err.println("PATH=" + pb.environment().get("PATH"));
+		ExecutionResult result = fs.execute(pb, null);
+		assertEquals(0, result.getRc());
+		String err = toString(result.getStderr());
+		if (!err.isEmpty()) {
+			System.err.println(err);
+		}
+		String out = toString(result.getStdout());
+		if (!out.isEmpty()) {
+			System.out.println(out);
+		}
+		return result;
+	}
+
+	private boolean atLeastGitVersion(int minMajor, int minMinor)
+			throws IOException, InterruptedException {
+		String version = toString(runGitCommand("version").getStdout())
+				.split(" ")[2];
+		System.out.println(version);
+		String[] digits = version.split("\\.");
+		int major = Integer.parseInt(digits[0]);
+		int minor = Integer.parseInt(digits[1]);
+		return (major >= minMajor) && (minor >= minMinor);
+	}
+
 	private RefUpdate updateRef(String name) throws IOException {
 		final RefUpdate ref = db.updateRef(name);
 		ref.setNewObjectId(db.resolve(Constants.HEAD));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
index 6cad8b6..434f7e4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -16,9 +16,9 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 
 import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
@@ -206,7 +206,7 @@ public void testDonePruneTooYoungPacks() throws Exception {
 
 		// The old packfile is too young to be deleted. We should end up with
 		// two pack files
-		gc.setExpire(new Date(oldPackfile.lastModified() - 1));
+		gc.setExpire(Instant.ofEpochMilli(oldPackfile.lastModified() - 1));
 		gc.gc().get();
 		stats = gc.getStatistics();
 		assertEquals(0, stats.numberOfLooseObjects);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
index ca0f684..84ec132 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
@@ -16,8 +16,8 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
+import java.time.Instant;
 import java.util.Collections;
-import java.util.Date;
 
 import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
 import org.eclipse.jgit.lib.ObjectId;
@@ -30,7 +30,7 @@ public class GcPruneNonReferencedTest extends GcTestCase {
 	@Test
 	public void nonReferencedNonExpiredObject_notPruned() throws Exception {
 		RevBlob a = tr.blob("a");
-		gc.setExpire(new Date(lastModified(a)));
+		gc.setExpire(Instant.ofEpochMilli(lastModified(a)));
 		gc.prune(Collections.<ObjectId> emptySet());
 		assertTrue(repo.getObjectDatabase().has(a));
 	}
@@ -58,7 +58,7 @@ public void nonReferencedExpiredObjectTree_pruned() throws Exception {
 	@Test
 	public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
 		RevBlob a = tr.blob("a");
-		gc.setExpire(new Date(lastModified(a) + 1));
+		gc.setExpire(Instant.ofEpochMilli(lastModified(a) + 1));
 
 		fsTick();
 		RevBlob b = tr.blob("b");
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index d1342c0..33cbc86 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -49,7 +49,10 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import java.io.File;
@@ -66,6 +69,7 @@
 
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
@@ -207,33 +211,35 @@ public void testOpenLooseObjectSuppressStaleFileHandleException()
 				.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b");
 		WindowCursor curs = new WindowCursor(db.getObjectDatabase());
 
-		LooseObjects mock = mock(LooseObjects.class);
+		Config config = new Config();
+		config.setString("core", null, "trustLooseObjectStat", "ALWAYS");
+		LooseObjects spy = Mockito.spy(new LooseObjects(config, trash));
 		UnpackedObjectCache unpackedObjectCacheMock = mock(
 				UnpackedObjectCache.class);
 
-		Mockito.when(mock.getObjectLoader(any(), any(), any()))
-				.thenThrow(new IOException("Stale File Handle"));
-		Mockito.when(mock.open(curs, id)).thenCallRealMethod();
-		Mockito.when(mock.unpackedObjectCache())
-				.thenReturn(unpackedObjectCacheMock);
+		doThrow(new IOException("Stale File Handle")).when(spy)
+				.getObjectLoader(any(), any(), any());
+		doReturn(unpackedObjectCacheMock).when(spy).unpackedObjectCache();
 
-		assertNull(mock.open(curs, id));
+		assertNull(spy.open(curs, id));
 		verify(unpackedObjectCacheMock).remove(id);
 	}
 
-	@Test
+	@Test(expected = IOException.class)
 	public void testOpenLooseObjectPropagatesIOExceptions() throws Exception {
 		ObjectId id = ObjectId
 				.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b");
 		WindowCursor curs = new WindowCursor(db.getObjectDatabase());
 
-		LooseObjects mock = mock(LooseObjects.class);
+		Config config = new Config();
+		config.setString("core", null, "trustLooseObjectStat", "NEVER");
+		LooseObjects spy = spy(new LooseObjects(config,
+				db.getObjectDatabase().getDirectory()));
 
-		Mockito.when(mock.getObjectLoader(any(), any(), any()))
-				.thenThrow(new IOException("some IO failure"));
-		Mockito.when(mock.open(curs, id)).thenCallRealMethod();
+		doThrow(new IOException("some IO failure")).when(spy)
+				.getObjectLoader(any(), any(), any());
 
-		assertThrows(IOException.class, () -> mock.open(curs, id));
+		spy.open(curs, id);
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
index 24bdc4a..1f934ac 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
@@ -13,6 +13,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.File;
@@ -25,6 +26,7 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
 
@@ -99,6 +101,39 @@ public void testIteratorMethodsContract() {
 		}
 	}
 
+	@Test
+	public void testIteratorMutableEntryCompareTo() {
+		Iterator<PackIndex.MutableEntry> iterA = smallIdx.iterator();
+		Iterator<PackIndex.MutableEntry> iterB = smallIdx.iterator();
+
+		MutableEntry aEntry = iterA.next();
+		iterB.next();
+		MutableEntry bEntry = iterB.next();
+		// b is one ahead
+		assertTrue(aEntry.compareBySha1To(bEntry) < 0);
+		assertTrue(bEntry.compareBySha1To(aEntry) > 0);
+
+		// advance a, now should be equal
+		assertEquals(0, iterA.next().compareBySha1To(bEntry));
+	}
+
+	@Test
+	public void testIteratorMutableEntryCopyTo() {
+		Iterator<PackIndex.MutableEntry> it = smallIdx.iterator();
+
+		MutableObjectId firstOidCopy = new MutableObjectId();
+		MutableEntry next = it.next();
+		next.copyOidTo(firstOidCopy);
+		ObjectId firstImmutable = next.toObjectId();
+
+		MutableEntry second = it.next();
+
+		// The copy has the right value after "next"
+		assertTrue(firstImmutable.equals(firstOidCopy));
+		assertFalse("iterator has moved",
+				second.toObjectId().equals(firstImmutable));
+	}
+
 	/**
 	 * Test results of iterator comparing to content of well-known (prepared)
 	 * small index.
@@ -106,22 +141,22 @@ public void testIteratorMethodsContract() {
 	@Test
 	public void testIteratorReturnedValues1() {
 		Iterator<PackIndex.MutableEntry> iter = smallIdx.iterator();
-		assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", iter.next()
-				.name());
-		assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab", iter.next()
-				.name());
-		assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259", iter.next()
-				.name());
-		assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3", iter.next()
-				.name());
-		assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7", iter.next()
-				.name());
-		assertEquals("902d5476fa249b7abc9d84c611577a81381f0327", iter.next()
-				.name());
-		assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035", iter.next()
-				.name());
-		assertEquals("c59759f143fb1fe21c197981df75a7ee00290799", iter.next()
-				.name());
+		assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904",
+				iter.next().name());
+		assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab",
+				iter.next().name());
+		assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259",
+				iter.next().name());
+		assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3",
+				iter.next().name());
+		assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7",
+				iter.next().name());
+		assertEquals("902d5476fa249b7abc9d84c611577a81381f0327",
+				iter.next().name());
+		assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035",
+				iter.next().name());
+		assertEquals("c59759f143fb1fe21c197981df75a7ee00290799",
+				iter.next().name());
 		assertFalse(iter.hasNext());
 	}
 
@@ -198,16 +233,16 @@ public void testCompareEntriesOffsetsWithGetOffsets() {
 	@Test
 	public void testIteratorReturnedValues2() {
 		Iterator<PackIndex.MutableEntry> iter = denseIdx.iterator();
-		while (!iter.next().name().equals(
-				"0a3d7772488b6b106fb62813c4d6d627918d9181")) {
+		while (!iter.next().name()
+				.equals("0a3d7772488b6b106fb62813c4d6d627918d9181")) {
 			// just iterating
 		}
-		assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3", iter.next()
-				.name()); // same level-1
-		assertEquals("10da5895682013006950e7da534b705252b03be6", iter.next()
-				.name()); // same level-1
-		assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8", iter.next()
-				.name());
+		assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3",
+				iter.next().name()); // same level-1
+		assertEquals("10da5895682013006950e7da534b705252b03be6",
+				iter.next().name()); // same level-1
+		assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8",
+				iter.next().name());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index cb977bd..acc36d7 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -40,6 +40,7 @@
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefRename;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -111,16 +112,17 @@ public void testNoCacheObjectIdSubclass() throws IOException {
 		assertNotSame(newid, r.getObjectId());
 		assertSame(ObjectId.class, r.getObjectId().getClass());
 		assertEquals(newid, r.getObjectId());
-		List<ReflogEntry> reverseEntries1 = db
+		List<ReflogEntry> reverseEntries1 = db.getRefDatabase()
 				.getReflogReader("refs/heads/abc").getReverseEntries();
 		ReflogEntry entry1 = reverseEntries1.get(0);
 		assertEquals(1, reverseEntries1.size());
 		assertEquals(ObjectId.zeroId(), entry1.getOldId());
 		assertEquals(r.getObjectId(), entry1.getNewId());
-		assertEquals(new PersonIdent(db).toString(),  entry1.getWho().toString());
+		assertEquals(new PersonIdent(db).toString(),
+				entry1.getWho().toString());
 		assertEquals("", entry1.getComment());
-		List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
-				.getReverseEntries();
+		List<ReflogEntry> reverseEntries2 = db.getRefDatabase()
+				.getReflogReader("HEAD").getReverseEntries();
 		assertEquals(0, reverseEntries2.size());
 	}
 
@@ -136,8 +138,11 @@ public void testNewNamespaceConflictWithLoosePrefixNameExists()
 		final RefUpdate ru2 = updateRef(newRef2);
 		Result update2 = ru2.update();
 		assertEquals(Result.LOCK_FAILURE, update2);
-		assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(1, refDb.getReflogReader("refs/heads/z")
+				.getReverseEntries().size());
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	@Test
@@ -147,8 +152,10 @@ public void testNewNamespaceConflictWithPackedPrefixNameExists()
 		final RefUpdate ru = updateRef(newRef);
 		Result update = ru.update();
 		assertEquals(Result.LOCK_FAILURE, update);
-		assertNull(db.getReflogReader("refs/heads/master/x"));
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertNull(refDb.getReflogReader("refs/heads/master/x"));
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	@Test
@@ -163,9 +170,12 @@ public void testNewNamespaceConflictWithLoosePrefixOfExisting()
 		final RefUpdate ru2 = updateRef(newRef2);
 		Result update2 = ru2.update();
 		assertEquals(Result.LOCK_FAILURE, update2);
-		assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
-		assertNull(db.getReflogReader("refs/heads/z"));
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(1, refDb.getReflogReader("refs/heads/z/a")
+				.getReverseEntries().size());
+		assertNull(refDb.getReflogReader("refs/heads/z"));
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	@Test
@@ -175,8 +185,10 @@ public void testNewNamespaceConflictWithPackedPrefixOfExisting()
 		final RefUpdate ru = updateRef(newRef);
 		Result update = ru.update();
 		assertEquals(Result.LOCK_FAILURE, update);
-		assertNull(db.getReflogReader("refs/heads/prefix"));
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertNull(refDb.getReflogReader("refs/heads/prefix"));
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	/**
@@ -197,8 +209,11 @@ public void testDeleteHEADreferencedRef() throws IOException {
 		Result delete = updateRef2.delete();
 		assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
 		assertEquals(pid, db.resolve("refs/heads/master"));
-		assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
-		assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(1, refDb.getReflogReader("refs/heads/master")
+				.getReverseEntries().size());
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	@Test
@@ -209,7 +224,8 @@ public void testWriteReflog() throws IOException {
 		updateRef.setForceUpdate(true);
 		Result update = updateRef.update();
 		assertEquals(Result.FORCED, update);
-		assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
+		assertEquals(1, db.getRefDatabase().getReflogReader("refs/heads/master")
+				.getReverseEntries().size());
 	}
 
 	@Test
@@ -219,15 +235,18 @@ public void testLooseDelete() throws IOException {
 		ref.update(); // create loose ref
 		ref = updateRef(newRef); // refresh
 		delete(ref, Result.NO_CHANGE);
-		assertNull(db.getReflogReader("refs/heads/abc"));
+		assertNull(db.getRefDatabase().getReflogReader("refs/heads/abc"));
 	}
 
 	@Test
 	public void testDeleteHead() throws IOException {
 		final RefUpdate ref = updateRef(Constants.HEAD);
 		delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
-		assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(0, refDb.getReflogReader("refs/heads/master")
+				.getReverseEntries().size());
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 	}
 
 	@Test
@@ -423,7 +442,7 @@ public void testUpdateRefDetached() throws Exception {
 
 		// the branch HEAD referred to is left untouched
 		assertEquals(pid, db.resolve("refs/heads/master"));
-		ReflogReader reflogReader = db.getReflogReader("HEAD");
+		ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(pid, e.getOldId());
 		assertEquals(ppid, e.getNewId());
@@ -453,7 +472,7 @@ public void testUpdateRefDetachedUnbornHead() throws Exception {
 
 		// the branch HEAD referred to is left untouched
 		assertNull(db.resolve("refs/heads/unborn"));
-		ReflogReader reflogReader = db.getReflogReader("HEAD");
+		ReflogReader reflogReader = db.getRefDatabase().getReflogReader("HEAD");
 		ReflogEntry e = reflogReader.getReverseEntries().get(0);
 		assertEquals(ObjectId.zeroId(), e.getOldId());
 		assertEquals(ppid, e.getNewId());
@@ -691,9 +710,12 @@ public void testRenameBranchNoPreviousLog() throws IOException {
 		assertEquals(Result.RENAMED, result);
 		assertEquals(rb, db.resolve("refs/heads/new/name"));
 		assertNull(db.resolve("refs/heads/b"));
-		assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size());
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
-				.getLastEntry().getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(1, refDb.getReflogReader("refs/heads/new/name")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+						.getComment());
 		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
 		assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
 	}
@@ -713,11 +735,15 @@ public void testRenameBranchHasPreviousLog() throws IOException {
 		assertEquals(Result.RENAMED, result);
 		assertEquals(rb, db.resolve("refs/heads/new/name"));
 		assertNull(db.resolve("refs/heads/b"));
-		assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
-				.getLastEntry().getComment());
-		assertEquals("Just a message", db.getReflogReader("new/name")
-				.getReverseEntries().get(1).getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(2, refDb.getReflogReader("refs/heads/new/name")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+						.getComment());
+		assertEquals("Just a message",
+				refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+						.get(1).getComment());
 		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
 		assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
 	}
@@ -737,13 +763,20 @@ public void testRenameCurrentBranch() throws IOException {
 		assertEquals(Result.RENAMED, result);
 		assertEquals(rb, db.resolve("refs/heads/new/name"));
 		assertNull(db.resolve("refs/heads/b"));
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader(
-				"new/name").getLastEntry().getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+						.getComment());
 		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
 		assertEquals(rb, db.resolve(Constants.HEAD));
-		assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment());
-		assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment());
+		assertEquals(2, refDb.getReflogReader("refs/heads/new/name")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+						.get(0).getComment());
+		assertEquals("Just a message",
+				refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+						.get(1).getComment());
 	}
 
 	@Test
@@ -766,11 +799,17 @@ public void testRenameBranchAlsoInPack() throws IOException {
 		assertEquals(Result.RENAMED, result);
 		assertEquals(rb2, db.resolve("refs/heads/new/name"));
 		assertNull(db.resolve("refs/heads/b"));
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader(
-				"new/name").getLastEntry().getComment());
-		assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
-		assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
-		assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getLastEntry()
+						.getComment());
+		assertEquals(3, refDb.getReflogReader("refs/heads/new/name")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed b to new/name",
+				refDb.getReflogReader("refs/heads/new/name").getReverseEntries()
+						.get(0).getComment());
+		assertEquals(0,
+				refDb.getReflogReader("HEAD").getReverseEntries().size());
 		// make sure b's log file is gone too.
 		assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
 
@@ -789,9 +828,10 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 		ObjectId oldfromId = db.resolve(fromName);
 		ObjectId oldHeadId = db.resolve(Constants.HEAD);
 		writeReflog(db, oldfromId, "Just a message", fromName);
-		List<ReflogEntry> oldFromLog = db
+		RefDatabase refDb = db.getRefDatabase();
+		List<ReflogEntry> oldFromLog = refDb
 				.getReflogReader(fromName).getReverseEntries();
-		List<ReflogEntry> oldHeadLog = oldHeadId != null ? db
+		List<ReflogEntry> oldHeadLog = oldHeadId != null ? refDb
 				.getReflogReader(Constants.HEAD).getReverseEntries() : null;
 
 		assertTrue("internal check, we have a log", new File(db.getDirectory(),
@@ -818,10 +858,10 @@ public void tryRenameWhenLocked(String toLock, String fromName,
 			assertEquals(oldHeadId, db.resolve(Constants.HEAD));
 			assertEquals(oldfromId, db.resolve(fromName));
 			assertNull(db.resolve(toName));
-			assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
+			assertEquals(oldFromLog.toString(), refDb.getReflogReader(fromName)
 					.getReverseEntries().toString());
 			if (oldHeadId != null && oldHeadLog != null)
-				assertEquals(oldHeadLog.toString(), db.getReflogReader(
+				assertEquals(oldHeadLog.toString(), refDb.getReflogReader(
 						Constants.HEAD).getReverseEntries().toString());
 		} finally {
 			lockFile.unlock();
@@ -942,15 +982,18 @@ public void testRenameRefNameColission1avoided() throws IOException {
 		assertEquals(Result.RENAMED, result);
 		assertNull(db.resolve("refs/heads/a"));
 		assertEquals(rb, db.resolve("refs/heads/a/b"));
-		assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size());
-		assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b")
-				.getReverseEntries().get(0).getComment());
-		assertEquals("Just a message", db.getReflogReader("a/b")
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(3, refDb.getReflogReader("refs/heads/a/b")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed a to a/b",
+				refDb.getReflogReader("refs/heads/a/b").getReverseEntries()
+						.get(0).getComment());
+		assertEquals("Just a message", refDb.getReflogReader("refs/heads/a/b")
 				.getReverseEntries().get(1).getComment());
-		assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
-				.get(2).getComment());
+		assertEquals("Setup", refDb.getReflogReader("refs/heads/a/b")
+				.getReverseEntries().get(2).getComment());
 		// same thing was logged to HEAD
-		assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
+		assertEquals("Branch: renamed a to a/b", refDb.getReflogReader("HEAD")
 				.getReverseEntries().get(0).getComment());
 	}
 
@@ -978,15 +1021,20 @@ public void testRenameRefNameColission2avoided() throws IOException {
 
 		assertNull(db.resolve("refs/heads/prefix/a"));
 		assertEquals(rb, db.resolve("refs/heads/prefix"));
-		assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size());
-		assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
-				"prefix").getReverseEntries().get(0).getComment());
-		assertEquals("Just a message", db.getReflogReader("prefix")
-				.getReverseEntries().get(1).getComment());
-		assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
-				.get(2).getComment());
-		assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
-				"HEAD").getReverseEntries().get(0).getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(3, refDb.getReflogReader("refs/heads/prefix")
+				.getReverseEntries().size());
+		assertEquals("Branch: renamed prefix/a to prefix",
+				refDb.getReflogReader("refs/heads/prefix").getReverseEntries()
+						.get(0).getComment());
+		assertEquals("Just a message",
+				refDb.getReflogReader("refs/heads/prefix").getReverseEntries()
+						.get(1).getComment());
+		assertEquals("Setup", refDb.getReflogReader("refs/heads/prefix")
+				.getReverseEntries().get(2).getComment());
+		assertEquals("Branch: renamed prefix/a to prefix",
+				refDb.getReflogReader("HEAD").getReverseEntries().get(0)
+						.getComment());
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
index eb521ff..16645cb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
@@ -27,6 +27,7 @@
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.ReflogEntry;
 import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
@@ -154,18 +155,22 @@ public void testReadRightLog() throws Exception {
 		setupReflog("logs/refs/heads/a", aLine);
 		setupReflog("logs/refs/heads/master", masterLine);
 		setupReflog("logs/HEAD", headLine);
-		assertEquals("branch: change to master", db.getReflogReader("master")
-				.getLastEntry().getComment());
-		assertEquals("branch: change to a", db.getReflogReader("a")
-				.getLastEntry().getComment());
-		assertEquals("branch: change to HEAD", db.getReflogReader("HEAD")
-				.getLastEntry().getComment());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals("branch: change to master",
+				refDb.getReflogReader("refs/heads/master").getLastEntry()
+						.getComment());
+		assertEquals("branch: change to a",
+				refDb.getReflogReader("refs/heads/a").getLastEntry()
+						.getComment());
+		assertEquals("branch: change to HEAD",
+				refDb.getReflogReader("HEAD").getLastEntry().getComment());
 	}
 
 	@Test
 	public void testReadLineWithMissingComment() throws Exception {
 		setupReflog("logs/refs/heads/master", oneLineWithoutComment);
-		final ReflogReader reader = db.getReflogReader("master");
+		final ReflogReader reader = db.getRefDatabase()
+				.getReflogReader("refs/heads/master");
 		ReflogEntry e = reader.getLastEntry();
 		assertEquals(ObjectId
 				.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e
@@ -183,15 +188,18 @@ public void testReadLineWithMissingComment() throws Exception {
 
 	@Test
 	public void testNoLog() throws Exception {
-		assertEquals(0, db.getReflogReader("master").getReverseEntries().size());
-		assertNull(db.getReflogReader("master").getLastEntry());
+		RefDatabase refDb = db.getRefDatabase();
+		assertEquals(0,
+				refDb.getReflogReader("refs/heads/master").getReverseEntries()
+						.size());
+		assertNull(refDb.getReflogReader("refs/heads/master").getLastEntry());
 	}
 
 	@Test
 	public void testCheckout() throws Exception {
 		setupReflog("logs/HEAD", switchBranch);
-		List<ReflogEntry> entries = db.getReflogReader(Constants.HEAD)
-				.getReverseEntries();
+		List<ReflogEntry> entries = db.getRefDatabase()
+				.getReflogReader(Constants.HEAD).getReverseEntries();
 		assertEquals(1, entries.size());
 		ReflogEntry entry = entries.get(0);
 		CheckoutEntry checkout = entry.parseCheckout();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
index 8e9b7b8..a836333 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogWriterTest.java
@@ -16,6 +16,8 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneOffset;
 
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -32,7 +34,7 @@ public void shouldFilterLineFeedFromMessage() throws Exception {
 		ReflogWriter writer =
 				new ReflogWriter((RefDirectory) db.getRefDatabase());
 		PersonIdent ident = new PersonIdent("John Doe", "john@doe.com",
-				1243028200000L, 120);
+				Instant.ofEpochMilli(1243028200000L), ZoneOffset.ofHours(2));
 		ObjectId oldId = ObjectId
 				.fromString("da85355dfc525c9f6f3927b876f379f46ccf826e");
 		ObjectId newId = ObjectId
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
new file mode 100644
index 0000000..82f3eb1
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_LARGEOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_REVINDEX;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.FakeIndexFactory.IndexObject;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.util.NB;
+import org.junit.Test;
+
+public class MultiPackIndexWriterTest {
+
+	@Test
+	public void write_allSmallOffsets() throws IOException {
+		PackIndex index1 = indexOf(
+				object("0000000000000000000000000000000000000001", 500),
+				object("0000000000000000000000000000000000000003", 1500),
+				object("0000000000000000000000000000000000000005", 3000));
+		PackIndex index2 = indexOf(
+				object("0000000000000000000000000000000000000002", 500),
+				object("0000000000000000000000000000000000000004", 1500),
+				object("0000000000000000000000000000000000000006", 3000));
+
+		Map<String, PackIndex> data = Map.of("packname1", index1, "packname2",
+				index2);
+
+		MultiPackIndexWriter writer = new MultiPackIndexWriter();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
+		// header (12 bytes)
+		// + chunkHeader (6 * 12 bytes)
+		// + fanout table (256 * 4 bytes)
+		// + OIDs (6 * 20 bytes)
+		// + (pack, offset) pairs (6 * 8)
+		// + RIDX (6 * 4 bytes)
+		// + packfile names (2 * 10)
+		// + checksum (20)
+		assertEquals(1340, out.size());
+		List<Integer> chunkIds = readChunkIds(out);
+		assertEquals(5, chunkIds.size());
+		assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+		assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+		assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+		assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+		assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+	}
+
+	@Test
+	public void write_smallOffset_limit() throws IOException {
+		PackIndex index1 = indexOf(
+				object("0000000000000000000000000000000000000001", 500),
+				object("0000000000000000000000000000000000000003", 1500),
+				object("0000000000000000000000000000000000000005", (1L << 32) -1));
+		PackIndex index2 = indexOf(
+				object("0000000000000000000000000000000000000002", 500),
+				object("0000000000000000000000000000000000000004", 1500),
+				object("0000000000000000000000000000000000000006", 3000));
+		Map<String, PackIndex> data =
+				Map.of("packname1", index1, "packname2", index2);
+
+		MultiPackIndexWriter writer = new MultiPackIndexWriter();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
+		// header (12 bytes)
+		// + chunkHeader (6 * 12 bytes)
+		// + fanout table (256 * 4 bytes)
+		// + OIDs (6 * 20 bytes)
+		// + (pack, offset) pairs (6 * 8)
+		// + RIDX (6 * 4 bytes)
+		// + packfile names (2 * 10)
+		// + checksum (20)
+		assertEquals(1340, out.size());
+		List<Integer> chunkIds = readChunkIds(out);
+		assertEquals(5, chunkIds.size());
+		assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+		assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+		assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+		assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+		assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+	}
+
+	@Test
+	public void write_largeOffset() throws IOException {
+		PackIndex index1 = indexOf(
+				object("0000000000000000000000000000000000000001", 500),
+				object("0000000000000000000000000000000000000003", 1500),
+				object("0000000000000000000000000000000000000005", 1L << 32));
+		PackIndex index2 = indexOf(
+				object("0000000000000000000000000000000000000002", 500),
+				object("0000000000000000000000000000000000000004", 1500),
+				object("0000000000000000000000000000000000000006", 3000));
+		Map<String, PackIndex> data =
+				Map.of("packname1", index1, "packname2", index2);
+
+		MultiPackIndexWriter writer = new MultiPackIndexWriter();
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
+		// header (12 bytes)
+		// + chunkHeader (7 * 12 bytes)
+		// + fanout table (256 * 4 bytes)
+		// + OIDs (6 * 20 bytes)
+		// + (pack, offset) pairs (6 * 8)
+		// + (large-offset) (1 * 8)
+		// + RIDX (6 * 4 bytes)
+		// + packfile names (2 * 10)
+		// + checksum (20)
+		assertEquals(1360, out.size());
+		List<Integer> chunkIds = readChunkIds(out);
+		assertEquals(6, chunkIds.size());
+		assertEquals(0, chunkIds.indexOf(MIDX_CHUNKID_OIDFANOUT));
+		assertEquals(1, chunkIds.indexOf(MIDX_CHUNKID_OIDLOOKUP));
+		assertEquals(2, chunkIds.indexOf(MIDX_CHUNKID_OBJECTOFFSETS));
+		assertEquals(3, chunkIds.indexOf(MIDX_CHUNKID_LARGEOFFSETS));
+		assertEquals(4, chunkIds.indexOf(MIDX_CHUNKID_REVINDEX));
+		assertEquals(5, chunkIds.indexOf(MIDX_CHUNKID_PACKNAMES));
+	}
+
+	private List<Integer> readChunkIds(ByteArrayOutputStream out) {
+		List<Integer> chunkIds = new ArrayList<>();
+		byte[] raw = out.toByteArray();
+		int numChunks = raw[6];
+		int position = 12;
+		for (int i = 0; i < numChunks; i++) {
+			chunkIds.add(NB.decodeInt32(raw, position));
+			position += CHUNK_LOOKUP_WIDTH;
+		}
+		return chunkIds;
+	}
+
+	private static PackIndex indexOf(IndexObject... objs) {
+		return FakeIndexFactory.indexOf(Arrays.asList(objs));
+	}
+
+	private static IndexObject object(String name, long offset) {
+		return new IndexObject(name, offset);
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
new file mode 100644
index 0000000..1d8bde0
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.junit.FakeIndexFactory.IndexObject;
+import org.junit.Test;
+
+public class PackIndexMergerTest {
+
+	@Test
+	public void rawIterator_noDuplicates() {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000001", 500),
+				oidOffset("0000000000000000000000000000000000000005", 12),
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndex idxTwo = indexOf(
+				oidOffset("0000000000000000000000000000000000000002", 501),
+				oidOffset("0000000000000000000000000000000000000003", 13),
+				oidOffset("0000000000000000000000000000000000000015", 1501));
+		PackIndex idxThree = indexOf(
+				oidOffset("0000000000000000000000000000000000000004", 502),
+				oidOffset("0000000000000000000000000000000000000007", 14),
+				oidOffset("0000000000000000000000000000000000000012", 1502));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		assertEquals(9, merger.getUniqueObjectCount());
+		assertEquals(3, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
+		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
+		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 502);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 14);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+				1502);
+		assertNextEntry(it, "0000000000000000000000000000000000000015", 1,
+				1501);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void rawIterator_allDuplicates() {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000001", 500),
+				oidOffset("0000000000000000000000000000000000000005", 12),
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idxOne, "p2", idxOne, "p3", idxOne));
+		assertEquals(3, merger.getUniqueObjectCount());
+		assertEquals(3, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 1, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 2, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 1, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 2, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 1,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 2,
+				1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void bySha1Iterator_noDuplicates() {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000001", 500),
+				oidOffset("0000000000000000000000000000000000000005", 12),
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndex idxTwo = indexOf(
+				oidOffset("0000000000000000000000000000000000000002", 501),
+				oidOffset("0000000000000000000000000000000000000003", 13),
+				oidOffset("0000000000000000000000000000000000000015", 1501));
+		PackIndex idxThree = indexOf(
+				oidOffset("0000000000000000000000000000000000000004", 502),
+				oidOffset("0000000000000000000000000000000000000007", 14),
+				oidOffset("0000000000000000000000000000000000000012", 1502));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		assertEquals(9, merger.getUniqueObjectCount());
+		assertEquals(3, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
+		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
+		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 502);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 14);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+				1502);
+		assertNextEntry(it, "0000000000000000000000000000000000000015", 1,
+				1501);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void bySha1Iterator_allDuplicates() {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000001", 500),
+				oidOffset("0000000000000000000000000000000000000005", 12),
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idxOne, "p2", idxOne, "p3", idxOne));
+		assertEquals(3, merger.getUniqueObjectCount());
+		assertEquals(3, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void bySha1Iterator_differentIndexSizes() {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndex idxTwo = indexOf(
+				oidOffset("0000000000000000000000000000000000000002", 500),
+				oidOffset("0000000000000000000000000000000000000003", 12));
+		PackIndex idxThree = indexOf(
+				oidOffset("0000000000000000000000000000000000000004", 500),
+				oidOffset("0000000000000000000000000000000000000007", 12),
+				oidOffset("0000000000000000000000000000000000000012", 1500));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		assertEquals(6, merger.getUniqueObjectCount());
+		assertEquals(3, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+				1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void merger_noIndexes() {
+		PackIndexMerger merger = new PackIndexMerger(Map.of());
+		assertEquals(0, merger.getUniqueObjectCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		assertTrue(merger.getPackNames().isEmpty());
+		assertEquals(0, merger.getPackCount());
+		assertFalse(merger.bySha1Iterator().hasNext());
+	}
+
+	@Test
+	public void merger_emptyIndexes() {
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", indexOf(), "p2", indexOf()));
+		assertEquals(0, merger.getUniqueObjectCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		assertEquals(2, merger.getPackNames().size());
+		assertEquals(2, merger.getPackCount());
+		assertFalse(merger.bySha1Iterator().hasNext());
+	}
+
+	@Test
+	public void bySha1Iterator_largeOffsets_needsChunk() {
+		PackIndex idx1 = indexOf(
+				oidOffset("0000000000000000000000000000000000000002", 1L << 32),
+				oidOffset("0000000000000000000000000000000000000004", 12));
+		PackIndex idx2 = indexOf(oidOffset(
+				"0000000000000000000000000000000000000003", (1L << 31) + 10));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idx1, "p2", idx2));
+		assertTrue(merger.needsLargeOffsetsChunk());
+		assertEquals(2, merger.getOffsetsOver31BitsCount());
+		assertEquals(3, merger.getUniqueObjectCount());
+	}
+
+	@Test
+	public void bySha1Iterator_largeOffsets_noChunk() {
+		// If no value is over 2^32-1, then we don't need large offset
+		PackIndex idx1 = indexOf(
+				oidOffset("0000000000000000000000000000000000000002",
+						(1L << 31) + 15),
+				oidOffset("0000000000000000000000000000000000000004", 12));
+		PackIndex idx2 = indexOf(oidOffset(
+				"0000000000000000000000000000000000000003", (1L << 31) + 10));
+		PackIndexMerger merger = new PackIndexMerger(
+				Map.of("p1", idx1, "p2", idx2));
+		assertFalse(merger.needsLargeOffsetsChunk());
+		assertEquals(2, merger.getOffsetsOver31BitsCount());
+		assertEquals(3, merger.getUniqueObjectCount());
+	}
+
+	private static void assertNextEntry(
+			Iterator<PackIndexMerger.MidxMutableEntry> it, String oid,
+			int packId, long offset) {
+		assertTrue(it.hasNext());
+		PackIndexMerger.MidxMutableEntry e = it.next();
+		assertEquals(oid, e.getObjectId().name());
+		assertEquals(packId, e.getPackId());
+		assertEquals(offset, e.getOffset());
+	}
+
+	private static IndexObject oidOffset(String oid, long offset) {
+		return new IndexObject(oid, offset);
+	}
+
+	private static PackIndex indexOf(IndexObject... objs) {
+		return FakeIndexFactory.indexOf(Arrays.asList(objs));
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
new file mode 100644
index 0000000..917288a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.junit.Test;
+
+public class PackIndexPeekIteratorTest {
+    @Test
+    public void next() {
+        PackIndex index1 = indexOf(
+                object("0000000000000000000000000000000000000001", 500),
+                object("0000000000000000000000000000000000000003", 1500),
+                object("0000000000000000000000000000000000000005", 3000));
+        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+        assertEquals("0000000000000000000000000000000000000001", it.next().name());
+        assertEquals("0000000000000000000000000000000000000003", it.next().name());
+        assertEquals("0000000000000000000000000000000000000005", it.next().name());
+        assertNull(it.next());
+    }
+
+    @Test
+    public void peek_doesNotAdvance() {
+        PackIndex index1 = indexOf(
+                object("0000000000000000000000000000000000000001", 500),
+                object("0000000000000000000000000000000000000003", 1500),
+                object("0000000000000000000000000000000000000005", 3000));
+        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+        it.next();
+        assertEquals("0000000000000000000000000000000000000001", it.peek().name());
+        assertEquals("0000000000000000000000000000000000000001", it.peek().name());
+        it.next();
+        assertEquals("0000000000000000000000000000000000000003", it.peek().name());
+        assertEquals("0000000000000000000000000000000000000003", it.peek().name());
+        it.next();
+        assertEquals("0000000000000000000000000000000000000005", it.peek().name());
+        assertEquals("0000000000000000000000000000000000000005", it.peek().name());
+        it.next();
+        assertNull(it.peek());
+        assertNull(it.peek());
+    }
+
+    @Test
+    public void empty() {
+        PackIndex index1 = indexOf();
+        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
+        assertNull(it.next());
+        assertNull(it.peek());
+    }
+
+    private static PackIndex indexOf(FakeIndexFactory.IndexObject... objs) {
+        return FakeIndexFactory.indexOf(Arrays.asList(objs));
+    }
+
+    private static FakeIndexFactory.IndexObject object(String name, long offset) {
+        return new FakeIndexFactory.IndexObject(name, offset);
+    }
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
index ea0d92a..a54002b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
@@ -29,6 +29,8 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -175,7 +177,8 @@ public void hasObjMapRefsSmallTable() throws IOException {
 
 	@Test
 	public void hasObjLogs() throws IOException {
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 		ReftableConfig cfg = new ReftableConfig();
 		cfg.setIndexObjects(false);
@@ -617,7 +620,8 @@ public void invalidReflogWriteOrderUpdateIndex() throws IOException {
 			.setMinUpdateIndex(1)
 			.setMaxUpdateIndex(2)
 			.begin();
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 
 		writer.writeLog(MASTER, 1, who, ObjectId.zeroId(), id(1), msg);
@@ -633,7 +637,8 @@ public void invalidReflogWriteOrderName() throws IOException {
 			.setMinUpdateIndex(1)
 			.setMaxUpdateIndex(1)
 			.begin();
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 
 		writer.writeLog(NEXT, 1, who, ObjectId.zeroId(), id(1), msg);
@@ -647,7 +652,8 @@ public void invalidReflogWriteOrderName() throws IOException {
 	public void withReflog() throws IOException {
 		Ref master = ref(MASTER, 1);
 		Ref next = ref(NEXT, 2);
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 
 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@@ -712,11 +718,14 @@ public void reflogReader() throws IOException {
 		writer.writeRef(master);
 		writer.writeRef(next);
 
-		PersonIdent who1 = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who1 = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		writer.writeLog(MASTER, 3, who1, ObjectId.zeroId(), id(1), "1");
-		PersonIdent who2 = new PersonIdent("Log", "Ger", 1500079710, -8 * 60);
+		PersonIdent who2 = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		writer.writeLog(MASTER, 2, who2, id(1), id(2), "2");
-		PersonIdent who3 = new PersonIdent("Log", "Ger", 1500079711, -8 * 60);
+		PersonIdent who3 = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		writer.writeLog(MASTER, 1, who3, id(2), id(3), "3");
 
 		writer.finish();
@@ -753,7 +762,8 @@ public void allRefs() throws IOException {
 				.setMaxUpdateIndex(1)
 				.setConfig(cfg)
 				.begin();
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 
 		// Fill out the 1st ref block.
 		List<String> names = new ArrayList<>();
@@ -782,7 +792,8 @@ public void allRefs() throws IOException {
 
 	@Test
 	public void reflogSeek() throws IOException {
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochSecond(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 		String msgNext = "test next";
 
@@ -827,7 +838,8 @@ public void reflogSeek() throws IOException {
 
 	@Test
 	public void reflogSeekPrefix() throws IOException {
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 
 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 		ReftableWriter writer = new ReftableWriter(buffer)
@@ -850,7 +862,8 @@ public void reflogSeekPrefix() throws IOException {
 
 	@Test
 	public void onlyReflog() throws IOException {
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		String msg = "test";
 
 		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@@ -916,7 +929,8 @@ public void logScan() throws IOException {
 			writer.writeRef(ref);
 		}
 
-		PersonIdent who = new PersonIdent("Log", "Ger", 1500079709, -8 * 60);
+		PersonIdent who = new PersonIdent("Log", "Ger",
+				Instant.ofEpochMilli(1500079709), ZoneOffset.ofHours(-8));
 		for (Ref ref : refs) {
 			writer.writeLog(ref.getName(), 1, who,
 					ObjectId.zeroId(), ref.getObjectId(),
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
index 450b753..1581d49 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java
@@ -11,6 +11,7 @@
 package org.eclipse.jgit.junit;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -18,7 +19,6 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import java.util.Date;
 import java.util.regex.Pattern;
 
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -199,8 +199,8 @@ public void amendRef() throws Exception {
 		assertEquals(orig.getAuthorIdent(), amended.getAuthorIdent());
 
 		// Committer name/email is the same, but time was incremented.
-		assertEquals(new PersonIdent(orig.getCommitterIdent(), new Date(0)),
-				new PersonIdent(amended.getCommitterIdent(), new Date(0)));
+		assertEquals(new PersonIdent(orig.getCommitterIdent(), EPOCH),
+				new PersonIdent(amended.getCommitterIdent(), EPOCH));
 		assertTrue(orig.getCommitTime() < amended.getCommitTime());
 
 		assertEquals("foo contents", blobAsString(amended, "foo"));
@@ -275,9 +275,9 @@ public void cherryPick() throws Exception {
 		RevCommit toPick = tr.commit()
 				.parent(tr.commit().create()) // Can't cherry-pick root.
 				.author(new PersonIdent("Cherrypick Author", "cpa@example.com",
-						tr.getDate(), tr.getTimeZone()))
+						tr.getInstant(), tr.getTimeZoneId()))
 				.author(new PersonIdent("Cherrypick Committer", "cpc@example.com",
-						tr.getDate(), tr.getTimeZone()))
+						tr.getInstant(), tr.getTimeZoneId()))
 				.message("message to cherry-pick")
 				.add("bar", "bar contents\n")
 				.create();
@@ -294,8 +294,8 @@ public void cherryPick() throws Exception {
 		assertEquals(toPick.getAuthorIdent(), result.getAuthorIdent());
 
 		// Committer name/email matches default, and time was incremented.
-		assertEquals(new PersonIdent(head.getCommitterIdent(), new Date(0)),
-				new PersonIdent(result.getCommitterIdent(), new Date(0)));
+		assertEquals(new PersonIdent(head.getCommitterIdent(), EPOCH),
+				new PersonIdent(result.getCommitterIdent(), EPOCH));
 		assertTrue(toPick.getCommitTime() < result.getCommitTime());
 
 		assertEquals("message to cherry-pick", result.getFullMessage());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index 31940a1..06fee8e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -1636,6 +1636,47 @@ public void testCoreCommitGraphConfig() {
 		assertFalse(config.get(CoreConfig.KEY).enableCommitGraph());
 	}
 
+	@Test
+	public void testGetNoDefaultBoolean() {
+		Config config = new Config();
+		assertNull(config.getBoolean("foo", "bar"));
+		assertNull(config.getBoolean("foo", "bar", "baz"));
+	}
+
+	@Test
+	public void testGetNoDefaultEnum() {
+		Config config = new Config();
+		assertNull(config.getEnum(new TestEnum[] { TestEnum.ONE_TWO }, "foo",
+				"bar", "baz"));
+	}
+
+	@Test
+	public void testGetNoDefaultInt() {
+		Config config = new Config();
+		assertNull(config.getInt("foo", "bar"));
+		assertNull(config.getInt("foo", "bar", "baz"));
+	}
+	@Test
+	public void testGetNoDefaultIntInRange() {
+		Config config = new Config();
+		assertNull(config.getIntInRange("foo", "bar", 1, 5));
+		assertNull(config.getIntInRange("foo", "bar", "baz", 1, 5));
+	}
+
+	@Test
+	public void testGetNoDefaultLong() {
+		Config config = new Config();
+		assertNull(config.getLong("foo", "bar"));
+		assertNull(config.getLong("foo", "bar", "baz"));
+	}
+
+	@Test
+	public void testGetNoDefaultTimeUnit() {
+		Config config = new Config();
+		assertNull(config.getTimeUnit("foo", "bar", "baz",
+				TimeUnit.SECONDS));
+	}
+
 	private static void assertValueRoundTrip(String value)
 			throws ConfigInvalidException {
 		assertValueRoundTrip(value, value);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
index 2b7b6ca..cd98606 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java
@@ -2,7 +2,7 @@
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2013, Robin Stocker <robin@nibor.org> and others
+ * Copyright (C) 2013, 2025 Robin Stocker <robin@nibor.org> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -539,7 +539,7 @@ public void testAssumeUnchanged() throws Exception {
 			assertTrue(diff.getAssumeUnchanged().contains("file3"));
 			assertTrue(diff.getModified().contains("file"));
 
-			git.add().addFilepattern(".").call();
+			git.add().addFilepattern(".").setAll(false).call();
 
 			iterator = new FileTreeIterator(db);
 			diff = new IndexDiff(db, Constants.HEAD, iterator);
@@ -551,6 +551,18 @@ public void testAssumeUnchanged() throws Exception {
 			assertTrue(diff.getAssumeUnchanged().contains("file3"));
 			assertTrue(diff.getChanged().contains("file"));
 			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
+
+			git.add().addFilepattern(".").call();
+
+			iterator = new FileTreeIterator(db);
+			diff = new IndexDiff(db, Constants.HEAD, iterator);
+			diff.diff();
+			assertEquals(1, diff.getAssumeUnchanged().size());
+			assertEquals(0, diff.getModified().size());
+			assertEquals(1, diff.getChanged().size());
+			assertTrue(diff.getAssumeUnchanged().contains("file2"));
+			assertTrue(diff.getChanged().contains("file"));
+			assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders());
 		}
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
index b02f245..85f9612 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDatabaseConflictingNamesTest.java
@@ -71,6 +71,11 @@ public List<Ref> getAdditionalRefs() throws IOException {
 		}
 
 		@Override
+		public ReflogReader getReflogReader(Ref ref) throws IOException {
+			return null;
+		}
+
+		@Override
 		public void create() throws IOException {
 			// Not needed
 		}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index 854180e..a93937e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -16,6 +16,9 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneOffset;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -24,22 +27,23 @@
 public class ReflogConfigTest extends RepositoryTestCase {
 	@Test
 	public void testlogAllRefUpdates() throws Exception {
-		long commitTime = 1154236443000L;
-		int tz = -4 * 60;
+		Instant commitTime = Instant.ofEpochSecond(1154236443L);
+		ZoneOffset tz = ZoneOffset.ofHours(-4);
 
 		// check that there are no entries in the reflog and turn off writing
 		// reflogs
-		assertTrue(db.getReflogReader(Constants.HEAD).getReverseEntries()
+		RefDatabase refDb = db.getRefDatabase();
+		assertTrue(refDb.getReflogReader(Constants.HEAD).getReverseEntries()
 				.isEmpty());
-		final FileBasedConfig cfg = db.getConfig();
+		FileBasedConfig cfg = db.getConfig();
 		cfg.setBoolean("core", null, "logallrefupdates", false);
 		cfg.save();
 
 		// do one commit and check that reflog size is 0: no reflogs should be
 		// written
 		commit("A Commit\n", commitTime, tz);
-		commitTime += 60 * 1000;
-		assertTrue("Reflog for HEAD still contain no entry", db
+		commitTime = commitTime.plus(Duration.ofMinutes(1));
+		assertTrue("Reflog for HEAD still contain no entry", refDb
 				.getReflogReader(Constants.HEAD).getReverseEntries().isEmpty());
 
 		// set the logAllRefUpdates parameter to true and check it
@@ -52,10 +56,10 @@ public void testlogAllRefUpdates() throws Exception {
 
 		// do one commit and check that reflog size is increased to 1
 		commit("A Commit\n", commitTime, tz);
-		commitTime += 60 * 1000;
-		assertTrue(
-				"Reflog for HEAD should contain one entry",
-				db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 1);
+		commitTime = commitTime.plus(Duration.ofMinutes(1));
+		assertTrue("Reflog for HEAD should contain one entry",
+				refDb.getReflogReader(Constants.HEAD).getReverseEntries()
+						.size() == 1);
 
 		// set the logAllRefUpdates parameter to false and check it
 		cfg.setBoolean("core", null, "logallrefupdates", false);
@@ -67,10 +71,10 @@ public void testlogAllRefUpdates() throws Exception {
 
 		// do one commit and check that reflog size is 2
 		commit("A Commit\n", commitTime, tz);
-		commitTime += 60 * 1000;
-		assertTrue(
-				"Reflog for HEAD should contain two entries",
-				db.getReflogReader(Constants.HEAD).getReverseEntries().size() == 2);
+		commitTime = commitTime.plus(Duration.ofMinutes(1));
+		assertTrue("Reflog for HEAD should contain two entries",
+				refDb.getReflogReader(Constants.HEAD).getReverseEntries()
+						.size() == 2);
 
 		// set the logAllRefUpdates parameter to false and check it
 		cfg.setEnum("core", null, "logallrefupdates",
@@ -84,13 +88,13 @@ public void testlogAllRefUpdates() throws Exception {
 		// do one commit and check that reflog size is 3
 		commit("A Commit\n", commitTime, tz);
 		assertTrue("Reflog for HEAD should contain three entries",
-				db.getReflogReader(Constants.HEAD).getReverseEntries()
+				refDb.getReflogReader(Constants.HEAD).getReverseEntries()
 						.size() == 3);
 	}
 
-	private void commit(String commitMsg, long commitTime, int tz)
+	private void commit(String commitMsg, Instant commitTime, ZoneOffset tz)
 			throws IOException {
-		final CommitBuilder commit = new CommitBuilder();
+		CommitBuilder commit = new CommitBuilder();
 		commit.setAuthor(new PersonIdent(author, commitTime, tz));
 		commit.setCommitter(new PersonIdent(committer, commitTime, tz));
 		commit.setMessage(commitMsg);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
index ae811f8..8865ba9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java
@@ -15,6 +15,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.time.Instant;
+import java.time.ZoneOffset;
+
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.junit.RepositoryTestCase;
@@ -162,7 +165,8 @@ private static ObjectId commit(final ObjectInserter odi,
 			final ObjectId[] parentIds) throws Exception {
 		final CommitBuilder c = new CommitBuilder();
 		c.setTreeId(treeB.writeTree(odi));
-		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
+		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor",
+				Instant.ofEpochSecond(1), ZoneOffset.UTC));
 		c.setCommitter(c.getAuthor());
 		c.setParentIds(parentIds);
 		c.setMessage("Tree " + c.getTreeId().name());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java
index f410960..b1998f3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java
@@ -15,6 +15,8 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneOffset;
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.dircache.DirCache;
@@ -357,7 +359,8 @@ private static ObjectId commit(ObjectInserter odi, DirCache treeB,
 			ObjectId[] parentIds) throws Exception {
 		CommitBuilder c = new CommitBuilder();
 		c.setTreeId(treeB.writeTree(odi));
-		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
+		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor",
+				Instant.ofEpochSecond(1), ZoneOffset.UTC));
 		c.setCommitter(c.getAuthor());
 		c.setParentIds(parentIds);
 		c.setMessage("Tree " + c.getTreeId().name());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
index 798aebe..0016adf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
@@ -16,6 +16,8 @@
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneOffset;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -375,7 +377,8 @@ private static ObjectId commit(ObjectInserter odi, DirCache treeB,
 			ObjectId[] parentIds) throws Exception {
 		CommitBuilder c = new CommitBuilder();
 		c.setTreeId(treeB.writeTree(odi));
-		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0));
+		c.setAuthor(new PersonIdent("A U Thor", "a.u.thor",
+				Instant.ofEpochMilli(1L), ZoneOffset.UTC));
 		c.setCommitter(c.getAuthor());
 		c.setParentIds(parentIds);
 		c.setMessage("Tree " + c.getTreeId().name());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
index 6872289..014ff92 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2009, Google Inc. and others
+ * Copyright (C) 2008, 2024 Google Inc. and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -23,7 +23,9 @@
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
-import java.util.TimeZone;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.CommitBuilder;
@@ -94,18 +96,17 @@ public void testParse_NoParents() throws Exception {
 		assertNotNull(cAuthor);
 		assertEquals(authorName, cAuthor.getName());
 		assertEquals(authorEmail, cAuthor.getEmailAddress());
-		assertEquals((long) authorTime * 1000, cAuthor.getWhen().getTime());
-		assertEquals(TimeZone.getTimeZone("GMT" + authorTimeZone),
-				cAuthor.getTimeZone());
+		assertEquals(Instant.ofEpochSecond(authorTime),
+				cAuthor.getWhenAsInstant());
+		assertEquals(ZoneId.of(authorTimeZone), cAuthor.getZoneId());
 
 		final PersonIdent cCommitter = c.getCommitterIdent();
 		assertNotNull(cCommitter);
 		assertEquals(committerName, cCommitter.getName());
 		assertEquals(committerEmail, cCommitter.getEmailAddress());
-		assertEquals((long) committerTime * 1000,
-				cCommitter.getWhen().getTime());
-		assertEquals(TimeZone.getTimeZone("GMT" + committerTimeZone),
-				cCommitter.getTimeZone());
+		assertEquals(Instant.ofEpochSecond(committerTime),
+				cCommitter.getWhenAsInstant());
+		assertEquals(ZoneId.of(committerTimeZone), cCommitter.getZoneId());
 	}
 
 	private RevCommit create(String msg) throws Exception {
@@ -153,9 +154,13 @@ public void testParse_incompleteAuthorAndCommitter() throws Exception {
 			c.parseCanonical(rw, b.toString().getBytes(UTF_8));
 		}
 		assertEquals(
-				new PersonIdent("", "a_u_thor@example.com", 1218123387000L, 7),
+				new PersonIdent("", "a_u_thor@example.com",
+						Instant.ofEpochMilli(1218123387000L),
+						ZoneOffset.ofHoursMinutes(0, 7)),
 				c.getAuthorIdent());
-		assertEquals(new PersonIdent("", "", 1218123390000L, -5),
+		assertEquals(
+				new PersonIdent("", "", Instant.ofEpochMilli(1218123390000L),
+						ZoneOffset.ofHoursMinutes(0, -5)),
 				c.getCommitterIdent());
 	}
 
@@ -408,6 +413,7 @@ public void testParse_NoMessage() throws Exception {
 		final RevCommit c = create(msg);
 		assertEquals(msg, c.getFullMessage());
 		assertEquals(msg, c.getShortMessage());
+		assertEquals(msg, c.getFirstMessageLine());
 	}
 
 	@Test
@@ -415,6 +421,7 @@ public void testParse_OnlyLFMessage() throws Exception {
 		final RevCommit c = create("\n");
 		assertEquals("\n", c.getFullMessage());
 		assertEquals("", c.getShortMessage());
+		assertEquals("", c.getFirstMessageLine());
 	}
 
 	@Test
@@ -423,6 +430,7 @@ public void testParse_ShortLineOnlyNoLF() throws Exception {
 		final RevCommit c = create(shortMsg);
 		assertEquals(shortMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals(shortMsg, c.getFirstMessageLine());
 	}
 
 	@Test
@@ -432,6 +440,7 @@ public void testParse_ShortLineOnlyEndLF() throws Exception {
 		final RevCommit c = create(fullMsg);
 		assertEquals(fullMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals(shortMsg, c.getFirstMessageLine());
 	}
 
 	@Test
@@ -441,6 +450,7 @@ public void testParse_ShortLineOnlyEmbeddedLF() throws Exception {
 		final RevCommit c = create(fullMsg);
 		assertEquals(fullMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals("This is a", c.getFirstMessageLine());
 	}
 
 	@Test
@@ -450,6 +460,7 @@ public void testParse_ShortLineOnlyEmbeddedAndEndingLF() throws Exception {
 		final RevCommit c = create(fullMsg);
 		assertEquals(fullMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals("This is a", c.getFirstMessageLine());
 	}
 
 	@Test
@@ -461,6 +472,7 @@ public void testParse_GitStyleMessage() throws Exception {
 		final RevCommit c = create(fullMsg);
 		assertEquals(fullMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals(shortMsg, c.getFirstMessageLine());
 	}
 
 	@Test
@@ -480,6 +492,7 @@ public void testParse_PublicParseMethod()
 		assertEquals(author, p.getAuthorIdent());
 		assertEquals(committer, p.getCommitterIdent());
 		assertEquals("Test commit", p.getShortMessage());
+		assertEquals("Test commit", p.getFirstMessageLine());
 		assertEquals(src.getMessage(), p.getFullMessage());
 	}
 
@@ -494,6 +507,7 @@ public void testParse_GitStyleMessageWithCRLF() throws Exception {
 		final RevCommit c = create(fullMsg);
 		assertEquals(fullMsg, c.getFullMessage());
 		assertEquals(shortMsg, c.getShortMessage());
+		assertEquals("This fixes a", c.getFirstMessageLine());
 	}
 
 	private static ObjectId id(String str) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
index 81ff4a2..7fece66 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertNull;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Date;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -217,14 +218,132 @@ public void testCommitTimeRevFilter() throws Exception {
 		final RevCommit b = commit(a);
 		tick(100);
 
-		Date since = getDate();
+		Instant since = getInstant();
 		final RevCommit c1 = commit(b);
 		tick(100);
 
 		final RevCommit c2 = commit(b);
 		tick(100);
 
-		Date until = getDate();
+		Instant until = getInstant();
+		final RevCommit d = commit(c1, c2);
+		tick(100);
+
+		final RevCommit e = commit(d);
+
+		{
+			RevFilter after = CommitTimeRevFilter.after(since);
+			assertNotNull(after);
+			rw.setRevFilter(after);
+			markStart(e);
+			assertCommit(e, rw.next());
+			assertCommit(d, rw.next());
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertNull(rw.next());
+		}
+
+		{
+			RevFilter before = CommitTimeRevFilter.before(until);
+			assertNotNull(before);
+			rw.reset();
+			rw.setRevFilter(before);
+			markStart(e);
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertCommit(b, rw.next());
+			assertCommit(a, rw.next());
+			assertNull(rw.next());
+		}
+
+		{
+			RevFilter between = CommitTimeRevFilter.between(since, until);
+			assertNotNull(between);
+			rw.reset();
+			rw.setRevFilter(between);
+			markStart(e);
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertNull(rw.next());
+		}
+	}
+
+	@Test
+	public void testCommitTimeRevFilter_date() throws Exception {
+		// Using deprecated Date api for the commit time rev filter.
+		// Delete this tests when method is removed.
+		final RevCommit a = commit();
+		tick(100);
+
+		final RevCommit b = commit(a);
+		tick(100);
+
+		Date since = Date.from(getInstant());
+		final RevCommit c1 = commit(b);
+		tick(100);
+
+		final RevCommit c2 = commit(b);
+		tick(100);
+
+		Date until = Date.from(getInstant());
+		final RevCommit d = commit(c1, c2);
+		tick(100);
+
+		final RevCommit e = commit(d);
+
+		{
+			RevFilter after = CommitTimeRevFilter.after(since);
+			assertNotNull(after);
+			rw.setRevFilter(after);
+			markStart(e);
+			assertCommit(e, rw.next());
+			assertCommit(d, rw.next());
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertNull(rw.next());
+		}
+
+		{
+			RevFilter before = CommitTimeRevFilter.before(until);
+			assertNotNull(before);
+			rw.reset();
+			rw.setRevFilter(before);
+			markStart(e);
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertCommit(b, rw.next());
+			assertCommit(a, rw.next());
+			assertNull(rw.next());
+		}
+
+		{
+			RevFilter between = CommitTimeRevFilter.between(since, until);
+			assertNotNull(between);
+			rw.reset();
+			rw.setRevFilter(between);
+			markStart(e);
+			assertCommit(c2, rw.next());
+			assertCommit(c1, rw.next());
+			assertNull(rw.next());
+		}
+	}
+
+	@Test
+	public void testCommitTimeRevFilter_long() throws Exception {
+		final RevCommit a = commit();
+		tick(100);
+
+		final RevCommit b = commit(a);
+		tick(100);
+
+		long since = getInstant().toEpochMilli();
+		final RevCommit c1 = commit(b);
+		tick(100);
+
+		final RevCommit c2 = commit(b);
+		tick(100);
+
+		long until = getInstant().toEpochMilli();
 		final RevCommit d = commit(c1, c2);
 		tick(100);
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
index ec0c0e7..8fa6a83 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java
@@ -12,6 +12,7 @@
 
 import static org.junit.Assert.assertSame;
 
+import java.time.Instant;
 import java.util.Date;
 
 import org.eclipse.jgit.dircache.DirCacheEntry;
@@ -38,8 +39,14 @@ protected RevWalk createRevWalk() {
 		return new RevWalk(db);
 	}
 
+	// Use getInstant() instead
+	@Deprecated
 	protected Date getDate() {
-		return util.getDate();
+		return Date.from(util.getInstant());
+	}
+
+	protected Instant getInstant() {
+		return util.getInstant();
 	}
 
 	protected void tick(int secDelta) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
index 300c869..4306975 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleAddTest.java
@@ -114,6 +114,13 @@ public void addSubmodule() throws Exception {
 				try (Repository subModRepo = generator.getRepository()) {
 					assertNotNull(subModRepo);
 					assertEquals(subCommit, commit);
+					String worktreeDir = subModRepo.getConfig().getString(
+							ConfigConstants.CONFIG_CORE_SECTION, null,
+							ConfigConstants.CONFIG_KEY_WORKTREE);
+					assertEquals("../../../sub", worktreeDir);
+					String gitdir = read(new File(subModRepo.getWorkTree(),
+							Constants.DOT_GIT));
+					assertEquals("gitdir: ../.git/modules/sub", gitdir);
 				}
 			}
 			Status status = Git.wrap(db).status().call();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
index b10bd73..d541170 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/submodule/SubmoduleUpdateTest.java
@@ -17,21 +17,25 @@
 import java.io.IOException;
 import java.util.Collection;
 
+import org.eclipse.jgit.api.CheckoutCommand;
 import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.InitCommand;
+import org.eclipse.jgit.api.SubmoduleAddCommand;
 import org.eclipse.jgit.api.SubmoduleUpdateCommand;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheEditor;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.JGitTestUtil;
 import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.junit.Test;
 
@@ -40,6 +44,91 @@
  */
 public class SubmoduleUpdateTest extends RepositoryTestCase {
 
+	private Repository submoduleRepo;
+
+	private Git git;
+
+	private AnyObjectId subRepoCommit2;
+
+	private void createSubmoduleRepo() throws IOException, GitAPIException {
+		File directory = createTempDirectory("submodule_repo");
+		InitCommand init = Git.init();
+		init.setDirectory(directory);
+		init.call();
+		submoduleRepo = Git.open(directory).getRepository();
+		try (Git sub = Git.wrap(submoduleRepo)) {
+			// commit something
+			JGitTestUtil.writeTrashFile(submoduleRepo, "commit1.txt",
+					"commit 1");
+			sub.add().addFilepattern("commit1.txt").call();
+			sub.commit().setMessage("commit 1").call().getId();
+
+			JGitTestUtil.writeTrashFile(submoduleRepo, "commit2.txt",
+					"commit 2");
+			sub.add().addFilepattern("commit2.txt").call();
+			subRepoCommit2 = sub.commit().setMessage("commit 2").call().getId();
+		}
+	}
+
+	private void addSubmodule(String path) throws GitAPIException {
+		SubmoduleAddCommand command = new SubmoduleAddCommand(db);
+		command.setPath(path);
+		String uri = submoduleRepo.getDirectory().toURI().toString();
+		command.setURI(uri);
+		try (Repository repo = command.call()) {
+			assertNotNull(repo);
+		}
+		git.add().addFilepattern(path).addFilepattern(Constants.DOT_GIT_MODULES)
+				.call();
+		git.commit().setMessage("adding submodule").call();
+		recursiveDelete(new File(git.getRepository().getWorkTree(), path));
+		recursiveDelete(
+				new File(new File(git.getRepository().getCommonDirectory(),
+						Constants.MODULES), path));
+	}
+
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		createSubmoduleRepo();
+
+		git = Git.wrap(db);
+		// commit something
+		writeTrashFile("initial.txt", "initial");
+		git.add().addFilepattern("initial.txt").call();
+		git.commit().setMessage("initial commit").call();
+	}
+
+	public void updateModeClonedRestoredSubmoduleTemplate(String mode)
+			throws Exception {
+		String path = "sub";
+		addSubmodule(path);
+
+		StoredConfig cfg = git.getRepository().getConfig();
+		if (mode != null) {
+			cfg.load();
+			cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+					ConfigConstants.CONFIG_KEY_UPDATE, mode);
+			cfg.save();
+		}
+		SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(db);
+		update.call();
+		try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+			update.call();
+			assertEquals(subRepoCommit2.getName(),
+					subGit.getRepository().getBranch());
+		}
+
+		recursiveDelete(new File(db.getWorkTree(), path));
+
+		update.call();
+		try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+			update.call();
+			assertEquals(subRepoCommit2.getName(),
+					subGit.getRepository().getBranch());
+		}
+	}
+
 	@Test
 	public void repositoryWithNoSubmodules() throws GitAPIException {
 		SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
@@ -50,35 +139,9 @@ public void repositoryWithNoSubmodules() throws GitAPIException {
 
 	@Test
 	public void repositoryWithSubmodule() throws Exception {
-		writeTrashFile("file.txt", "content");
-		Git git = Git.wrap(db);
-		git.add().addFilepattern("file.txt").call();
-		final RevCommit commit = git.commit().setMessage("create file").call();
 
 		final String path = "sub";
-		DirCache cache = db.lockDirCache();
-		DirCacheEditor editor = cache.editor();
-		editor.add(new PathEdit(path) {
-
-			@Override
-			public void apply(DirCacheEntry ent) {
-				ent.setFileMode(FileMode.GITLINK);
-				ent.setObjectId(commit);
-			}
-		});
-		editor.commit();
-
-		StoredConfig config = db.getConfig();
-		config.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-				ConfigConstants.CONFIG_KEY_URL, db.getDirectory().toURI()
-						.toString());
-		config.save();
-
-		FileBasedConfig modulesConfig = new FileBasedConfig(new File(
-				db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS());
-		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-				ConfigConstants.CONFIG_KEY_PATH, path);
-		modulesConfig.save();
+		addSubmodule(path);
 
 		SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
 		Collection<String> updated = command.call();
@@ -90,14 +153,22 @@ public void apply(DirCacheEntry ent) {
 			assertTrue(generator.next());
 			try (Repository subRepo = generator.getRepository()) {
 				assertNotNull(subRepo);
-				assertEquals(commit, subRepo.resolve(Constants.HEAD));
+				assertEquals(subRepoCommit2, subRepo.resolve(Constants.HEAD));
+				String worktreeDir = subRepo.getConfig().getString(
+						ConfigConstants.CONFIG_CORE_SECTION, null,
+						ConfigConstants.CONFIG_KEY_WORKTREE);
+				assertEquals("../../../sub", worktreeDir);
+				String gitdir = read(
+						new File(subRepo.getWorkTree(), Constants.DOT_GIT));
+				assertEquals("gitdir: ../.git/modules/sub", gitdir);
+
 			}
 		}
 	}
 
 	@Test
-	public void repositoryWithUnconfiguredSubmodule() throws IOException,
-			GitAPIException {
+	public void repositoryWithUnconfiguredSubmodule()
+			throws IOException, GitAPIException {
 		final ObjectId id = ObjectId
 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
 		final String path = "sub";
@@ -113,16 +184,14 @@ public void apply(DirCacheEntry ent) {
 		});
 		editor.commit();
 
-		FileBasedConfig modulesConfig = new FileBasedConfig(new File(
-				db.getWorkTree(), Constants.DOT_GIT_MODULES), db.getFS());
+		FileBasedConfig modulesConfig = new FileBasedConfig(
+				new File(db.getWorkTree(), Constants.DOT_GIT_MODULES),
+				db.getFS());
 		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
 				ConfigConstants.CONFIG_KEY_PATH, path);
 		String url = "git://server/repo.git";
 		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
 				ConfigConstants.CONFIG_KEY_URL, url);
-		String update = "rebase";
-		modulesConfig.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
-				ConfigConstants.CONFIG_KEY_UPDATE, update);
 		modulesConfig.save();
 
 		SubmoduleUpdateCommand command = new SubmoduleUpdateCommand(db);
@@ -132,8 +201,8 @@ public void apply(DirCacheEntry ent) {
 	}
 
 	@Test
-	public void repositoryWithInitializedSubmodule() throws IOException,
-			GitAPIException {
+	public void repositoryWithInitializedSubmodule()
+			throws IOException, GitAPIException {
 		final ObjectId id = ObjectId
 				.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
 		final String path = "sub";
@@ -160,4 +229,77 @@ public void apply(DirCacheEntry ent) {
 		assertNotNull(updated);
 		assertTrue(updated.isEmpty());
 	}
+
+	@Test
+	public void updateModeMergeClonedRestoredSubmodule() throws Exception {
+		updateModeClonedRestoredSubmoduleTemplate(
+				ConfigConstants.CONFIG_KEY_MERGE);
+	}
+
+	@Test
+	public void updateModeRebaseClonedRestoredSubmodule() throws Exception {
+		updateModeClonedRestoredSubmoduleTemplate(
+				ConfigConstants.CONFIG_KEY_REBASE);
+	}
+
+	@Test
+	public void updateModeCheckoutClonedRestoredSubmodule() throws Exception {
+		updateModeClonedRestoredSubmoduleTemplate(
+				ConfigConstants.CONFIG_KEY_CHECKOUT);
+	}
+
+	@Test
+	public void updateModeMissingClonedRestoredSubmodule() throws Exception {
+		updateModeClonedRestoredSubmoduleTemplate(null);
+	}
+
+	@Test
+	public void updateMode() throws Exception {
+		String path = "sub";
+		addSubmodule(path);
+
+		StoredConfig cfg = git.getRepository().getConfig();
+		cfg.load();
+		cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+				ConfigConstants.CONFIG_KEY_UPDATE,
+				ConfigConstants.CONFIG_KEY_REBASE);
+		cfg.save();
+
+		SubmoduleUpdateCommand update = new SubmoduleUpdateCommand(db);
+		update.call();
+		try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+			CheckoutCommand checkout = subGit.checkout();
+			checkout.setName("master");
+			checkout.call();
+			update.call();
+			assertEquals("master", subGit.getRepository().getBranch());
+		}
+
+		cfg.load();
+		cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+				ConfigConstants.CONFIG_KEY_UPDATE,
+				ConfigConstants.CONFIG_KEY_CHECKOUT);
+		cfg.save();
+
+		update.call();
+		try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+			assertEquals(subRepoCommit2.getName(),
+					subGit.getRepository().getBranch());
+		}
+
+		cfg.load();
+		cfg.setString(ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+				ConfigConstants.CONFIG_KEY_UPDATE,
+				ConfigConstants.CONFIG_KEY_MERGE);
+		cfg.save();
+
+		update.call();
+		try (Git subGit = Git.open(new File(db.getWorkTree(), path))) {
+			CheckoutCommand checkout = subGit.checkout();
+			checkout.setName("master");
+			checkout.call();
+			update.call();
+			assertEquals("master", subGit.getRepository().getBranch());
+		}
+	}
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java
index cee023d..6290b79 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateIdentTest.java
@@ -138,6 +138,51 @@ public void incompleteCasesMatchPersonIdent() throws Exception {
 				"Me <me@example.com>");
 	}
 
+	@Test
+	public void timezoneRange_hours() {
+		int HOUR_TO_MS = 60 * 60 * 1000;
+
+		// java.util.TimeZone: Hours must be between 0 to 23
+		PushCertificateIdent hourLimit = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +2300");
+		assertEquals(1380, hourLimit.getTimeZoneOffset());
+		assertEquals(23 * HOUR_TO_MS,
+				hourLimit.getTimeZone().getOffset(1218123387));
+
+		PushCertificateIdent hourDubious = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +2400");
+		assertEquals(1440, hourDubious.getTimeZoneOffset());
+		assertEquals(0, hourDubious.getTimeZone().getOffset(1218123387));
+	}
+
+	@Test
+	public void timezoneRange_minutes() {
+		PushCertificateIdent hourLimit = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +0059");
+		assertEquals(59, hourLimit.getTimeZoneOffset());
+		assertEquals(59 * 60 * 1000,
+				hourLimit.getTimeZone().getOffset(1218123387));
+
+		// This becomes one hour and one minute (!)
+		PushCertificateIdent hourDubious = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +0061");
+		assertEquals(61, hourDubious.getTimeZoneOffset());
+		assertEquals(61 * 60 * 1000,
+				hourDubious.getTimeZone().getOffset(1218123387));
+
+		PushCertificateIdent weirdCase = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +0099");
+		assertEquals(99, weirdCase.getTimeZoneOffset());
+		assertEquals(99 * 60 * 1000,
+				weirdCase.getTimeZone().getOffset(1218123387));
+
+		PushCertificateIdent weirdCase2 = PushCertificateIdent
+				.parse("A U. Thor <a_u_thor@example.com> 1218123387 +0199");
+		assertEquals(60 + 99, weirdCase2.getTimeZoneOffset());
+		assertEquals((60 + 99) * 60 * 1000,
+				weirdCase2.getTimeZone().getOffset(1218123387));
+	}
+
 	private static void assertMatchesPersonIdent(String raw,
 			PersonIdent expectedPersonIdent, String expectedUserId) {
 		PushCertificateIdent certIdent = PushCertificateIdent.parse(raw);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
index 4f01e4d..a03222b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
@@ -23,6 +23,8 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -318,8 +320,8 @@ public void putMatchingWithSomeMatchingRefs() throws Exception {
 	}
 
 	private PersonIdent newIdent() {
-		return new PersonIdent(
-				"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
+		return new PersonIdent("A U. Thor", "author@example.com",
+				Instant.ofEpochMilli(ts.getAndIncrement()), ZoneOffset.UTC);
 	}
 
 	private PushCertificateStore newStore() {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
index 029b45e..96d3a58 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportHttpTest.java
@@ -14,10 +14,10 @@
 import java.io.File;
 import java.io.IOException;
 import java.net.HttpCookie;
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
@@ -101,7 +101,7 @@ public void testProcessResponseCookies() throws IOException {
 						.singletonList("cookie2=some value; Max-Age=1234; Path=/"));
 
 		try (TransportHttp transportHttp = new TransportHttp(db, uri)) {
-			Date creationDate = new Date();
+			Instant creationDate = Instant.now();
 			transportHttp.processResponseCookies(connection);
 
 			// evaluate written cookie file
@@ -112,8 +112,9 @@ public void testProcessResponseCookies() throws IOException {
 			cookie.setPath("/u/2/");
 
 			cookie.setMaxAge(
-					(Instant.parse("2100-01-01T11:00:00.000Z").toEpochMilli()
-							- creationDate.getTime()) / 1000);
+					Duration.between(creationDate,
+							Instant.parse("2100-01-01T11:00:00.000Z"))
+							.getSeconds());
 			cookie.setSecure(true);
 			cookie.setHttpOnly(true);
 			expectedCookies.add(cookie);
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.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
index aaecfd2..5c2f0e5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -1,5 +1,6 @@
 package org.eclipse.jgit.transport;
 
+import static java.time.ZoneOffset.UTC;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
@@ -18,6 +19,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1506,14 +1508,19 @@ public void testV2FetchDeepenWithoutDone() throws Exception {
 	public void testV2FetchShallowSince() throws Exception {
 		PersonIdent person = new PersonIdent(remote.getRepository());
 
-		RevCommit beyondBoundary = remote.commit()
-			.committer(new PersonIdent(person, 1510000000, 0)).create();
-		RevCommit boundary = remote.commit().parent(beyondBoundary)
-			.committer(new PersonIdent(person, 1520000000, 0)).create();
-		RevCommit tooOld = remote.commit()
-			.committer(new PersonIdent(person, 1500000000, 0)).create();
+		RevCommit beyondBoundary = remote.commit().committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1510000), UTC))
+				.create();
+		RevCommit boundary = remote.commit().parent(beyondBoundary).committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1520000), UTC))
+				.create();
+		RevCommit tooOld = remote.commit().committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1500000), UTC))
+				.create();
 		RevCommit merge = remote.commit().parent(boundary).parent(tooOld)
-			.committer(new PersonIdent(person, 1530000000, 0)).create();
+				.committer(new PersonIdent(person,
+						Instant.ofEpochSecond(1530000), UTC))
+				.create();
 
 		remote.update("branch1", merge);
 
@@ -1559,12 +1566,15 @@ public void testV2FetchShallowSince() throws Exception {
 	public void testV2FetchShallowSince_excludedParentWithMultipleChildren() throws Exception {
 		PersonIdent person = new PersonIdent(remote.getRepository());
 
-		RevCommit base = remote.commit()
-			.committer(new PersonIdent(person, 1500000000, 0)).create();
-		RevCommit child1 = remote.commit().parent(base)
-			.committer(new PersonIdent(person, 1510000000, 0)).create();
-		RevCommit child2 = remote.commit().parent(base)
-			.committer(new PersonIdent(person, 1520000000, 0)).create();
+		RevCommit base = remote.commit().committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1500000), UTC))
+				.create();
+		RevCommit child1 = remote.commit().parent(base).committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1510000), UTC))
+				.create();
+		RevCommit child2 = remote.commit().parent(base).committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1520000), UTC))
+				.create();
 
 		remote.update("branch1", child1);
 		remote.update("branch2", child2);
@@ -1601,8 +1611,9 @@ public void testV2FetchShallowSince_excludedParentWithMultipleChildren() throws
 	public void testV2FetchShallowSince_noCommitsSelected() throws Exception {
 		PersonIdent person = new PersonIdent(remote.getRepository());
 
-		RevCommit tooOld = remote.commit()
-				.committer(new PersonIdent(person, 1500000000, 0)).create();
+		RevCommit tooOld = remote.commit().committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1500000), UTC))
+				.create();
 
 		remote.update("branch1", tooOld);
 
@@ -1726,12 +1737,15 @@ public void testV2FetchDeepenNot_supportAnnotatedTags() throws Exception {
 	public void testV2FetchDeepenNot_excludedParentWithMultipleChildren() throws Exception {
 		PersonIdent person = new PersonIdent(remote.getRepository());
 
-		RevCommit base = remote.commit()
-			.committer(new PersonIdent(person, 1500000000, 0)).create();
-		RevCommit child1 = remote.commit().parent(base)
-			.committer(new PersonIdent(person, 1510000000, 0)).create();
-		RevCommit child2 = remote.commit().parent(base)
-			.committer(new PersonIdent(person, 1520000000, 0)).create();
+		RevCommit base = remote.commit().committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1500000), UTC))
+				.create();
+		RevCommit child1 = remote.commit().parent(base).committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1510000), UTC))
+				.create();
+		RevCommit child2 = remote.commit().parent(base).committer(
+				new PersonIdent(person, Instant.ofEpochSecond(1520000), UTC))
+				.create();
 
 		remote.update("base", base);
 		remote.update("branch1", child1);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
index 3265249..44e8632 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java
@@ -12,7 +12,9 @@
 
 import static org.junit.Assert.assertEquals;
 
-import java.util.concurrent.TimeUnit;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
 
 import org.eclipse.jgit.junit.MockSystemReader;
 import org.eclipse.jgit.lib.ObjectId;
@@ -53,9 +55,9 @@ public class ChangeIdUtilTest {
 
 	MockSystemReader mockSystemReader = new MockSystemReader();
 
-	final long when = mockSystemReader.getCurrentTime();
+	Instant when = mockSystemReader.now();
 
-	final int tz = new MockSystemReader().getTimezone(when);
+	ZoneId tz = new MockSystemReader().getTimeZoneAt(when);
 
 	PersonIdent author = new PersonIdent("J. Author", "ja@example.com");
 	{
@@ -218,23 +220,23 @@ public void testACommitWithSubject_NonFooterAndBugAndSob() throws Exception {
 	@Test
 	public void testACommitWithSubjectBodyBugBrackersAndSob() throws Exception {
 		assertEquals(
-				"a commit with subject body, bug. brackers and sob\n\nText\n\nBug: 33\nChange-Id: I90ecb589bef766302532c3e00915e10114b00f62\n[bracket]\nSigned-off-by: me@you.too\n",
-				call("a commit with subject body, bug. brackers and sob\n\nText\n\nBug: 33\n[bracket]\nSigned-off-by: me@you.too\n\n"));
+				"a commit with subject body, bug, brackers and sob\n\nText\n\nBug: 33\n[bracket]\nChange-Id: I94dc6ed919a4baaa7c1bf8712717b888c6b90363\nSigned-off-by: me@you.too\n",
+				call("a commit with subject body, bug, brackers and sob\n\nText\n\nBug: 33\n[bracket]\nSigned-off-by: me@you.too\n\n"));
 	}
 
 	@Test
 	public void testACommitWithSubjectBodyBugLineWithASpaceAndSob()
 			throws Exception {
 		assertEquals(
-				"a commit with subject body, bug. line with a space and sob\n\nText\n\nBug: 33\nChange-Id: I864e2218bdee033c8ce9a7f923af9e0d5dc16863\n \nSigned-off-by: me@you.too\n",
-				call("a commit with subject body, bug. line with a space and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
+				"a commit with subject body, bug, line with a space and sob\n\nText\n\nBug: 33\n \nChange-Id: I126b472d2e0e64ad8187d61857f0169f9ccdae86\nSigned-off-by: me@you.too\n",
+				call("a commit with subject body, bug, line with a space and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
 	}
 
 	@Test
 	public void testACommitWithSubjectBodyBugEmptyLineAndSob() throws Exception {
 		assertEquals(
-				"a commit with subject body, bug. empty line and sob\n\nText\n\nBug: 33\nChange-Id: I33f119f533313883e6ada3df600c4f0d4db23a76\n \nSigned-off-by: me@you.too\n",
-				call("a commit with subject body, bug. empty line and sob\n\nText\n\nBug: 33\n \nSigned-off-by: me@you.too\n\n"));
+				"a commit with subject body, bug, empty line and sob\n\nText\n\nBug: 33\n\nChange-Id: Ic3b61b6e39a0815669b65302e9e75e6a5a019a26\nSigned-off-by: me@you.too\n",
+				call("a commit with subject body, bug, empty line and sob\n\nText\n\nBug: 33\n\nSigned-off-by: me@you.too\n\n"));
 	}
 
 	@Test
@@ -342,9 +344,7 @@ public void testTimeAltersId() throws Exception {
 
 	/** Increment the {@link #author} and {@link #committer} times. */
 	protected void tick() {
-		final long delta = TimeUnit.MILLISECONDS.convert(5 * 60,
-				TimeUnit.SECONDS);
-		final long now = author.getWhen().getTime() + delta;
+		Instant now = author.getWhenAsInstant().plus(Duration.ofMinutes(5));
 
 		author = new PersonIdent(author, now, tz);
 		committer = new PersonIdent(committer, now, tz);
@@ -528,7 +528,7 @@ public void testKernelStyleFooter() throws Exception {
 	}
 
 	@Test
-	public void testChangeIdAfterBugOrIssue() throws Exception {
+	public void testChangeIdAfterOtherFooters() throws Exception {
 		assertEquals("a\n" + //
 				"\n" + //
 				"Bug: 42\n" + //
@@ -541,6 +541,18 @@ public void testChangeIdAfterBugOrIssue() throws Exception {
 
 		assertEquals("a\n" + //
 				"\n" + //
+				"Bug: 42\n" + //
+				"     multi-line Bug footer\n" + //
+				"Change-Id: Icc953ef35f1a4ee5eb945132aefd603ae3d9dd9f\n" + //
+				SOB1,//
+				call("a\n" + //
+						"\n" + //
+						"Bug: 42\n" + //
+						"     multi-line Bug footer\n" + //
+						SOB1));
+
+		assertEquals("a\n" + //
+				"\n" + //
 				"Issue: 42\n" + //
 				"Change-Id: Ie66e07d89ae5b114c0975b49cf326e90331dd822\n" + //
 				SOB1,//
@@ -548,6 +560,14 @@ public void testChangeIdAfterBugOrIssue() throws Exception {
 						"\n" + //
 						"Issue: 42\n" + //
 						SOB1));
+
+		assertEquals("a\n" + //
+				"\n" + //
+				"Other: none\n" + //
+				"Change-Id: Ide70e625dea61854206378a377dd12e462ae720f\n",//
+				call("a\n" + //
+						"\n" + //
+						"Other: none\n"));
 	}
 
 	@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java
index e517889..6d23db8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_ParsePersonIdentTest.java
@@ -107,6 +107,21 @@ public void testParsePersonIdent_malformedCases() {
 		assertPersonIdent("Me <me@example.com 1234567890 -0700", null);
 	}
 
+	@Test
+	public void testParsePersonIdent_badTz() {
+		PersonIdent tooBig = RawParseUtils
+				.parsePersonIdent("Me <me@example.com> 1234567890 +8315");
+		assertEquals(tooBig.getZoneOffset().getTotalSeconds(), 0);
+
+		PersonIdent tooSmall = RawParseUtils
+				.parsePersonIdent("Me <me@example.com> 1234567890 -8315");
+		assertEquals(tooSmall.getZoneOffset().getTotalSeconds(), 0);
+
+		PersonIdent notATime = RawParseUtils
+				.parsePersonIdent("Me <me@example.com> 1234567890 -0370");
+		assertEquals(notATime.getZoneOffset().getTotalSeconds(), 0);
+	}
+
 	private static void assertPersonIdent(String line, PersonIdent expected) {
 		PersonIdent actual = RawParseUtils.parsePersonIdent(line);
 		assertEquals(expected, actual);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
index 214bbca..a927d8d 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RelativeDateFormatterTest.java
@@ -16,7 +16,7 @@
 import static org.eclipse.jgit.util.RelativeDateFormatter.YEAR_IN_MILLIS;
 import static org.junit.Assert.assertEquals;
 
-import java.util.Date;
+import java.time.Instant;
 
 import org.eclipse.jgit.junit.MockSystemReader;
 import org.junit.After;
@@ -37,9 +37,9 @@ public void tearDown() {
 
 	private static void assertFormat(long ageFromNow, long timeUnit,
 			String expectedFormat) {
-		Date d = new Date(SystemReader.getInstance().getCurrentTime()
-				- ageFromNow * timeUnit);
-		String s = RelativeDateFormatter.format(d);
+		long millis = ageFromNow * timeUnit;
+		Instant aTime = SystemReader.getInstance().now().minusMillis(millis);
+		String s = RelativeDateFormatter.format(aTime);
 		assertEquals(expectedFormat, s);
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
index 015da16..9a1c710 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java
@@ -12,6 +12,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
@@ -172,4 +173,22 @@ public void testCommonPrefix() {
 		assertEquals("foo bar ",
 				StringUtils.commonPrefix("foo bar 42", "foo bar 24"));
 	}
+
+	@Test
+	public void testTrim() {
+		assertEquals("a", StringUtils.trim("a", '/'));
+		assertEquals("aaaa", StringUtils.trim("aaaa", '/'));
+		assertEquals("aaa", StringUtils.trim("/aaa", '/'));
+		assertEquals("aaa", StringUtils.trim("aaa/", '/'));
+		assertEquals("aaa", StringUtils.trim("/aaa/", '/'));
+		assertEquals("aa/aa", StringUtils.trim("/aa/aa/", '/'));
+		assertEquals("aa/aa", StringUtils.trim("aa/aa", '/'));
+
+		assertEquals("", StringUtils.trim("", '/'));
+		assertEquals("", StringUtils.trim("/", '/'));
+		assertEquals("", StringUtils.trim("//", '/'));
+		assertEquals("", StringUtils.trim("///", '/'));
+
+		assertNull(StringUtils.trim(null, '/'));
+	}
 }
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 399977a..eb80688 100644
--- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
@@ -4,14 +4,14 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit.ui
 Bundle-SymbolicName: org.eclipse.jgit.ui
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.awtui;version="7.1.2"
-Import-Package: org.eclipse.jgit.errors;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.lib;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.nls;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revplot;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.revwalk;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.transport;version="[7.1.2,7.2.0)",
- org.eclipse.jgit.util;version="[7.1.2,7.2.0)"
+Export-Package: org.eclipse.jgit.awtui;version="7.2.2"
+Import-Package: org.eclipse.jgit.errors;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.lib;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.nls;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revplot;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.revwalk;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.transport;version="[7.2.2,7.3.0)",
+ org.eclipse.jgit.util;version="[7.2.2,7.3.0)"
diff --git a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
index 7ed704c..851cbdc 100644
--- a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit.ui - Sources
 Bundle-SymbolicName: org.eclipse.jgit.ui.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ui;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ui;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index 90f74a4..df375c8 100644
--- a/org.eclipse.jgit.ui/pom.xml
+++ b/org.eclipse.jgit.ui/pom.xml
@@ -19,7 +19,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit.ui</artifactId>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 023018e..eeed75f 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,66 +1,57 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
-    <resource path="src/org/eclipse/jgit/diff/DiffDriver.java" type="org.eclipse.jgit.diff.DiffDriver">
-        <filter id="1109393411">
+    <resource path="META-INF/MANIFEST.MF">
+        <filter id="923795461">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="org.eclipse.jgit.diff.DiffDriver"/>
+                <message_argument value="7.2.2"/>
+                <message_argument value="7.1.0"/>
+            </message_arguments>
+        </filter>
+        <filter id="934281281">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.lib"/>
+                <message_argument value="8.1.0"/>
+                <message_argument value="7.1.0"/>
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/diff/DiffFormatter.java" type="org.eclipse.jgit.diff.DiffFormatter">
-        <filter id="1142947843">
+    <resource path="src/org/eclipse/jgit/lib/RefDatabase.java" type="org.eclipse.jgit.lib.RefDatabase">
+        <filter id="336695337">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="format(EditList, RawText, RawText, DiffDriver)"/>
-            </message_arguments>
-        </filter>
-        <filter id="1142947843">
-            <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="format(FileHeader, RawText, RawText, DiffDriver)"/>
-            </message_arguments>
-        </filter>
-        <filter id="1142947843">
-            <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="writeHunkHeader(int, int, int, int, String)"/>
+                <message_argument value="org.eclipse.jgit.lib.RefDatabase"/>
+                <message_argument value="getReflogReader(Ref)"/>
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
-        <filter id="1142947843">
+    <resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
+        <filter id="403804204">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="ATTR_BUILTIN_UNION_MERGE_DRIVER"/>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getBoolean(Config, String, String, String, Boolean)"/>
             </message_arguments>
         </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/merge/ContentMergeStrategy.java" type="org.eclipse.jgit.merge.ContentMergeStrategy">
-        <filter id="1176502275">
+        <filter id="403804204">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="UNION"/>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getInt(Config, String, String, String, Integer)"/>
             </message_arguments>
         </filter>
-    </resource>
-    <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
-        <filter id="336658481">
+        <filter id="403804204">
             <message_arguments>
-                <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
-                <message_argument value="attributesNodeProvider"/>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getIntInRange(Config, String, String, String, int, int, Integer)"/>
             </message_arguments>
         </filter>
-        <filter id="1142947843">
+        <filter id="403804204">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="attributesNodeProvider"/>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getLong(Config, String, String, String, Long)"/>
             </message_arguments>
         </filter>
-        <filter id="1142947843">
+        <filter id="403804204">
             <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="setAttributesNodeProvider(AttributesNodeProvider)"/>
+                <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+                <message_argument value="getTimeUnit(Config, String, String, String, Long, TimeUnit)"/>
             </message_arguments>
         </filter>
     </resource>
@@ -72,14 +63,6 @@
             </message_arguments>
         </filter>
     </resource>
-    <resource path="src/org/eclipse/jgit/transport/UploadPack.java" type="org.eclipse.jgit.transport.UploadPack$RequestPolicy">
-        <filter id="1176502275">
-            <message_arguments>
-                <message_argument value="6.10.1"/>
-                <message_argument value="implies(UploadPack.RequestPolicy)"/>
-            </message_arguments>
-        </filter>
-    </resource>
     <resource path="src/org/eclipse/jgit/util/Iterators.java" type="org.eclipse.jgit.util.Iterators">
         <filter id="1109393411">
             <message_arguments>
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 1c06d57..c2013f6 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -3,14 +3,14 @@
 Bundle-Name: %Bundle-Name
 Automatic-Module-Name: org.eclipse.jgit
 Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 7.1.2.qualifier
+Bundle-Version: 7.2.2.qualifier
 Bundle-Localization: OSGI-INF/l10n/plugin
 Bundle-Vendor: %Bundle-Vendor
 Bundle-ActivationPolicy: lazy
 Service-Component: OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml
 Eclipse-ExtensibleAPI: true
-Export-Package: org.eclipse.jgit.annotations;version="7.1.2",
- org.eclipse.jgit.api;version="7.1.2";
+Export-Package: org.eclipse.jgit.annotations;version="7.2.2",
+ org.eclipse.jgit.api;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.notes,
    org.eclipse.jgit.dircache,
@@ -25,72 +25,77 @@
    org.eclipse.jgit.revwalk.filter,
    org.eclipse.jgit.blame,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="7.1.2";
+ org.eclipse.jgit.api.errors;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="7.1.2";
+ org.eclipse.jgit.attributes;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="7.1.2";
+ org.eclipse.jgit.blame;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
-   org.eclipse.jgit.treewalk.filter,
-   org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="7.1.2";
+   org.eclipse.jgit.blame.cache,
+   org.eclipse.jgit.diff,
+   org.eclipse.jgit.treewalk.filter",
+ org.eclipse.jgit.blame.cache;version="7.2.2";
+  uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.diff;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.attributes,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.patch,
+   org.eclipse.jgit.attributes,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="7.1.2";
+ org.eclipse.jgit.dircache;version="7.2.2";
   uses:="org.eclipse.jgit.events,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util",
- org.eclipse.jgit.errors;version="7.1.2";
+ org.eclipse.jgit.errors;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.dircache,
-   org.eclipse.jgit.lib,
-   org.eclipse.jgit.internal.storage.pack",
- org.eclipse.jgit.events;version="7.1.2";
+   org.eclipse.jgit.lib",
+ org.eclipse.jgit.events;version="7.2.2";
   uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="7.1.2",
- org.eclipse.jgit.gitrepo;version="7.1.2";
+ org.eclipse.jgit.fnmatch;version="7.2.2",
+ org.eclipse.jgit.gitrepo;version="7.2.2";
   uses:="org.xml.sax.helpers,
    org.eclipse.jgit.api,
+   org.eclipse.jgit.api.errors,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="7.1.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="7.1.2",
- org.eclipse.jgit.ignore.internal;version="7.1.2";
+ org.eclipse.jgit.gitrepo.internal;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.hooks;version="7.2.2";
+  uses:="org.eclipse.jgit.lib,
+   org.eclipse.jgit.util",
+ org.eclipse.jgit.ignore;version="7.2.2",
+ org.eclipse.jgit.ignore.internal;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="7.1.2";
+ org.eclipse.jgit.internal;version="7.2.2";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.diff;version="7.1.2";
+ org.eclipse.jgit.internal.diff;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.diffmergetool;version="7.1.2";
+ org.eclipse.jgit.internal.diffmergetool;version="7.2.2";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.pgm,
    org.eclipse.egit.ui",
- org.eclipse.jgit.internal.fsck;version="7.1.2";
+ org.eclipse.jgit.internal.fsck;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="7.1.2";
+ org.eclipse.jgit.internal.revwalk;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.commitgraph;version="7.1.2";
+ org.eclipse.jgit.internal.storage.commitgraph;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="7.1.2";
+ org.eclipse.jgit.internal.storage.dfs;version="7.2.2";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.http.server,
    org.eclipse.jgit.http.test,
    org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="7.1.2";
+ org.eclipse.jgit.internal.storage.file;version="7.2.2";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.junit.http,
@@ -99,41 +104,43 @@
    org.eclipse.jgit.pgm,
    org.eclipse.jgit.pgm.test,
    org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="7.1.2";
+ org.eclipse.jgit.internal.storage.io;version="7.2.2";
   x-friends:="org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.memory;version="7.1.2";
+ org.eclipse.jgit.internal.storage.memory;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.pack;version="7.1.2";
+ org.eclipse.jgit.internal.storage.midx;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.internal.storage.pack;version="7.2.2";
   x-friends:="org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="7.1.2";
+ org.eclipse.jgit.internal.storage.reftable;version="7.2.2";
   x-friends:="org.eclipse.jgit.http.test,
    org.eclipse.jgit.junit,
    org.eclipse.jgit.test,
    org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="7.1.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="7.1.2";
+ org.eclipse.jgit.internal.submodule;version="7.2.2";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="7.1.2";
+ org.eclipse.jgit.internal.transport.http;version="7.2.2";
   x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="7.1.2";
+ org.eclipse.jgit.internal.transport.parser;version="7.2.2";
   x-friends:="org.eclipse.jgit.http.server,
    org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="7.1.2";
+ org.eclipse.jgit.internal.transport.ssh;version="7.2.2";
   x-friends:="org.eclipse.jgit.ssh.apache,
    org.eclipse.jgit.ssh.jsch,
    org.eclipse.jgit.test",
- org.eclipse.jgit.internal.util;version="7.1.2";
-  x-friends:=" org.eclipse.jgit.junit",
- org.eclipse.jgit.lib;version="7.1.2";
+ org.eclipse.jgit.internal.util;version="7.2.2";
+  x-friends:="org.eclipse.jgit.junit",
+ org.eclipse.jgit.lib;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.util.sha1,
    org.eclipse.jgit.dircache,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.internal.storage.file,
+   org.eclipse.jgit.api,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.events,
    com.googlecode.javaewah,
@@ -142,12 +149,12 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.submodule,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="7.1.2";
+ org.eclipse.jgit.lib.internal;version="7.2.2";
   x-friends:="org.eclipse.jgit.test,
    org.eclipse.jgit.pgm,
    org.eclipse.egit.ui",
- org.eclipse.jgit.logging;version="7.1.2",
- org.eclipse.jgit.merge;version="7.1.2";
+ org.eclipse.jgit.logging;version="7.2.2",
+ org.eclipse.jgit.merge;version="7.2.2";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
@@ -156,67 +163,69 @@
    org.eclipse.jgit.util,
    org.eclipse.jgit.api,
    org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="7.1.2",
- org.eclipse.jgit.notes;version="7.1.2";
+ org.eclipse.jgit.nls;version="7.2.2",
+ org.eclipse.jgit.notes;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="7.1.2";
+ org.eclipse.jgit.patch;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
+   org.eclipse.jgit.revwalk,
    org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="7.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="7.1.2";
-  uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.diff,
-   org.eclipse.jgit.treewalk.filter,
-   org.eclipse.jgit.revwalk.filter,
-   org.eclipse.jgit.treewalk",
- org.eclipse.jgit.revwalk.filter;version="7.1.2";
+ org.eclipse.jgit.revplot;version="7.2.2";
   uses:="org.eclipse.jgit.revwalk,
-   org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="7.1.2";
+   org.eclipse.jgit.lib",
+ org.eclipse.jgit.revwalk;version="7.2.2";
   uses:="org.eclipse.jgit.lib,
-   org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="7.1.2";
-  uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="7.1.2";
-  uses:="org.eclipse.jgit.lib,
+   org.eclipse.jgit.revwalk.filter,
    org.eclipse.jgit.diff,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.treewalk,
+   org.eclipse.jgit.internal.storage.commitgraph",
+ org.eclipse.jgit.revwalk.filter;version="7.2.2";
+  uses:="org.eclipse.jgit.revwalk,
+   org.eclipse.jgit.lib,
    org.eclipse.jgit.util",
- org.eclipse.jgit.transport;version="7.1.2";
+ org.eclipse.jgit.storage.file;version="7.2.2";
+  uses:="org.eclipse.jgit.lib,
+   org.eclipse.jgit.util",
+ org.eclipse.jgit.storage.pack;version="7.2.2";
+  uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.submodule;version="7.2.2";
+  uses:="org.eclipse.jgit.lib,
+   org.eclipse.jgit.treewalk.filter,
+   org.eclipse.jgit.diff,
+   org.eclipse.jgit.treewalk,
+   org.eclipse.jgit.util",
+ org.eclipse.jgit.transport;version="7.2.2";
   uses:="javax.crypto,
+   org.eclipse.jgit.hooks,
    org.eclipse.jgit.util.io,
    org.eclipse.jgit.lib,
-   org.eclipse.jgit.revwalk,
    org.eclipse.jgit.transport.http,
-   org.eclipse.jgit.internal.storage.file,
+   org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk,
    org.eclipse.jgit.util,
    org.eclipse.jgit.internal.storage.pack,
    org.eclipse.jgit.transport.resolver,
    org.eclipse.jgit.storage.pack,
    org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="7.1.2";
+ org.eclipse.jgit.transport.http;version="7.2.2";
   uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="7.1.2";
+ org.eclipse.jgit.transport.resolver;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="7.1.2";
+ org.eclipse.jgit.treewalk;version="7.2.2";
   uses:="org.eclipse.jgit.dircache,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.attributes,
    org.eclipse.jgit.revwalk,
    org.eclipse.jgit.treewalk.filter,
    org.eclipse.jgit.util",
- org.eclipse.jgit.treewalk.filter;version="7.1.2";
+ org.eclipse.jgit.treewalk.filter;version="7.2.2";
   uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="7.1.2";
+ org.eclipse.jgit.util;version="7.2.2";
   uses:="org.eclipse.jgit.transport,
    org.eclipse.jgit.hooks,
    org.eclipse.jgit.revwalk,
@@ -229,12 +238,12 @@
    org.eclipse.jgit.treewalk,
    javax.net.ssl,
    org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="7.1.2";
+ org.eclipse.jgit.util.io;version="7.2.2";
   uses:="org.eclipse.jgit.attributes,
    org.eclipse.jgit.lib,
    org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="7.1.2",
- org.eclipse.jgit.util.time;version="7.1.2"
+ org.eclipse.jgit.util.sha1;version="7.2.2",
+ org.eclipse.jgit.util.time;version="7.2.2"
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
  javax.crypto,
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 1bf5168..dc4706e 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@
 Bundle-Name: org.eclipse.jgit - Sources
 Bundle-SymbolicName: org.eclipse.jgit.source
 Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 7.1.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="7.1.2.qualifier";roots="."
+Bundle-Version: 7.2.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="7.2.2.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index df45481..87fe65b 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -20,7 +20,7 @@
   <parent>
     <groupId>org.eclipse.jgit</groupId>
     <artifactId>org.eclipse.jgit-parent</artifactId>
-    <version>7.1.2-SNAPSHOT</version>
+    <version>7.2.2-SNAPSHOT</version>
   </parent>
 
   <artifactId>org.eclipse.jgit</artifactId>
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 4e2073b..27270a1 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -76,6 +76,7 @@
 buildingBitmaps=Building bitmaps
 cachedPacksPreventsIndexCreation=Using cached packs prevents index creation
 cachedPacksPreventsListingObjects=Using cached packs prevents listing objects
+cacheRegionAllOrNoneNull=expected all null or none: {0}, {1}
 cannotAccessLastModifiedForSafeDeletion=Unable to access lastModifiedTime of file {0}, skip deletion since we cannot safely avoid race condition
 cannotBeCombined=Cannot be combined.
 cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included.
@@ -265,6 +266,7 @@
 deletingNotSupported=Deleting {0} not supported.
 depthMustBeAt1=Depth must be >= 1
 depthWithUnshallow=Depth and unshallow can\'t be used together
+deprecatedTrustFolderStat=Option core.trustFolderStat is deprecated, replace it by core.trustStat.
 destinationIsNotAWildcard=Destination is not a wildcard.
 detachedHeadDetected=HEAD is detached
 diffToolNotGivenError=No diff tool provided and no defaults configured.
@@ -462,6 +464,7 @@
 invalidTimeUnitValue2=Invalid time unit value: {0}.{1}={2}
 invalidTimeUnitValue3=Invalid time unit value: {0}.{1}.{2}={3}
 invalidTreeZeroLengthName=Cannot append a tree entry with zero-length name
+invalidTrustStat=core.trustStat must not be set to TrustStat.INHERIT, falling back to TrustStat.ALWAYS.
 invalidURL=Invalid URL {0}
 invalidWildcards=Invalid wildcards {0}
 invalidRefSpec=Invalid refspec {0}
@@ -525,6 +528,8 @@
 month=month
 months=months
 monthsAgo={0} months ago
+multiPackIndexUnexpectedSize=MultiPack index: expected %d bytes but out has %d bytes
+multiPackIndexWritingCancelled=Multipack index writing was canceled
 multipleMergeBasesFor=Multiple merge bases for:\n  {0}\n  {1} found:\n  {2}\n  {3}
 nameMustNotBeNullOrEmpty=Ref name must not be null or empty.
 need2Arguments=Need 2 arguments
@@ -613,6 +618,7 @@
 personIdentEmailNonNull=E-mail address of PersonIdent must not be null.
 personIdentNameNonNull=Name of PersonIdent must not be null.
 postCommitHookFailed=Execution of post-commit hook failed: {0}.
+precedenceTrustConfig=Both core.trustFolderStat and core.trustStat are set, ignoring trustFolderStat since trustStat takes precedence. Remove core.trustFolderStat from your configuration.
 prefixRemote=remote:
 problemWithResolvingPushRefSpecsLocally=Problem with resolving push ref specs locally: {0}
 progressMonUploading=Uploading {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index c895dc9..b4d1cab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> and others
+ * Copyright (C) 2010, 2025 Stefan Lay <stefan.lay@sap.com> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -17,6 +17,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.text.MessageFormat;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +60,15 @@ public class AddCommand extends GitCommand<DirCache> {
 
 	private WorkingTreeIterator workingTreeIterator;
 
+	// Update only known index entries, don't add new ones. If there's no file
+	// for an index entry, remove it: stage deletions.
 	private boolean update = false;
 
+	// If TRUE, also stage deletions, otherwise only update and add index
+	// entries.
+	// If not set explicitly
+	private Boolean all;
+
 	// This defaults to true because it's what JGit has been doing
 	// traditionally. The C git default would be false.
 	private boolean renormalize = true;
@@ -82,6 +90,17 @@ public AddCommand(Repository repo) {
 	 * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
 	 * <code>dir/file2</code>) can also be given to add all files in the
 	 * directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
+	 * </p>
+	 * <p>
+	 * If a pattern {@code "."} is added, all changes in the git repository's
+	 * working tree will be added.
+	 * </p>
+	 * <p>
+	 * File patterns are required unless {@code isUpdate() == true} or
+	 * {@link #setAll(boolean)} is called. If so and no file patterns are given,
+	 * all changes will be added (i.e., a file pattern of {@code "."} is
+	 * implied).
+	 * </p>
 	 *
 	 * @param filepattern
 	 *            repository-relative path of file/directory to add (with
@@ -113,15 +132,41 @@ public AddCommand setWorkingTreeIterator(WorkingTreeIterator f) {
 	 * Executes the {@code Add} command. Each instance of this class should only
 	 * be used for one invocation of the command. Don't call this method twice
 	 * on an instance.
+	 * </p>
+	 *
+	 * @throws JGitInternalException
+	 *             on errors, but also if {@code isUpdate() == true} _and_
+	 *             {@link #setAll(boolean)} had been called
+	 * @throws NoFilepatternException
+	 *             if no file patterns are given if {@code isUpdate() == false}
+	 *             and {@link #setAll(boolean)} was not called
 	 */
 	@Override
 	public DirCache call() throws GitAPIException, NoFilepatternException {
-
-		if (filepatterns.isEmpty())
-			throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
 		checkCallable();
+
+		if (update && all != null) {
+			throw new JGitInternalException(MessageFormat.format(
+					JGitText.get().illegalCombinationOfArguments,
+					"--update", "--all/--no-all")); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		boolean addAll;
+		if (filepatterns.isEmpty()) {
+			if (update || all != null) {
+				addAll = true;
+			} else {
+				throw new NoFilepatternException(
+						JGitText.get().atLeastOnePatternIsRequired);
+			}
+		} else {
+			addAll = filepatterns.contains("."); //$NON-NLS-1$
+			if (all == null && !update) {
+				all = Boolean.TRUE;
+			}
+		}
+		boolean stageDeletions = update || (all != null && all.booleanValue());
+
 		DirCache dc = null;
-		boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
 
 		try (ObjectInserter inserter = repo.newObjectInserter();
 				NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
@@ -181,7 +226,8 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 
 				if (f == null) { // working tree file does not exist
 					if (entry != null
-							&& (!update || GITLINK == entry.getFileMode())) {
+							&& (!stageDeletions
+									|| GITLINK == entry.getFileMode())) {
 						builder.add(entry);
 					}
 					continue;
@@ -252,7 +298,8 @@ public DirCache call() throws GitAPIException, NoFilepatternException {
 	}
 
 	/**
-	 * Set whether to only match against already tracked files
+	 * Set whether to only match against already tracked files. If
+	 * {@code update == true}, re-sets a previous {@link #setAll(boolean)}.
 	 *
 	 * @param update
 	 *            If set to true, the command only matches {@code filepattern}
@@ -314,4 +361,32 @@ public AddCommand setRenormalize(boolean renormalize) {
 	public boolean isRenormalize() {
 		return renormalize;
 	}
+
+	/**
+	 * Defines whether the command will use '--all' mode: update existing index
+	 * entries, add new entries, and remove index entries for which there is no
+	 * file. (In other words: also stage deletions.)
+	 * <p>
+	 * The setting is independent of {@link #setUpdate(boolean)}.
+	 * </p>
+	 *
+	 * @param all
+	 *            whether to enable '--all' mode
+	 * @return {@code this}
+	 * @since 7.2
+	 */
+	public AddCommand setAll(boolean all) {
+		this.all = Boolean.valueOf(all);
+		return this;
+	}
+
+	/**
+	 * Tells whether '--all' has been set for this command.
+	 *
+	 * @return {@code true} if it was set; {@code false} otherwise
+	 * @since 7.2
+	 */
+	public boolean isAll() {
+		return all != null && all.booleanValue();
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 3e034f1..4a536b9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -67,6 +67,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
 
 	private boolean bare;
 
+	private boolean relativePaths;
+
 	private FS fs;
 
 	private String remote = Constants.DEFAULT_REMOTE_NAME;
@@ -264,6 +266,7 @@ void verifyDirectories(URIish u) {
 	private Repository init() throws GitAPIException {
 		InitCommand command = Git.init();
 		command.setBare(bare);
+		command.setRelativeDirs(relativePaths);
 		if (fs != null) {
 			command.setFs(fs);
 		}
@@ -555,6 +558,20 @@ public CloneCommand setBare(boolean bare) throws IllegalStateException {
 	}
 
 	/**
+	 * Set whether the cloned repository shall use relative paths for GIT_DIR
+	 * and GIT_WORK_TREE
+	 *
+	 * @param relativePaths
+	 *            if true, use relative paths for GIT_DIR and GIT_WORK_TREE
+	 * @return this instance
+	 * @since 7.2
+	 */
+	public CloneCommand setRelativePaths(boolean relativePaths) {
+		this.relativePaths = relativePaths;
+		return this;
+	}
+
+	/**
 	 * Set the file system abstraction to be used for repositories created by
 	 * this command.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 805a886..d252628 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -15,11 +15,11 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -76,6 +76,11 @@ public class DescribeCommand extends GitCommand<String> {
 	private List<FileNameMatcher> matchers = new ArrayList<>();
 
 	/**
+	 * Pattern matchers to be applied to tags for exclusion.
+	 */
+	private List<FileNameMatcher> excludeMatchers = new ArrayList<>();
+
+	/**
 	 * Whether to use all refs in the refs/ namespace
 	 */
 	private boolean useAll;
@@ -263,6 +268,27 @@ public DescribeCommand setMatch(String... patterns) throws InvalidPatternExcepti
 		return this;
 	}
 
+	/**
+	 * Sets one or more {@code glob(7)} patterns that tags must not match to be
+	 * considered. If multiple patterns are provided, they will all be applied.
+	 *
+	 * @param patterns
+	 *            the {@code glob(7)} pattern or patterns
+	 * @return {@code this}
+	 * @throws org.eclipse.jgit.errors.InvalidPatternException
+	 *             if the pattern passed in was invalid.
+	 * @see <a href=
+	 *      "https://www.kernel.org/pub/software/scm/git/docs/git-describe.html"
+	 *      >Git documentation about describe</a>
+	 * @since 7.2
+	 */
+	public DescribeCommand setExclude(String... patterns) throws InvalidPatternException {
+		for (String p : patterns) {
+			excludeMatchers.add(new FileNameMatcher(p, null));
+		}
+		return this;
+	}
+
 	private final Comparator<Ref> TAG_TIE_BREAKER = new Comparator<>() {
 
 		@Override
@@ -274,25 +300,28 @@ public int compare(Ref o1, Ref o2) {
 			}
 		}
 
-		private Date tagDate(Ref tag) throws IOException {
+		private Instant tagDate(Ref tag) throws IOException {
 			RevTag t = w.parseTag(tag.getObjectId());
 			w.parseBody(t);
-			return t.getTaggerIdent().getWhen();
+			return t.getTaggerIdent().getWhenAsInstant();
 		}
 	};
 
 	private Optional<Ref> getBestMatch(List<Ref> tags) {
 		if (tags == null || tags.isEmpty()) {
 			return Optional.empty();
-		} else if (matchers.isEmpty()) {
+		} else if (matchers.isEmpty() && excludeMatchers.isEmpty()) {
 			Collections.sort(tags, TAG_TIE_BREAKER);
 			return Optional.of(tags.get(0));
-		} else {
+		}
+
+		Stream<Ref> matchingTags;
+		if (!matchers.isEmpty()) {
 			// Find the first tag that matches in the stream of all tags
 			// filtered by matchers ordered by tie break order
-			Stream<Ref> matchingTags = Stream.empty();
+			matchingTags = Stream.empty();
 			for (FileNameMatcher matcher : matchers) {
-				Stream<Ref> m = tags.stream().filter(
+				Stream<Ref> m = tags.stream().filter( //
 						tag -> {
 							matcher.append(formatRefName(tag.getName()));
 							boolean result = matcher.isMatch();
@@ -301,8 +330,22 @@ private Optional<Ref> getBestMatch(List<Ref> tags) {
 						});
 				matchingTags = Stream.of(matchingTags, m).flatMap(i -> i);
 			}
-			return matchingTags.sorted(TAG_TIE_BREAKER).findFirst();
+		} else {
+			// If there are no matchers, there are only excluders
+			// Assume all tags match for now before applying excluders
+			matchingTags = tags.stream();
 		}
+
+		for (FileNameMatcher matcher : excludeMatchers) {
+			matchingTags = matchingTags.filter( //
+					tag -> {
+						matcher.append(formatRefName(tag.getName()));
+						boolean result = matcher.isMatch();
+						matcher.reset();
+						return !result;
+					});
+		}
+		return matchingTags.sorted(TAG_TIE_BREAKER).findFirst();
 	}
 
 	private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException {
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/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
index 88d7e91..f6935e1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -12,6 +12,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.text.ParseException;
+import java.time.Instant;
 import java.util.Date;
 import java.util.Properties;
 import java.util.concurrent.ExecutionException;
@@ -59,7 +60,7 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
 
 	private ProgressMonitor monitor;
 
-	private Date expire;
+	private Instant expire;
 
 	private PackConfig pconfig;
 
@@ -98,8 +99,29 @@ public GarbageCollectCommand setProgressMonitor(ProgressMonitor monitor) {
 	 * @param expire
 	 *            minimal age of objects to be pruned.
 	 * @return this instance
+	 * @deprecated use {@link #setExpire(Instant)} instead
 	 */
+	@Deprecated(since = "7.2")
 	public GarbageCollectCommand setExpire(Date expire) {
+		if (expire != null) {
+			this.expire = expire.toInstant();
+		}
+		return this;
+	}
+
+	/**
+	 * During gc() or prune() each unreferenced, loose object which has been
+	 * created or modified after <code>expire</code> will not be pruned. Only
+	 * older objects may be pruned. If set to null then every object is a
+	 * candidate for pruning. Use {@link org.eclipse.jgit.util.GitTimeParser} to
+	 * parse time formats used by git gc.
+	 *
+	 * @param expire
+	 *            minimal age of objects to be pruned.
+	 * @return this instance
+	 * @since 7.2
+	 */
+	public GarbageCollectCommand setExpire(Instant expire) {
 		this.expire = expire;
 		return this;
 	}
@@ -108,8 +130,8 @@ public GarbageCollectCommand setExpire(Date expire) {
 	 * Whether to use aggressive mode or not. If set to true JGit behaves more
 	 * similar to native git's "git gc --aggressive". If set to
 	 * <code>true</code> compressed objects found in old packs are not reused
-	 * but every object is compressed again. Configuration variables
-	 * pack.window and pack.depth are set to 250 for this GC.
+	 * but every object is compressed again. Configuration variables pack.window
+	 * and pack.depth are set to 250 for this GC.
 	 *
 	 * @since 3.6
 	 * @param aggressive
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
index 240290f..1da71aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
@@ -19,6 +19,7 @@
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Repository;
@@ -44,6 +45,8 @@ public class InitCommand implements Callable<Git> {
 
 	private String initialBranch;
 
+	private boolean relativePaths;
+
 	/**
 	 * {@inheritDoc}
 	 * <p>
@@ -100,7 +103,11 @@ public Git call() throws GitAPIException {
 					: initialBranch);
 			Repository repository = builder.build();
 			if (!repository.getObjectDatabase().exists())
-				repository.create(bare);
+				if (repository instanceof FileRepository) {
+					((FileRepository) repository).create(bare, relativePaths);
+				} else {
+					repository.create(bare);
+				}
 			return new Git(repository, true);
 		} catch (IOException | ConfigInvalidException e) {
 			throw new JGitInternalException(e.getMessage(), e);
@@ -214,4 +221,18 @@ public InitCommand setInitialBranch(String branch)
 		this.initialBranch = branch;
 		return this;
 	}
+
+	/**
+	 * * Set whether the repository shall use relative paths for GIT_DIR and
+	 * GIT_WORK_TREE
+	 *
+	 * @param relativePaths
+	 *            if true, use relative paths for GIT_DIR and GIT_WORK_TREE
+	 * @return {@code this}
+	 * @since 7.2
+	 */
+	public InitCommand setRelativeDirs(boolean relativePaths) {
+		this.relativePaths = relativePaths;
+		return this;
+	}
 }
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/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 858bd96..3ae7a6c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -18,6 +18,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -1835,23 +1837,26 @@ PersonIdent parseAuthor(byte[] raw) {
 
 		// the time is saved as <seconds since 1970> <timezone offset>
 		int timeStart = 0;
-		if (time.startsWith("@")) //$NON-NLS-1$
+		if (time.startsWith("@")) { //$NON-NLS-1$
 			timeStart = 1;
-		else
+		} else {
 			timeStart = 0;
-		long when = Long
-				.parseLong(time.substring(timeStart, time.indexOf(' '))) * 1000;
+		}
+		Instant when = Instant.ofEpochSecond(
+				Long.parseLong(time.substring(timeStart, time.indexOf(' '))));
 		String tzOffsetString = time.substring(time.indexOf(' ') + 1);
 		int multiplier = -1;
-		if (tzOffsetString.charAt(0) == '+')
+		if (tzOffsetString.charAt(0) == '+') {
 			multiplier = 1;
+		}
 		int hours = Integer.parseInt(tzOffsetString.substring(1, 3));
 		int minutes = Integer.parseInt(tzOffsetString.substring(3, 5));
 		// this is in format (+/-)HHMM (hours and minutes)
-		// we need to convert into minutes
-		int tz = (hours * 60 + minutes) * multiplier;
-		if (name != null && email != null)
+		ZoneOffset tz = ZoneOffset.ofHoursMinutes(hours * multiplier,
+				minutes * multiplier);
+		if (name != null && email != null) {
 			return new PersonIdent(name, email, when, tz);
+		}
 		return null;
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index dead274..a149649 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -68,7 +68,7 @@ public Collection<ReflogEntry> call() throws GitAPIException,
 		checkCallable();
 
 		try {
-			ReflogReader reader = repo.getReflogReader(ref);
+			ReflogReader reader = repo.getRefDatabase().getReflogReader(ref);
 			if (reader == null)
 				throw new RefNotFoundException(MessageFormat.format(
 						JGitText.get().refNotResolved, ref));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 855c3b1..6643c83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2024 Christian Halstrick <christian.halstrick@sap.com> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -143,8 +143,8 @@ public RevCommit call() throws NoMessageException, UnmergedPathsException,
 				merger.setCommitNames(new String[] {
 						"BASE", ourName, revertName }); //$NON-NLS-1$
 
-				String shortMessage = "Revert \"" + srcCommit.getShortMessage() //$NON-NLS-1$
-						+ "\""; //$NON-NLS-1$
+				String shortMessage = "Revert \"" //$NON-NLS-1$
+						+ srcCommit.getFirstMessageLine() + '"';
 				String newMessage = shortMessage + "\n\n" //$NON-NLS-1$
 						+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$
 						+ ".\n"; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index 23fbe01..2dba0ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -165,7 +165,8 @@ public ObjectId call() throws GitAPIException {
 
 		List<ReflogEntry> entries;
 		try {
-			ReflogReader reader = repo.getReflogReader(R_STASH);
+			ReflogReader reader = repo.getRefDatabase()
+					.getReflogReader(R_STASH);
 			if (reader == null) {
 				throw new RefNotFoundException(MessageFormat
 						.format(JGitText.get().refNotResolved, stashRef));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index 401f069..5105dfc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -176,8 +176,9 @@ public Repository call() throws GitAPIException {
 		CloneCommand clone = Git.cloneRepository();
 		configure(clone);
 		clone.setDirectory(moduleDirectory);
-		clone.setGitDir(new File(new File(repo.getCommonDirectory(),
-				Constants.MODULES), path));
+		clone.setGitDir(new File(
+				new File(repo.getCommonDirectory(), Constants.MODULES), path));
+		clone.setRelativePaths(true);
 		clone.setURI(resolvedUri);
 		if (monitor != null)
 			clone.setProgressMonitor(monitor);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index 751dabc..5e4b2ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.LockFile;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -39,6 +40,7 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.submodule.SubmoduleWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.util.FileUtils;
 
 /**
  * A class used to execute a submodule update command.
@@ -62,6 +64,8 @@ public class SubmoduleUpdateCommand extends
 
 	private boolean fetch = false;
 
+	private boolean clonedRestored;
+
 	/**
 	 * <p>
 	 * Constructor for SubmoduleUpdateCommand.
@@ -116,25 +120,77 @@ public SubmoduleUpdateCommand addPath(String path) {
 		return this;
 	}
 
+	private static boolean submoduleExists(File gitDir) {
+		if (gitDir != null && gitDir.isDirectory()) {
+			File[] files = gitDir.listFiles();
+			return files != null && files.length != 0;
+		}
+		return false;
+	}
+
+	private static void restoreSubmodule(File gitDir, File workingTree)
+			throws IOException {
+		LockFile dotGitLock = new LockFile(
+				new File(workingTree, Constants.DOT_GIT));
+		if (dotGitLock.lock()) {
+			String content = Constants.GITDIR
+					+ getRelativePath(gitDir, workingTree);
+			dotGitLock.write(Constants.encode(content));
+			dotGitLock.commit();
+		}
+	}
+
+	private static String getRelativePath(File gitDir, File workingTree) {
+		File relPath;
+		try {
+			relPath = workingTree.toPath().relativize(gitDir.toPath())
+					.toFile();
+		} catch (IllegalArgumentException e) {
+			relPath = gitDir;
+		}
+		return FileUtils.pathToString(relPath);
+	}
+
+	private String determineUpdateMode(String mode) {
+		if (clonedRestored) {
+			return ConfigConstants.CONFIG_KEY_CHECKOUT;
+		}
+		return mode;
+	}
+
 	private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
 			throws IOException, GitAPIException {
 		Repository repository = generator.getRepository();
+		boolean restored = false;
+		boolean cloned = false;
 		if (repository == null) {
-			if (callback != null) {
-				callback.cloningSubmodule(generator.getPath());
+			File gitDir = new File(
+					new File(repo.getCommonDirectory(), Constants.MODULES),
+					generator.getPath());
+			if (submoduleExists(gitDir)) {
+				restoreSubmodule(gitDir, generator.getDirectory());
+				restored = true;
+				clonedRestored = true;
+				repository = generator.getRepository();
+			} else {
+				if (callback != null) {
+					callback.cloningSubmodule(generator.getPath());
+				}
+				CloneCommand clone = Git.cloneRepository();
+				configure(clone);
+				clone.setURI(url);
+				clone.setDirectory(generator.getDirectory());
+				clone.setGitDir(gitDir);
+				clone.setRelativePaths(true);
+				if (monitor != null) {
+					clone.setProgressMonitor(monitor);
+				}
+				repository = clone.call().getRepository();
+				cloned = true;
+				clonedRestored = true;
 			}
-			CloneCommand clone = Git.cloneRepository();
-			configure(clone);
-			clone.setURI(url);
-			clone.setDirectory(generator.getDirectory());
-			clone.setGitDir(
-					new File(new File(repo.getCommonDirectory(), Constants.MODULES),
-							generator.getPath()));
-			if (monitor != null) {
-				clone.setProgressMonitor(monitor);
-			}
-			repository = clone.call().getRepository();
-		} else if (this.fetch) {
+		}
+		if ((this.fetch || restored) && !cloned) {
 			if (fetchCallback != null) {
 				fetchCallback.fetchingSubmodule(generator.getPath());
 			}
@@ -171,15 +227,17 @@ public Collection<String> call() throws InvalidConfigurationException,
 					continue;
 				// Skip submodules not registered in parent repository's config
 				String url = generator.getConfigUrl();
-				if (url == null)
+				if (url == null) {
 					continue;
-
+				}
+				clonedRestored = false;
 				try (Repository submoduleRepo = getOrCloneSubmodule(generator,
 						url); RevWalk walk = new RevWalk(submoduleRepo)) {
 					RevCommit commit = walk
 							.parseCommit(generator.getObjectId());
 
-					String update = generator.getConfigUpdate();
+					String update = determineUpdateMode(
+							generator.getConfigUpdate());
 					if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
 						MergeCommand merge = new MergeCommand(submoduleRepo);
 						merge.include(commit);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index 77967df..2d499ca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -28,6 +28,8 @@
 import org.eclipse.jgit.blame.Candidate.HeadCandidate;
 import org.eclipse.jgit.blame.Candidate.ReverseCandidate;
 import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
+import org.eclipse.jgit.blame.cache.BlameCache;
+import org.eclipse.jgit.blame.cache.CacheRegion;
 import org.eclipse.jgit.diff.DiffAlgorithm;
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -129,8 +131,19 @@ public class BlameGenerator implements AutoCloseable {
 
 	/** Blame is currently assigned to this source. */
 	private Candidate outCandidate;
+
 	private Region outRegion;
 
+	private final BlameCache blameCache;
+
+	/**
+	 * Blame in reverse order needs the source lines, but we don't have them in
+	 * the cache. We need to ignore the cache in that case.
+	 */
+	private boolean useCache = true;
+
+	private final Stats stats = new Stats();
+
 	/**
 	 * Create a blame generator for the repository and path (relative to
 	 * repository)
@@ -142,6 +155,25 @@ public class BlameGenerator implements AutoCloseable {
 	 *            repository).
 	 */
 	public BlameGenerator(Repository repository, String path) {
+		this(repository, path, null);
+	}
+
+	/**
+	 * Create a blame generator for the repository and path (relative to
+	 * repository)
+	 *
+	 * @param repository
+	 *            repository to access revision data from.
+	 * @param path
+	 *            initial path of the file to start scanning (relative to the
+	 *            repository).
+	 * @param blameCache
+	 *            previously calculated blames. This generator will *not*
+	 *            populate it, just consume it.
+	 * @since 7.2
+	 */
+	public BlameGenerator(Repository repository, String path,
+			@Nullable BlameCache blameCache) {
 		this.repository = repository;
 		this.resultPath = PathFilter.create(path);
 
@@ -150,6 +182,7 @@ public BlameGenerator(Repository repository, String path) {
 		initRevPool(false);
 
 		remaining = -1;
+		this.blameCache = blameCache;
 	}
 
 	private void initRevPool(boolean reverse) {
@@ -159,10 +192,12 @@ private void initRevPool(boolean reverse) {
 		if (revPool != null)
 			revPool.close();
 
-		if (reverse)
+		if (reverse) {
+			useCache = false;
 			revPool = new ReverseWalk(getRepository());
-		else
+		} else {
 			revPool = new RevWalk(getRepository());
+		}
 
 		SEEN = revPool.newFlag("SEEN"); //$NON-NLS-1$
 		reader = revPool.getObjectReader();
@@ -245,6 +280,31 @@ public RenameDetector getRenameDetector() {
 	}
 
 	/**
+	 * Stats about this generator
+	 *
+	 * @return the stats of this generator
+	 * @since 7.2
+	 */
+	public Stats getStats() {
+		return stats;
+	}
+
+	/**
+	 * Enable/disable the use of cache (if present). Enabled by default.
+	 * <p>
+	 * If caller need source line numbers, the generator cannot use the cache
+	 * (source lines are not there). Use this method to disable the cache in
+	 * that case.
+	 *
+	 * @param useCache
+	 *            should this generator use the cache.
+	 * @since 7.2
+	 */
+	public void setUseCache(boolean useCache) {
+		this.useCache = useCache;
+	}
+
+	/**
 	 * Push a candidate blob onto the generator's traversal stack.
 	 * <p>
 	 * Candidates should be pushed in history order from oldest-to-newest.
@@ -591,6 +651,20 @@ public boolean next() throws IOException {
 			Candidate n = pop();
 			if (n == null)
 				return done();
+			stats.candidatesVisited += 1;
+			if (blameCache != null && useCache) {
+				List<CacheRegion> cachedBlame = blameCache.get(repository,
+						n.sourceCommit, n.sourcePath.getPath());
+				if (cachedBlame != null) {
+					BlameRegionMerger rb = new BlameRegionMerger(repository,
+							revPool, cachedBlame);
+					Candidate fullyBlamed = rb.mergeCandidate(n);
+					if (fullyBlamed != null) {
+						stats.cacheHit = true;
+						return result(fullyBlamed);
+					}
+				}
+			}
 
 			int pCnt = n.getParentCount();
 			if (pCnt == 1) {
@@ -605,7 +679,7 @@ public boolean next() throws IOException {
 				// Do not generate a tip of a reverse. The region
 				// survives and should not appear to be deleted.
 
-			} else /* if (pCnt == 0) */{
+			} else /* if (pCnt == 0) */ {
 				// Root commit, with at least one surviving region.
 				// Assign the remaining blame here.
 				return result(n);
@@ -846,8 +920,8 @@ private boolean processMerge(Candidate n) throws IOException {
 				editList = new EditList(0);
 			} else {
 				p.loadText(reader);
-				editList = diffAlgorithm.diff(textComparator,
-						p.sourceText, n.sourceText);
+				editList = diffAlgorithm.diff(textComparator, p.sourceText,
+						n.sourceText);
 			}
 
 			if (editList.isEmpty()) {
@@ -981,6 +1055,10 @@ public int getRenameScore() {
 	/**
 	 * Get first line of the source data that has been blamed for the current
 	 * region
+	 * <p>
+	 * This value is not reliable when the generator is reusing cached values.
+	 * Cache doesn't keep the source lines, the returned value is based on the
+	 * result and can be off if the region moved in previous commits.
 	 *
 	 * @return first line of the source data that has been blamed for the
 	 *         current region. This is line number of where the region was added
@@ -994,6 +1072,10 @@ public int getSourceStart() {
 	/**
 	 * Get one past the range of the source data that has been blamed for the
 	 * current region
+	 * <p>
+	 * This value is not reliable when the generator is reusing cached values.
+	 * Cache doesn't keep the source lines, the returned value is based on the
+	 * result and can be off if the region moved in previous commits.
 	 *
 	 * @return one past the range of the source data that has been blamed for
 	 *         the current region. This is line number of where the region was
@@ -1124,4 +1206,39 @@ private static boolean isRename(DiffEntry ent) {
 		return ent.getChangeType() == ChangeType.RENAME
 				|| ent.getChangeType() == ChangeType.COPY;
 	}
+
+	/**
+	 * Stats about the work done by the generator
+	 *
+	 * @since 7.2
+	 */
+	public static class Stats {
+
+		/** Candidates taken from the queue */
+		private int candidatesVisited;
+
+		private boolean cacheHit;
+
+		/**
+		 * Number of candidates taken from the queue
+		 * <p>
+		 * The generator could signal it's done without exhausting all
+		 * candidates if there is no more remaining lines or the last visited
+		 * candidate is found in the cache.
+		 *
+		 * @return number of candidates taken from the queue
+		 */
+		public int getCandidatesVisited() {
+			return candidatesVisited;
+		}
+
+		/**
+		 * The generator found a blamed version in the cache
+		 *
+		 * @return true if we used results from the cache
+		 */
+		public boolean isCacheHit() {
+			return cacheHit;
+		}
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java
new file mode 100644
index 0000000..67bc6fb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+
+/**
+ * Translates an unblamed region into one or more blamed regions, using the
+ * fully blamed data from cache.
+ * <p>
+ * Blamed and unblamed regions are not symmetrical: An unblamed region is just a
+ * range of lines over the file. A blamed region is a Candidate (with the commit
+ * info) with a region inside (the range blamed).
+ */
+class BlameRegionMerger {
+	private final Repository repo;
+
+	private final List<CacheRegion> cachedRegions;
+
+	private final RevWalk rw;
+
+	BlameRegionMerger(Repository repo, RevWalk rw,
+			List<CacheRegion> cachedRegions) {
+		this.repo = repo;
+		List<CacheRegion> sorted = new ArrayList<>(cachedRegions);
+		Collections.sort(sorted);
+		this.cachedRegions = sorted;
+		this.rw = rw;
+	}
+
+	/**
+	 * Return one or more candidates blaming all the regions of the "unblamed"
+	 * incoming candidate.
+	 *
+	 * @param candidate
+	 *            a candidate with a list of unblamed regions
+	 * @return A linked list of Candidates with their blamed regions, null if
+	 *         there was any error.
+	 */
+	Candidate mergeCandidate(Candidate candidate) {
+		List<Candidate> newCandidates = new ArrayList<>();
+		Region r = candidate.regionList;
+		while (r != null) {
+			try {
+				newCandidates.addAll(mergeOneRegion(r));
+			} catch (IOException e) {
+				return null;
+			}
+			r = r.next;
+		}
+		return asLinkedCandidate(newCandidates);
+	}
+
+	// Visible for testing
+	List<Candidate> mergeOneRegion(Region region) throws IOException {
+		List<CacheRegion> overlaps = findOverlaps(region);
+		if (overlaps.isEmpty()) {
+			throw new IOException(
+					"Cached blame should cover all lines");
+		}
+		/*
+		 * Cached regions cover the whole file. We find first which ones overlap
+		 * with our unblamed region. Then we take the overlapping portions with
+		 * the corresponding blame.
+		 */
+		List<Candidate> candidates = new ArrayList<>();
+		for (CacheRegion overlap : overlaps) {
+			Region blamedRegions = intersectRegions(region, overlap);
+			Candidate c = new Candidate(repo, parse(overlap.getSourceCommit()),
+					PathFilter.create(overlap.getSourcePath()));
+			c.regionList = blamedRegions;
+			candidates.add(c);
+		}
+		return candidates;
+	}
+
+	// Visible for testing
+	List<CacheRegion> findOverlaps(Region unblamed) {
+		int unblamedStart = unblamed.sourceStart;
+		int unblamedEnd = unblamedStart + unblamed.length;
+		List<CacheRegion> overlapping = new ArrayList<>();
+		for (CacheRegion blamed : cachedRegions) {
+			// End is not included
+			if (blamed.getEnd() <= unblamedStart) {
+				// Blamed region is completely before
+				continue;
+			}
+
+			if (blamed.getStart() >= unblamedEnd) {
+				// Blamed region is completely after
+				// Blamed regions are sorted by start position, nothing will
+				// match anymore
+				break;
+			}
+			overlapping.add(blamed);
+		}
+		return overlapping;
+	}
+
+	// Visible for testing
+	/**
+	 * Calculate the intersection between a Region and a CacheRegion, adjusting
+	 * the start if needed.
+	 * <p>
+	 * This should be called only if there is an overlap (filtering the cached
+	 * regions with {@link #findOverlaps(Region)}), otherwise the result is
+	 * meaningless.
+	 *
+	 * @param unblamed
+	 *            a region from the blame generator
+	 * @param cached
+	 *            a cached region
+	 * @return a new region with the intersection.
+	 */
+	static Region intersectRegions(Region unblamed, CacheRegion cached) {
+		int blamedStart = Math.max(cached.getStart(), unblamed.sourceStart);
+		int blamedEnd = Math.min(cached.getEnd(),
+				unblamed.sourceStart + unblamed.length);
+		int length = blamedEnd - blamedStart;
+
+		// result start and source start should move together
+		int blameStartDelta = blamedStart - unblamed.sourceStart;
+		return new Region(unblamed.resultStart + blameStartDelta, blamedStart,
+				length);
+	}
+
+	// Tests can override this, so they don't need a real repo, commit and walk
+	protected RevCommit parse(ObjectId oid) throws IOException {
+		return rw.parseCommit(oid);
+	}
+
+	private static Candidate asLinkedCandidate(List<Candidate> c) {
+		Candidate head = c.get(0);
+		Candidate tail = head;
+		for (int i = 1; i < c.size(); i++) {
+			tail.queueNext = c.get(i);
+			tail = tail.queueNext;
+		}
+		return head;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
index 5e2746c..48f6b7e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
@@ -79,6 +79,7 @@ public static BlameResult create(BlameGenerator gen) throws IOException {
 
 	BlameResult(BlameGenerator bg, String path, RawText text) {
 		generator = bg;
+		generator.setUseCache(false);
 		resultPath = path;
 		resultContents = text;
 
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..cf3f978
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java
@@ -0,0 +1,125 @@
+/*
+ * 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.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+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;
+	}
+
+	@SuppressWarnings("nls")
+	@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(MessageFormat
+				.format(JGitText.get().cacheRegionAllOrNoneNull, path, commit));
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 6375a60..cbac3f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -1334,7 +1334,7 @@ private String getFuncName(RawText text, int startAt,
 					continue;
 				}
 				if (matchesAny(diffDriver.getMatchPatterns(), line)) {
-					String funcName = line.replaceAll("^[ \\t]+", "");
+					String funcName = line.replaceAll("^[ \\t]+", ""); //$NON-NLS-1$//$NON-NLS-2$
 					return funcName.substring(0,
 							Math.min(funcName.length(), 80)).trim();
 				}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java
index accf732..de02aec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java
@@ -217,10 +217,18 @@ public void checkout(DirCacheEntry entry, CheckoutMetadata metadata,
 			}
 		}
 		try {
-			if (recursiveDelete && Files.isDirectory(f.toPath(),
-					LinkOption.NOFOLLOW_LINKS)) {
+			boolean isDir = Files.isDirectory(f.toPath(),
+					LinkOption.NOFOLLOW_LINKS);
+			if (recursiveDelete && isDir) {
 				FileUtils.delete(f, FileUtils.RECURSIVE);
 			}
+			if (cache.getRepository().isWorkTreeCaseInsensitive() && !isDir) {
+				// We cannot rely on rename via Files.move() to work correctly
+				// if the target exists in a case variant. For instance with JDK
+				// 17 on Mac OS, the existing case-variant name is kept. On
+				// Windows 11 it would work and use the name given in 'f'.
+				FileUtils.delete(f, FileUtils.SKIP_MISSING);
+			}
 			FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
 			cachedParent.remove(f.getName());
 		} catch (IOException e) {
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/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 4f78404..18d7748 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -5,7 +5,7 @@
  * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
  * Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
  * Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
- * Copyright (C) 2017, 2023, Thomas Wolf <twolf@apache.org> and others
+ * Copyright (C) 2017, 2025, 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
@@ -31,6 +31,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.eclipse.jgit.api.errors.CanceledException;
 import org.eclipse.jgit.api.errors.FilterFailedException;
@@ -66,7 +67,6 @@
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FS.ExecutionResult;
-import org.eclipse.jgit.util.IntList;
 import org.eclipse.jgit.util.SystemReader;
 import org.eclipse.jgit.util.io.EolStreamTypeUtil;
 import org.slf4j.Logger;
@@ -113,9 +113,11 @@ public CheckoutMetadata(EolStreamType eolStreamType,
 
 	private Map<String, CheckoutMetadata> updated = new LinkedHashMap<>();
 
+	private Set<String> existing;
+
 	private ArrayList<String> conflicts = new ArrayList<>();
 
-	private ArrayList<String> removed = new ArrayList<>();
+	private TreeSet<String> removed;
 
 	private ArrayList<String> kept = new ArrayList<>();
 
@@ -185,7 +187,7 @@ public List<String> getToBeDeleted() {
 	 * @return a list of all files removed by this checkout
 	 */
 	public List<String> getRemoved() {
-		return removed;
+		return new ArrayList<>(removed);
 	}
 
 	/**
@@ -214,6 +216,14 @@ public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
 		this.mergeCommitTree = mergeCommitTree;
 		this.workingTree = workingTree;
 		this.initialCheckout = !repo.isBare() && !repo.getIndexFile().exists();
+		boolean caseInsensitive = !repo.isBare()
+				&& repo.isWorkTreeCaseInsensitive();
+		this.removed = caseInsensitive
+				? new TreeSet<>(String::compareToIgnoreCase)
+				: new TreeSet<>();
+		this.existing = caseInsensitive
+				? new TreeSet<>(String::compareToIgnoreCase)
+				: null;
 	}
 
 	/**
@@ -400,9 +410,11 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
 						// content to be checked out.
 						update(m);
 					}
-				} else
+				} else {
 					update(m);
-			} else if (f == null || !m.idEqual(i)) {
+				}
+			} else if (f == null || !m.idEqual(i)
+					|| m.getEntryRawMode() != i.getEntryRawMode()) {
 				// The working tree file is missing or the merge content differs
 				// from index content
 				update(m);
@@ -410,11 +422,11 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
 				// The index contains a file (and not a folder)
 				if (f.isModified(i.getDirCacheEntry(), true,
 						this.walk.getObjectReader())
-						|| i.getDirCacheEntry().getStage() != 0)
+						|| i.getDirCacheEntry().getStage() != 0) {
 					// The working tree file is dirty or the index contains a
 					// conflict
 					update(m);
-				else {
+				} else {
 					// update the timestamp of the index with the one from the
 					// file if not set, as we are sure to be in sync here.
 					DirCacheEntry entry = i.getDirCacheEntry();
@@ -424,9 +436,10 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
 					}
 					keep(i.getEntryPathString(), entry, f);
 				}
-			} else
+			} else {
 				// The index contains a folder
 				keep(i.getEntryPathString(), i.getDirCacheEntry(), f);
+			}
 		} else {
 			// There is no entry in the merge commit. Means: we want to delete
 			// what's currently in the index and working tree
@@ -521,6 +534,13 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 			// update our index
 			builder.finish();
 
+			// On case-insensitive file systems we may have a case variant kept
+			// and another one removed. In that case, don't remove it.
+			if (existing != null) {
+				removed.removeAll(existing);
+				existing.clear();
+			}
+
 			// init progress reporting
 			int numTotal = removed.size() + updated.size() + conflicts.size();
 			monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);
@@ -531,9 +551,9 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 			// when deleting files process them in the opposite order as they have
 			// been reported. This ensures the files are deleted before we delete
 			// their parent folders
-			IntList nonDeleted = new IntList();
-			for (int i = removed.size() - 1; i >= 0; i--) {
-				String r = removed.get(i);
+			Iterator<String> iter = removed.descendingIterator();
+			while (iter.hasNext()) {
+				String r = iter.next();
 				file = new File(repo.getWorkTree(), r);
 				if (!file.delete() && repo.getFS().exists(file)) {
 					// The list of stuff to delete comes from the index
@@ -542,7 +562,7 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 					// to delete it. A submodule is not empty, so it
 					// is safe to check this after a failed delete.
 					if (!repo.getFS().isDirectory(file)) {
-						nonDeleted.add(i);
+						iter.remove();
 						toBeDeleted.add(r);
 					}
 				} else {
@@ -560,8 +580,6 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 			if (file != null) {
 				removeEmptyParents(file);
 			}
-			removed = filterOut(removed, nonDeleted);
-			nonDeleted = null;
 			Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
 					.entrySet().iterator();
 			Map.Entry<String, CheckoutMetadata> e = null;
@@ -633,36 +651,6 @@ private boolean doCheckout() throws CorruptObjectException, IOException,
 		return toBeDeleted.isEmpty();
 	}
 
-	private static ArrayList<String> filterOut(ArrayList<String> strings,
-			IntList indicesToRemove) {
-		int n = indicesToRemove.size();
-		if (n == strings.size()) {
-			return new ArrayList<>(0);
-		}
-		switch (n) {
-		case 0:
-			return strings;
-		case 1:
-			strings.remove(indicesToRemove.get(0));
-			return strings;
-		default:
-			int length = strings.size();
-			ArrayList<String> result = new ArrayList<>(length - n);
-			// Process indicesToRemove from the back; we know that it
-			// contains indices in descending order.
-			int j = n - 1;
-			int idx = indicesToRemove.get(j);
-			for (int i = 0; i < length; i++) {
-				if (i == idx) {
-					idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
-				} else {
-					result.add(strings.get(i));
-				}
-			}
-			return result;
-		}
-	}
-
 	private static boolean isSamePrefix(String a, String b) {
 		int as = a.lastIndexOf('/');
 		int bs = b.lastIndexOf('/');
@@ -1233,6 +1221,9 @@ private void keep(String path, DirCacheEntry e, WorkingTreeIterator f)
 		if (!FileMode.TREE.equals(e.getFileMode())) {
 			builder.add(e);
 		}
+		if (existing != null) {
+			existing.add(path);
+		}
 		if (force) {
 			if (f == null || f.isModified(e, true, walk.getObjectReader())) {
 				kept.add(path);
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 c8012d6..bf252f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -106,6 +106,7 @@ public static JGitText get() {
 	/***/ public String buildingBitmaps;
 	/***/ public String cachedPacksPreventsIndexCreation;
 	/***/ public String cachedPacksPreventsListingObjects;
+	/***/ public String cacheRegionAllOrNoneNull;
 	/***/ public String cannotAccessLastModifiedForSafeDeletion;
 	/***/ public String cannotBeCombined;
 	/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
@@ -293,6 +294,7 @@ public static JGitText get() {
 	/***/ public String deleteTagUnexpectedResult;
 	/***/ public String deletingBranches;
 	/***/ public String deletingNotSupported;
+	/***/ public String deprecatedTrustFolderStat;
 	/***/ public String depthMustBeAt1;
 	/***/ public String depthWithUnshallow;
 	/***/ public String destinationIsNotAWildcard;
@@ -491,6 +493,7 @@ public static JGitText get() {
 	/***/ public String invalidTimeUnitValue2;
 	/***/ public String invalidTimeUnitValue3;
 	/***/ public String invalidTreeZeroLengthName;
+	/***/ public String invalidTrustStat;
 	/***/ public String invalidURL;
 	/***/ public String invalidWildcards;
 	/***/ public String invalidRefSpec;
@@ -555,6 +558,8 @@ public static JGitText get() {
 	/***/ public String month;
 	/***/ public String months;
 	/***/ public String monthsAgo;
+	/***/ public String multiPackIndexUnexpectedSize;
+	/***/ public String multiPackIndexWritingCancelled;
 	/***/ public String multipleMergeBasesFor;
 	/***/ public String nameMustNotBeNullOrEmpty;
 	/***/ public String need2Arguments;
@@ -643,6 +648,7 @@ public static JGitText get() {
 	/***/ public String personIdentEmailNonNull;
 	/***/ public String personIdentNameNonNull;
 	/***/ public String postCommitHookFailed;
+	/***/ public String precedenceTrustConfig;
 	/***/ public String prefixRemote;
 	/***/ public String problemWithResolvingPushRefSpecsLocally;
 	/***/ public String progressMonUploading;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index e6068a1..199481c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -90,7 +90,7 @@ public class DfsGarbageCollector {
 	private long coalesceGarbageLimit = 50 << 20;
 	private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);
 
-	private long startTimeMillis;
+	private Instant startTime;
 	private List<DfsPackFile> packsBefore;
 	private List<DfsReftable> reftablesBefore;
 	private List<DfsPackFile> expiredGarbagePacks;
@@ -352,7 +352,7 @@ public boolean pack(ProgressMonitor pm) throws IOException {
 			throw new IllegalStateException(
 					JGitText.get().supportOnlyPackIndexVersion2);
 
-		startTimeMillis = SystemReader.getInstance().getCurrentTime();
+		startTime = SystemReader.getInstance().now();
 		ctx = objdb.newReader();
 		try {
 			refdb.refresh();
@@ -435,7 +435,7 @@ private void readPacksBefore() throws IOException {
 		packsBefore = new ArrayList<>(packs.length);
 		expiredGarbagePacks = new ArrayList<>(packs.length);
 
-		long now = SystemReader.getInstance().getCurrentTime();
+		long now = SystemReader.getInstance().now().toEpochMilli();
 		for (DfsPackFile p : packs) {
 			DfsPackDescription d = p.getPackDescription();
 			if (d.getPackSource() != UNREACHABLE_GARBAGE) {
@@ -723,7 +723,7 @@ private DfsPackDescription writePack(PackSource source, PackWriter pw,
 
 		PackStatistics stats = pw.getStatistics();
 		pack.setPackStats(stats);
-		pack.setLastModified(startTimeMillis);
+		pack.setLastModified(startTime.toEpochMilli());
 		newPackDesc.add(pack);
 		newPackStats.add(stats);
 		newPackObj.add(pw.getObjectSet());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
index 3ba74b2..2751cd2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.RefList;
@@ -177,6 +178,11 @@ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> exclu
 	}
 
 	@Override
+	public ReflogReader getReflogReader(Ref ref) throws IOException {
+		return reftableDatabase.getReflogReader(ref.getName());
+	}
+
+	@Override
 	public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
 		if (!getReftableConfig().isIndexObjects()) {
 			return super.getTipsWithSha1(id);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
index 25b7583..d5a060f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
@@ -16,6 +16,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -23,6 +24,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
@@ -37,6 +39,7 @@
 import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
 import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -70,6 +73,8 @@ public class FileReftableDatabase extends RefDatabase {
 
 	private final FileReftableStack reftableStack;
 
+	private final AtomicBoolean autoRefresh;
+
 	FileReftableDatabase(FileRepository repo) throws IOException {
 		this(repo, new File(new File(repo.getCommonDirectory(), Constants.REFTABLE),
 				Constants.TABLES_LIST));
@@ -77,6 +82,9 @@ public class FileReftableDatabase extends RefDatabase {
 
 	FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
 		this.fileRepository = repo;
+		this.autoRefresh = new AtomicBoolean(repo.getConfig().getBoolean(
+				ConfigConstants.CONFIG_REFTABLE_SECTION,
+				ConfigConstants.CONFIG_KEY_AUTOREFRESH, false));
 		this.reftableStack = new FileReftableStack(refstackName,
 				new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
 			() -> fileRepository.fireEvent(new RefsChangedEvent()),
@@ -90,7 +98,13 @@ public MergedReftable openMergedReftable() throws IOException {
 		};
 	}
 
-	ReflogReader getReflogReader(String refname) throws IOException {
+	@Override
+	public ReflogReader getReflogReader(Ref ref) throws IOException {
+		return reftableDatabase.getReflogReader(ref.getName());
+	}
+
+	@Override
+	public ReflogReader getReflogReader(String refname) throws IOException {
 		return reftableDatabase.getReflogReader(refname);
 	}
 
@@ -177,6 +191,7 @@ public RefUpdate newUpdate(String refName, boolean detach)
 
 	@Override
 	public Ref exactRef(String name) throws IOException {
+		autoRefresh();
 		return reftableDatabase.exactRef(name);
 	}
 
@@ -187,6 +202,7 @@ public List<Ref> getRefs() throws IOException {
 
 	@Override
 	public Map<String, Ref> getRefs(String prefix) throws IOException {
+		autoRefresh();
 		List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
 		RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
 		for (Ref r : refs) {
@@ -199,6 +215,7 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
 	@Override
 	public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
 			throws IOException {
+		autoRefresh();
 		return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes);
 	}
 
@@ -217,6 +234,56 @@ public Ref peel(Ref ref) throws IOException {
 
 	}
 
+	/**
+	 * Whether to auto-refresh the reftable stack if it is out of date.
+	 *
+	 * @param autoRefresh
+	 *            whether to auto-refresh the reftable stack if it is out of
+	 *            date.
+	 */
+	public void setAutoRefresh(boolean autoRefresh) {
+		this.autoRefresh.set(autoRefresh);
+	}
+
+	/**
+	 * Whether the reftable stack is auto-refreshed if it is out of date.
+	 *
+	 * @return whether the reftable stack is auto-refreshed if it is out of
+	 *         date.
+	 */
+	public boolean isAutoRefresh() {
+		return autoRefresh.get();
+	}
+
+	private void autoRefresh() {
+		if (autoRefresh.get()) {
+			refresh();
+		}
+	}
+
+	/**
+	 * Check if the reftable stack is up to date, and if not, reload it.
+	 * <p>
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void refresh() {
+		try {
+			if (!reftableStack.isUpToDate()) {
+				ReentrantLock lock = getLock();
+				lock.lock();
+				try {
+					reftableDatabase.clearCache();
+					reftableStack.reload();
+				} finally {
+					lock.unlock();
+				}
+			}
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		}
+	}
+
 	private Ref doPeel(Ref leaf) throws IOException {
 		try (RevWalk rw = new RevWalk(fileRepository)) {
 			RevObject obj = rw.parseAny(leaf.getObjectId());
@@ -557,9 +624,10 @@ private static void writeConvertTable(Repository repo, ReftableWriter w,
 			boolean writeLogs) throws IOException {
 		int size = 0;
 		List<Ref> refs = repo.getRefDatabase().getRefs();
+		RefDatabase refDb = repo.getRefDatabase();
 		if (writeLogs) {
 			for (Ref r : refs) {
-				ReflogReader rlr = repo.getReflogReader(r.getName());
+				ReflogReader rlr = refDb.getReflogReader(r);
 				if (rlr != null) {
 					size = Math.max(rlr.getReverseEntries().size(), size);
 				}
@@ -582,10 +650,7 @@ private static void writeConvertTable(Repository repo, ReftableWriter w,
 		if (writeLogs) {
 			for (Ref r : refs) {
 				long idx = size;
-				ReflogReader reader = repo.getReflogReader(r.getName());
-				if (reader == null) {
-					continue;
-				}
+				ReflogReader reader = refDb.getReflogReader(r);
 				for (ReflogEntry e : reader.getReverseEntries()) {
 					w.writeLog(r.getName(), idx, e.getWho(), e.getOldId(),
 							e.getNewId(), e.getComment());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
index 0f5ff0f..b2c8892 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -18,8 +18,10 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.StandardCopyOption;
 import java.security.SecureRandom;
 import java.util.ArrayList;
@@ -27,6 +29,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -39,6 +42,8 @@
 import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
 import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.SystemReader;
 
@@ -59,6 +64,9 @@ private static class StackEntry {
 
 	private List<StackEntry> stack;
 
+	private AtomicReference<FileSnapshot> snapshot = new AtomicReference<>(
+			FileSnapshot.DIRTY);
+
 	private long lastNextUpdateIndex;
 
 	private final File stackPath;
@@ -98,6 +106,8 @@ static class CompactionStats {
 
 	private final CompactionStats stats;
 
+	private final TrustStat trustTablesListStat;
+
 	/**
 	 * Creates a stack corresponding to the list of reftables in the argument
 	 *
@@ -126,6 +136,8 @@ public FileReftableStack(File stackPath, File reftableDir,
 		reload();
 
 		stats = new CompactionStats();
+		trustTablesListStat = configSupplier.get().get(CoreConfig.KEY)
+				.getTrustTablesListStat();
 	}
 
 	CompactionStats getStats() {
@@ -272,8 +284,9 @@ public interface Writer {
 	}
 
 	private List<String> readTableNames() throws IOException {
+		FileSnapshot old;
 		List<String> names = new ArrayList<>(stack.size() + 1);
-
+		old = snapshot.get();
 		try (BufferedReader br = new BufferedReader(
 				new InputStreamReader(new FileInputStream(stackPath), UTF_8))) {
 			String line;
@@ -282,8 +295,10 @@ private List<String> readTableNames() throws IOException {
 					names.add(line);
 				}
 			}
+			snapshot.compareAndSet(old, FileSnapshot.save(stackPath));
 		} catch (FileNotFoundException e) {
 			// file isn't there: empty repository.
+			snapshot.compareAndSet(old, FileSnapshot.MISSING_FILE);
 		}
 		return names;
 	}
@@ -294,9 +309,28 @@ private List<String> readTableNames() throws IOException {
 	 *             on IO problem
 	 */
 	boolean isUpToDate() throws IOException {
-		// We could use FileSnapshot to avoid reading the file, but the file is
-		// small so it's probably a minor optimization.
 		try {
+			switch (trustTablesListStat) {
+			case NEVER:
+				break;
+			case AFTER_OPEN:
+				try (InputStream stream = Files
+						.newInputStream(stackPath.toPath())) {
+					// open the tables.list file to refresh attributes (on some
+					// NFS clients)
+				} catch (FileNotFoundException | NoSuchFileException e) {
+					// ignore
+				}
+				//$FALL-THROUGH$
+			case ALWAYS:
+				if (!snapshot.get().isModified(stackPath)) {
+					return true;
+				}
+				break;
+			case INHERIT:
+				// only used in CoreConfig internally
+				throw new IllegalStateException();
+			}
 			List<String> names = readTableNames();
 			if (names.size() != stack.size()) {
 				return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 84c8565..bcf9f1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -2,7 +2,7 @@
  * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  * Copyright (C) 2008-2010, Google Inc.
  * Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006-2024, Shawn O. Pearce <spearce@spearce.org> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -31,7 +31,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import org.eclipse.jgit.annotations.NonNull;
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
@@ -215,6 +214,16 @@ private void loadRepoConfig() throws IOException {
 		}
 	}
 
+	private String getRelativeDir(File base, File other) {
+		File relPath;
+		try {
+			relPath = base.toPath().relativize(other.toPath()).toFile();
+		} catch (IllegalArgumentException e) {
+			relPath = other;
+		}
+		return FileUtils.pathToString(relPath);
+	}
+
 	/**
 	 * {@inheritDoc}
 	 * <p>
@@ -223,6 +232,22 @@ private void loadRepoConfig() throws IOException {
 	 */
 	@Override
 	public void create(boolean bare) throws IOException {
+		create(bare, false);
+	}
+
+	/**
+	 * Create a new Git repository initializing the necessary files and
+	 * directories.
+	 *
+	 * @param bare
+	 *            if true, a bare repository (a repository without a working
+	 *            directory) is created.
+	 * @param relativePaths
+	 *            if true, relative paths are used for GIT_DIR and GIT_WORK_TREE
+	 * @throws IOException
+	 *             in case of IO problem
+	 */
+	public void create(boolean bare, boolean relativePaths) throws IOException {
 		final FileBasedConfig cfg = getConfig();
 		if (cfg.getFile().exists()) {
 			throw new IllegalStateException(MessageFormat.format(
@@ -293,15 +318,25 @@ && getDirectory().getName().startsWith(".")) //$NON-NLS-1$
 		if (!bare) {
 			File workTree = getWorkTree();
 			if (!getDirectory().getParentFile().equals(workTree)) {
+				String workTreePath;
+				String gitDirPath;
+				if (relativePaths) {
+					File canonGitDir = getDirectory().getCanonicalFile();
+					File canonWorkTree = getWorkTree().getCanonicalFile();
+					workTreePath = getRelativeDir(canonGitDir, canonWorkTree);
+					gitDirPath = getRelativeDir(canonWorkTree, canonGitDir);
+				} else {
+					workTreePath = getWorkTree().getAbsolutePath();
+					gitDirPath = getDirectory().getAbsolutePath();
+				}
 				cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
-								.getAbsolutePath());
+						ConfigConstants.CONFIG_KEY_WORKTREE, workTreePath);
 				LockFile dotGitLockFile = new LockFile(new File(workTree,
 						Constants.DOT_GIT));
 				try {
 					if (dotGitLockFile.lock()) {
 						dotGitLockFile.write(Constants.encode(Constants.GITDIR
-								+ getDirectory().getAbsolutePath()));
+								+ gitDirPath));
 						dotGitLockFile.commit();
 					}
 				} finally {
@@ -507,29 +542,6 @@ public void notifyIndexChanged(boolean internal) {
 	}
 
 	@Override
-	public ReflogReader getReflogReader(String refName) throws IOException {
-		if (refs instanceof FileReftableDatabase) {
-			// Cannot use findRef: reftable stores log data for deleted or renamed
-			// branches.
-			return ((FileReftableDatabase)refs).getReflogReader(refName);
-		}
-
-		// TODO: use exactRef here, which offers more predictable and therefore preferable
-		// behavior.
-		Ref ref = findRef(refName);
-		if (ref == null) {
-			return null;
-		}
-		return new ReflogReaderImpl(this, ref.getName());
-	}
-
-	@Override
-	public @NonNull ReflogReader getReflogReader(@NonNull Ref ref)
-			throws IOException {
-		return new ReflogReaderImpl(this, ref.getName());
-	}
-
-	@Override
 	public AttributesNodeProvider createAttributesNodeProvider() {
 		return new AttributesNodeProviderImpl(this);
 	}
@@ -661,8 +673,8 @@ void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
 			}
 
 			if (writeLogs) {
-				List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
-						.getReverseEntries();
+				ReflogReader reflogReader = oldDb.getReflogReader(r);
+				List<ReflogEntry> logs = reflogReader.getReverseEntries();
 				Collections.reverse(logs);
 				for (ReflogEntry e : logs) {
 					logWriter.log(r.getName(), e);
@@ -707,6 +719,8 @@ void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
 		}
 		repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
 				ConfigConstants.CONFIG_KEY_REF_STORAGE);
+		repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
 		repoConfig.save();
 	}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index b8911f5..b6bde6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -545,7 +545,8 @@ private FileStoreAttributes fileStoreAttributeCache() {
 		return fileStoreAttributeCache;
 	}
 
-	private static BasicFileAttributes getFileAttributes(File path) throws NoSuchElementException {
+	private static BasicFileAttributes getFileAttributes(File path)
+			throws NoSuchElementException {
 		try {
 			try {
 				return FS.DETECTED.fileAttributes(path);
@@ -565,5 +566,5 @@ private static BasicFileAttributes getFileAttributes(File path) throws NoSuchEle
 			LOG.error(e.getMessage(), e);
 		}
 		throw new NoSuchElementException(path.toString());
-  }
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 3105a3a..71294af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -102,9 +102,8 @@
 import org.eclipse.jgit.util.FS;
 import org.eclipse.jgit.util.FS.LockToken;
 import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.GitDateParser;
+import org.eclipse.jgit.util.GitTimeParser;
 import org.eclipse.jgit.util.StringUtils;
-import org.eclipse.jgit.util.SystemReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -160,11 +159,11 @@ public static void setExecutor(ExecutorService e) {
 
 	private long expireAgeMillis = -1;
 
-	private Date expire;
+	private Instant expire;
 
 	private long packExpireAgeMillis = -1;
 
-	private Date packExpire;
+	private Instant packExpire;
 
 	private Boolean packKeptObjects;
 
@@ -698,16 +697,18 @@ private long getExpireDate() throws ParseException {
 
 		if (expire == null && expireAgeMillis == -1) {
 			String pruneExpireStr = getPruneExpireStr();
-			if (pruneExpireStr == null)
+			if (pruneExpireStr == null) {
 				pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
-			expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
-					.getInstance().getLocale());
+			}
+			expire = GitTimeParser.parseInstant(pruneExpireStr);
 			expireAgeMillis = -1;
 		}
-		if (expire != null)
-			expireDate = expire.getTime();
-		if (expireAgeMillis != -1)
+		if (expire != null) {
+			expireDate = expire.toEpochMilli();
+		}
+		if (expireAgeMillis != -1) {
 			expireDate = System.currentTimeMillis() - expireAgeMillis;
+		}
 		return expireDate;
 	}
 
@@ -724,16 +725,18 @@ private long getPackExpireDate() throws ParseException {
 			String prunePackExpireStr = repo.getConfig().getString(
 					ConfigConstants.CONFIG_GC_SECTION, null,
 					ConfigConstants.CONFIG_KEY_PRUNEPACKEXPIRE);
-			if (prunePackExpireStr == null)
+			if (prunePackExpireStr == null) {
 				prunePackExpireStr = PRUNE_PACK_EXPIRE_DEFAULT;
-			packExpire = GitDateParser.parse(prunePackExpireStr, null,
-					SystemReader.getInstance().getLocale());
+			}
+			packExpire = GitTimeParser.parseInstant(prunePackExpireStr);
 			packExpireAgeMillis = -1;
 		}
-		if (packExpire != null)
-			packExpireDate = packExpire.getTime();
-		if (packExpireAgeMillis != -1)
+		if (packExpire != null) {
+			packExpireDate = packExpire.toEpochMilli();
+		}
+		if (packExpireAgeMillis != -1) {
 			packExpireDate = System.currentTimeMillis() - packExpireAgeMillis;
+		}
 		return packExpireDate;
 	}
 
@@ -1150,7 +1153,7 @@ private void deleteTempPacksIdx() {
 	 *             if an IO error occurred
 	 */
 	private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
-		ReflogReader reflogReader = repo.getReflogReader(ref);
+		ReflogReader reflogReader = repo.getRefDatabase().getReflogReader(ref);
 		List<ReflogEntry> rlEntries = reflogReader
 				.getReverseEntries();
 		if (rlEntries == null || rlEntries.isEmpty())
@@ -1653,12 +1656,31 @@ public void setPackConfig(@NonNull PackConfig pconfig) {
 	 * candidate for pruning.
 	 *
 	 * @param expire
-	 *            instant in time which defines object expiration
-	 *            objects with modification time before this instant are expired
-	 *            objects with modification time newer or equal to this instant
-	 *            are not expired
+	 *            instant in time which defines object expiration objects with
+	 *            modification time before this instant are expired objects with
+	 *            modification time newer or equal to this instant are not
+	 *            expired
+	 * @deprecated use {@link #setExpire(Instant)} instead
 	 */
+	@Deprecated(since = "7.2")
 	public void setExpire(Date expire) {
+		this.expire = expire.toInstant();
+		expireAgeMillis = -1;
+	}
+
+	/**
+	 * During gc() or prune() each unreferenced, loose object which has been
+	 * created or modified after or at <code>expire</code> will not be pruned.
+	 * Only older objects may be pruned. If set to null then every object is a
+	 * candidate for pruning.
+	 *
+	 * @param expire
+	 *            instant in time which defines object expiration objects with
+	 *            modification time before this instant are expired objects with
+	 *            modification time newer or equal to this instant are not
+	 *            expired
+	 */
+	public void setExpire(Instant expire) {
 		this.expire = expire;
 		expireAgeMillis = -1;
 	}
@@ -1671,8 +1693,24 @@ public void setExpire(Date expire) {
 	 *
 	 * @param packExpire
 	 *            instant in time which defines packfile expiration
+	 * @deprecated use {@link #setPackExpire(Instant)} instead
 	 */
+	@Deprecated(since = "7.2")
 	public void setPackExpire(Date packExpire) {
+		this.packExpire = packExpire.toInstant();
+		packExpireAgeMillis = -1;
+	}
+
+	/**
+	 * During gc() or prune() packfiles which are created or modified after or
+	 * at <code>packExpire</code> will not be deleted. Only older packfiles may
+	 * be deleted. If set to null then every packfile is a candidate for
+	 * deletion.
+	 *
+	 * @param packExpire
+	 *            instant in time which defines packfile expiration
+	 */
+	public void setPackExpire(Instant packExpire) {
 		this.packExpire = packExpire;
 		packExpireAgeMillis = -1;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
index 8647b3e..862aaab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -23,8 +23,7 @@
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.GitDateParser;
-import org.eclipse.jgit.util.SystemReader;
+import org.eclipse.jgit.util.GitTimeParser;
 
 /**
  * This class manages the gc.log file for a {@link FileRepository}.
@@ -62,8 +61,7 @@ private Instant getLogExpiry() throws ParseException {
 			if (logExpiryStr == null) {
 				logExpiryStr = LOG_EXPIRY_DEFAULT;
 			}
-			gcLogExpire = GitDateParser.parse(logExpiryStr, null,
-					SystemReader.getInstance().getLocale()).toInstant();
+			gcLogExpire = GitTimeParser.parseInstant(logExpiryStr);
 		}
 		return gcLogExpire;
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
index b4bb2a9..909b3e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
@@ -26,8 +26,9 @@
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.util.FileUtils;
@@ -49,13 +50,13 @@ class LooseObjects {
 	 * Maximum number of attempts to read a loose object for which a stale file
 	 * handle exception is thrown
 	 */
-	private final static int MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS = 5;
+	private final static int MAX_STALE_READ_RETRIES = 5;
 
 	private final File directory;
 
 	private final UnpackedObjectCache unpackedObjectCache;
 
-	private final boolean trustFolderStat;
+	private final TrustStat trustLooseObjectStat;
 
 	/**
 	 * Initialize a reference to an on-disk object directory.
@@ -68,9 +69,8 @@ class LooseObjects {
 	LooseObjects(Config config, File dir) {
 		directory = dir;
 		unpackedObjectCache = new UnpackedObjectCache();
-		trustFolderStat = config.getBoolean(
-				ConfigConstants.CONFIG_CORE_SECTION,
-				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+		trustLooseObjectStat = config.get(CoreConfig.KEY)
+				.getTrustLooseObjectStat();
 	}
 
 	/**
@@ -108,7 +108,8 @@ boolean hasCached(AnyObjectId id) {
 	 */
 	boolean has(AnyObjectId objectId) {
 		boolean exists = hasWithoutRefresh(objectId);
-		if (trustFolderStat || exists) {
+		if (trustLooseObjectStat == TrustStat.ALWAYS
+				|| exists) {
 			return exists;
 		}
 		try (InputStream stream = Files.newInputStream(directory.toPath())) {
@@ -163,13 +164,31 @@ boolean resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
 	}
 
 	ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException {
-		int readAttempts = 0;
-		while (readAttempts < MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS) {
-			readAttempts++;
-			File path = fileFor(id);
-			if (trustFolderStat && !path.exists()) {
+		File path = fileFor(id);
+		for (int retries = 0; retries < MAX_STALE_READ_RETRIES; retries++) {
+			boolean reload = true;
+			switch (trustLooseObjectStat) {
+			case NEVER:
 				break;
+			case AFTER_OPEN:
+				try (InputStream stream = Files
+						.newInputStream(path.getParentFile().toPath())) {
+					// open the loose object's fanout directory to refresh
+					// attributes (on some NFS clients)
+				} catch (FileNotFoundException | NoSuchFileException e) {
+					// ignore
+				}
+				//$FALL-THROUGH$
+			case ALWAYS:
+				if (!path.exists()) {
+					reload = false;
+				}
+				break;
+			case INHERIT:
+				// only used in CoreConfig internally
+				throw new IllegalStateException();
 			}
+			if (reload) {
 			try {
 				return getObjectLoader(curs, path, id);
 			} catch (FileNotFoundException noFile) {
@@ -183,9 +202,10 @@ ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException {
 				}
 				if (LOG.isDebugEnabled()) {
 					LOG.debug(MessageFormat.format(
-							JGitText.get().looseObjectHandleIsStale, id.name(),
-							Integer.valueOf(readAttempts), Integer.valueOf(
-									MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS)));
+								JGitText.get().looseObjectHandleIsStale,
+								id.name(), Integer.valueOf(retries),
+								Integer.valueOf(MAX_STALE_READ_RETRIES)));
+					}
 				}
 			}
 		}
@@ -211,7 +231,7 @@ ObjectLoader getObjectLoader(WindowCursor curs, File path, AnyObjectId id)
 		try {
 			return getObjectLoaderWithoutRefresh(curs, path, id);
 		} catch (FileNotFoundException e) {
-			if (trustFolderStat) {
+			if (trustLooseObjectStat == TrustStat.ALWAYS) {
 				throw e;
 			}
 			try (InputStream stream = Files
@@ -248,7 +268,7 @@ long getSize(WindowCursor curs, AnyObjectId id) throws IOException {
 			return getSizeWithoutRefresh(curs, id);
 		} catch (FileNotFoundException noFile) {
 			try {
-				if (trustFolderStat) {
+				if (trustLooseObjectStat == TrustStat.ALWAYS) {
 					throw noFile;
 				}
 				try (InputStream stream = Files
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
index 466e87f..544961b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
@@ -17,6 +17,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,7 +45,8 @@
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.util.FileUtils;
@@ -74,7 +77,7 @@ class PackDirectory {
 
 	private final AtomicReference<PackList> packList;
 
-	private final boolean trustFolderStat;
+	private final TrustStat trustPackStat;
 
 	/**
 	 * Initialize a reference to an on-disk 'pack' directory.
@@ -88,14 +91,7 @@ class PackDirectory {
 		this.config = config;
 		this.directory = directory;
 		packList = new AtomicReference<>(NO_PACKS);
-
-		// Whether to trust the pack folder's modification time. If set to false
-		// we will always scan the .git/objects/pack folder to check for new
-		// pack files. If set to true (default) we use the folder's size,
-		// modification time, and key (inode) and assume that no new pack files
-		// can be in this folder if these attributes have not changed.
-		trustFolderStat = config.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
-				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+		trustPackStat = config.get(CoreConfig.KEY).getTrustPackStat();
 	}
 
 	/**
@@ -317,38 +313,42 @@ private int checkRescanPackThreshold(int retries, PackMismatchException e)
 	}
 
 	private void handlePackError(IOException e, Pack p) {
-		String warnTmpl = null;
+		String warnTemplate = null;
+		String debugTemplate = null;
 		int transientErrorCount = 0;
-		String errTmpl = JGitText.get().exceptionWhileReadingPack;
+		String errorTemplate = JGitText.get().exceptionWhileReadingPack;
 		if ((e instanceof CorruptObjectException)
 				|| (e instanceof PackInvalidException)) {
-			warnTmpl = JGitText.get().corruptPack;
-			LOG.warn(MessageFormat.format(warnTmpl,
+			warnTemplate = JGitText.get().corruptPack;
+			LOG.warn(MessageFormat.format(warnTemplate,
 					p.getPackFile().getAbsolutePath()), e);
 			// Assume the pack is corrupted, and remove it from the list.
 			remove(p);
 		} else if (e instanceof FileNotFoundException) {
 			if (p.getPackFile().exists()) {
-				errTmpl = JGitText.get().packInaccessible;
+				errorTemplate = JGitText.get().packInaccessible;
 				transientErrorCount = p.incrementTransientErrorCount();
 			} else {
-				warnTmpl = JGitText.get().packWasDeleted;
+				debugTemplate = JGitText.get().packWasDeleted;
 				remove(p);
 			}
 		} else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
-			warnTmpl = JGitText.get().packHandleIsStale;
+			warnTemplate = JGitText.get().packHandleIsStale;
 			remove(p);
 		} else {
 			transientErrorCount = p.incrementTransientErrorCount();
 		}
-		if (warnTmpl != null) {
-			LOG.warn(MessageFormat.format(warnTmpl,
+		if (warnTemplate != null) {
+			LOG.warn(MessageFormat.format(warnTemplate,
 					p.getPackFile().getAbsolutePath()), e);
+		} else if (debugTemplate != null) {
+			LOG.debug(MessageFormat.format(debugTemplate,
+				p.getPackFile().getAbsolutePath()), e);
 		} else {
 			if (doLogExponentialBackoff(transientErrorCount)) {
 				// Don't remove the pack from the list, as the error may be
 				// transient.
-				LOG.error(MessageFormat.format(errTmpl,
+				LOG.error(MessageFormat.format(errorTemplate,
 						p.getPackFile().getAbsolutePath(),
 						Integer.valueOf(transientErrorCount)), e);
 			}
@@ -365,8 +365,26 @@ private boolean doLogExponentialBackoff(int n) {
 	}
 
 	boolean searchPacksAgain(PackList old) {
-		return (!trustFolderStat || old.snapshot.isModified(directory))
-				&& old != scanPacks(old);
+		switch (trustPackStat) {
+		case NEVER:
+			break;
+		case AFTER_OPEN:
+			try (InputStream stream = Files
+					.newInputStream(directory.toPath())) {
+				// open the pack directory to refresh attributes (on some NFS clients)
+			} catch (IOException e) {
+				// ignore
+			}
+			//$FALL-THROUGH$
+		case ALWAYS:
+			if (!old.snapshot.isModified(directory)) {
+				return false;
+			}
+			break;
+		case INHERIT:
+			// only used in CoreConfig internally
+		}
+		return old != scanPacks(old);
 	}
 
 	void insert(Pack pack) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
index 7189ce2..b3e4efb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
@@ -286,7 +286,7 @@ long findCRC32(AnyObjectId objId)
 	 *             the index cannot be read.
 	 */
 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
-			int matchLimit) throws IOException;
+				 int matchLimit) throws IOException;
 
 	/**
 	 * Get pack checksum
@@ -304,6 +304,7 @@ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
 	class MutableEntry {
 		/** Buffer of the ObjectId visited by the EntriesIterator. */
 		final MutableObjectId idBuffer = new MutableObjectId();
+
 		/** Offset into the packfile of the current object. */
 		long offset;
 
@@ -345,6 +346,34 @@ public MutableEntry cloneEntry() {
 			r.offset = offset;
 			return r;
 		}
+
+		/**
+		 * Similar to {@link Comparable#compareTo(Object)}, using only the
+		 * object id in the entry.
+		 *
+		 * @param other
+		 *            Another mutable entry (probably from another index)
+		 *
+		 * @return a negative integer, zero, or a positive integer as this
+		 *         object is less than, equal to, or greater than the specified
+		 *         object.
+		 */
+		public int compareBySha1To(MutableEntry other) {
+			return idBuffer.compareTo(other.idBuffer);
+		}
+
+		/**
+		 * Copy the current ObjectId to dest
+		 * <p>
+		 * Like {@link #toObjectId()}, but reusing the destination instead of
+		 * creating a new ObjectId instance.
+		 *
+		 * @param dest
+		 *            destination for the object id
+		 */
+		public void copyOidTo(MutableObjectId dest) {
+			dest.fromObjectId(idBuffer);
+		}
 	}
 
 	/**
@@ -368,7 +397,6 @@ protected EntriesIterator(long objectCount) {
 			this.objectCount = objectCount;
 		}
 
-
 		@Override
 		public boolean hasNext() {
 			return returnedNumber < objectCount;
@@ -393,7 +421,6 @@ public MutableEntry next() {
 		 */
 		protected abstract void readNext();
 
-
 		/**
 		 * Copies to the entry an {@link ObjectId} from the int buffer and
 		 * position idx
@@ -423,7 +450,8 @@ protected void setIdBuffer(byte[] raw, int idx) {
 		/**
 		 * Sets the {@code offset} to the entry
 		 *
-		 * @param offset the offset in the pack file
+		 * @param offset
+		 *            the offset in the pack file
 		 */
 		protected void setOffset(long offset) {
 			entry.offset = offset;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 552cba4..319a9ed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -64,10 +64,9 @@
 import org.eclipse.jgit.errors.ObjectWritingException;
 import org.eclipse.jgit.events.RefsChangedEvent;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig.TrustLooseRefStat;
-import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -76,6 +75,7 @@
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefWriter;
+import org.eclipse.jgit.lib.ReflogReader;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.SymbolicRef;
 import org.eclipse.jgit.revwalk.RevObject;
@@ -184,11 +184,7 @@ public class RefDirectory extends RefDatabase {
 
 	private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
 
-	private final boolean trustFolderStat;
-
-	private final TrustPackedRefsStat trustPackedRefsStat;
-
-	private final TrustLooseRefStat trustLooseRefStat;
+	private final CoreConfig coreConfig;
 
 	RefDirectory(RefDirectory refDb) {
 		parent = refDb.parent;
@@ -200,9 +196,7 @@ public class RefDirectory extends RefDatabase {
 		packedRefsFile = refDb.packedRefsFile;
 		looseRefs.set(refDb.looseRefs.get());
 		packedRefs.set(refDb.packedRefs.get());
-		trustFolderStat = refDb.trustFolderStat;
-		trustPackedRefsStat = refDb.trustPackedRefsStat;
-		trustLooseRefStat = refDb.trustLooseRefStat;
+		coreConfig = refDb.coreConfig;
 		inProcessPackedRefsLock = refDb.inProcessPackedRefsLock;
 	}
 
@@ -218,17 +212,7 @@ public class RefDirectory extends RefDatabase {
 
 		looseRefs.set(RefList.<LooseRef> emptyList());
 		packedRefs.set(NO_PACKED_REFS);
-		trustFolderStat = db.getConfig()
-				.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
-						ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
-		trustPackedRefsStat = db.getConfig()
-				.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
-						TrustPackedRefsStat.UNSET);
-		trustLooseRefStat = db.getConfig()
-				.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
-						ConfigConstants.CONFIG_KEY_TRUST_LOOSE_REF_STAT,
-						TrustLooseRefStat.ALWAYS);
+		coreConfig = db.getConfig().get(CoreConfig.KEY);
 		inProcessPackedRefsLock = new ReentrantLock(true);
 	}
 
@@ -456,6 +440,11 @@ public List<Ref> getAdditionalRefs() throws IOException {
 		return ret;
 	}
 
+	@Override
+	public ReflogReader getReflogReader(Ref ref) throws IOException {
+		return new ReflogReaderImpl(getRepository(), ref.getName());
+	}
+
 	@SuppressWarnings("unchecked")
 	private RefList<Ref> upcast(RefList<? extends Ref> loose) {
 		return (RefList<Ref>) loose;
@@ -979,7 +968,7 @@ else if (0 <= (idx = packed.find(dst.getName())))
 	PackedRefList getPackedRefs() throws IOException {
 		final PackedRefList curList = packedRefs.get();
 
-		switch (trustPackedRefsStat) {
+		switch (coreConfig.getTrustPackedRefsStat()) {
 		case NEVER:
 			break;
 		case AFTER_OPEN:
@@ -995,12 +984,8 @@ PackedRefList getPackedRefs() throws IOException {
 				return curList;
 			}
 			break;
-		case UNSET:
-			if (trustFolderStat
-					&& !curList.snapshot.isModified(packedRefsFile)) {
-				return curList;
-			}
-			break;
+		case INHERIT:
+			// only used in CoreConfig internally
 		}
 
 		return refreshPackedRefs(curList);
@@ -1186,7 +1171,7 @@ private Ref readRef(String name, RefList<Ref> packed) throws IOException {
 	LooseRef scanRef(LooseRef ref, String name) throws IOException {
 		final File path = fileFor(name);
 
-		if (trustLooseRefStat.equals(TrustLooseRefStat.AFTER_OPEN)) {
+		if (coreConfig.getTrustLooseRefStat() == TrustStat.AFTER_OPEN) {
 			refreshPathToLooseRef(Paths.get(name));
 		}
 
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index 36a7ece..9c60d36 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
@@ -791,7 +791,9 @@ private void queueRemoveAll(Set<Pack> packs) {
 			}
 			numRemovers++;
 		}
-		for (int numRemoved = 0; removeNextBlock(numRemoved); numRemoved++);
+		for (int numRemoved = 0; removeNextBlock(numRemoved); numRemoved++) {
+			// empty
+		}
 		synchronized (this) {
 			if (numRemovers > 0) {
 				numRemovers--;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java
new file mode 100644
index 0000000..6122a9a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+class MultiPackIndexConstants {
+	static final int MIDX_SIGNATURE = 0x4d494458; /* MIDX */
+
+	static final byte MIDX_VERSION = 1;
+
+	/**
+	 * We infer the length of object IDs (OIDs) from this value:
+	 * 
+	 * <pre>
+	 * 1 => SHA-1
+	 * 2 => SHA-256
+	 * </pre>
+	 */
+	static final byte OID_HASH_VERSION = 1;
+
+	static final int MULTIPACK_INDEX_FANOUT_SIZE = 4 * 256;
+
+	/**
+	 * First 4 bytes describe the chunk id. Value 0 is a terminating label.
+	 * Other 8 bytes provide the byte-offset in current file for chunk to start.
+	 */
+	static final int CHUNK_LOOKUP_WIDTH = 12;
+
+	/** "PNAM" chunk */
+	static final int MIDX_CHUNKID_PACKNAMES = 0x504e414d;
+
+	/** "OIDF" chunk */
+	static final int MIDX_CHUNKID_OIDFANOUT = 0x4f494446;
+
+	/** "OIDL" chunk */
+	static final int MIDX_CHUNKID_OIDLOOKUP = 0x4f49444c;
+
+	/** "OOFF" chunk */
+	static final int MIDX_CHUNKID_OBJECTOFFSETS = 0x4f4f4646;
+
+	/** "LOFF" chunk */
+	static final int MIDX_CHUNKID_LARGEOFFSETS = 0x4c4f4646;
+
+	/** "RIDX" chunk */
+	static final int MIDX_CHUNKID_REVINDEX = 0x52494458;
+
+	private MultiPackIndexConstants() {
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java
new file mode 100644
index 0000000..795d39e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Prints a multipack index file in a human-readable format.
+ *
+ * @since 7.2
+ */
+@SuppressWarnings({ "boxing", "nls" })
+public class MultiPackIndexPrettyPrinter {
+
+	/**
+	 * Writes to out, in human-readable format, the multipack index in rawMidx
+	 *
+	 * @param rawMidx the bytes of a multipack index
+	 * @param out a writer
+	 */
+	public static void prettyPrint(byte[] rawMidx, PrintWriter out) {
+		// Header (12 bytes)
+		out.println("[ 0] Magic: " + new String(rawMidx, 0, 4, UTF_8));
+		out.println("[ 4] Version number: " + (int) rawMidx[4]);
+		out.println("[ 5] OID version: " + (int) rawMidx[5]);
+		int chunkCount = rawMidx[6];
+		out.println("[ 6] # of chunks: " + chunkCount);
+		out.println("[ 7] # of bases: " + (int) rawMidx[7]);
+		int numberOfPacks = NB.decodeInt32(rawMidx, 8);
+		out.println("[ 8] # of packs: " + numberOfPacks);
+
+		// Chunk lookup table
+		List<ChunkSegment> chunkSegments = new ArrayList<>();
+		int current = printChunkLookup(out, rawMidx, chunkCount, chunkSegments);
+
+		for (int i = 0; i < chunkSegments.size() - 1; i++) {
+			ChunkSegment segment = chunkSegments.get(i);
+			if (current != segment.startOffset()) {
+				throw new IllegalStateException(String.format(
+						"We are at byte %d, but segment should start at %d",
+						current, segment.startOffset()));
+			}
+			out.printf("Starting chunk: %s @ %d%n", segment.chunkName(),
+					segment.startOffset());
+			switch (segment.chunkName()) {
+				case "OIDF" -> current = printOIDF(out, rawMidx, current);
+				case "OIDL" -> current = printOIDL(out, rawMidx, current,
+						chunkSegments.get(i + 1).startOffset);
+				case "OOFF" -> current = printOOFF(out, rawMidx, current,
+						chunkSegments.get(i + 1).startOffset);
+				case "PNAM" -> current = printPNAM(out, rawMidx, current,
+						chunkSegments.get(i + 1).startOffset);
+				case "RIDX" -> current = printRIDX(out, rawMidx, current,
+						chunkSegments.get(i + 1).startOffset);
+				default -> {
+					out.printf(
+							"Skipping %s (don't know how to print it yet)%n",
+							segment.chunkName());
+					current = (int) chunkSegments.get(i + 1).startOffset();
+				}
+			}
+		}
+		// Checksum is a SHA-1, use ObjectId to parse it
+		out.printf("[ %d] Checksum %s%n", current,
+				ObjectId.fromRaw(rawMidx, current).name());
+		out.printf("Total size: " + (current + 20));
+	}
+
+	private static int printChunkLookup(PrintWriter out, byte[] rawMidx, int chunkCount,
+			List<ChunkSegment> chunkSegments) {
+		out.println("Starting chunk lookup @ 12");
+		int current = 12;
+		for (int i = 0; i < chunkCount; i++) {
+			String chunkName = new String(rawMidx, current, 4, UTF_8);
+			long offset = NB.decodeInt64(rawMidx, current + 4);
+			out.printf("[ %d] |%8s|%8d|%n", current, chunkName, offset);
+			current += CHUNK_LOOKUP_WIDTH;
+			chunkSegments.add(new ChunkSegment(chunkName, offset));
+		}
+		String chunkName = "0000";
+		long offset = NB.decodeInt64(rawMidx, current + 4);
+		out.printf("[ %d] |%8s|%8d|%n", current, chunkName, offset);
+		current += CHUNK_LOOKUP_WIDTH;
+		chunkSegments.add(new ChunkSegment(chunkName, offset));
+		return current;
+	}
+
+	private static int printOIDF(PrintWriter out, byte[] rawMidx, int start) {
+		int current = start;
+		for (short i = 0; i < 256; i++) {
+			out.printf("[ %d] (%02X) %d%n", current, i,
+					NB.decodeInt32(rawMidx, current));
+			current += 4;
+		}
+		return current;
+	}
+
+	private static int printOIDL(PrintWriter out, byte[] rawMidx, int start, long end) {
+		int i = start;
+		while (i < end) {
+			out.printf("[ %d] %s%n", i,
+					ObjectId.fromRaw(rawMidx, i).name());
+			i += 20;
+		}
+		return i;
+	}
+
+	private static int printOOFF(PrintWriter out, byte[] rawMidx, int start, long end) {
+		int i = start;
+		while (i < end) {
+			out.printf("[ %d] %d %d%n", i, NB.decodeInt32(rawMidx, i),
+					NB.decodeInt32(rawMidx, i + 4));
+			i += 8;
+		}
+		return i;
+	}
+
+	private static int printRIDX(PrintWriter out, byte[] rawMidx, int start, long end) {
+		int i = start;
+		while (i < end) {
+			out.printf("[ %d] %d%n", i, NB.decodeInt32(rawMidx, i));
+			i += 4;
+		}
+		return (int) end;
+	}
+
+	private static int printPNAM(PrintWriter out, byte[] rawMidx, int start, long end) {
+		int nameStart = start;
+		for (int i = start; i < end; i++) {
+			if (rawMidx[i] == 0) {
+				out
+						.println(new String(rawMidx, nameStart, i - nameStart));
+				nameStart = i + 1;
+			}
+		}
+		return (int) end;
+	}
+
+	private record ChunkSegment(String chunkName, long startOffset) {
+	}
+}
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
new file mode 100644
index 0000000..bddf3ac
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_LARGEOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_REVINDEX;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_SIGNATURE;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_VERSION;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MULTIPACK_INDEX_FANOUT_SIZE;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.OID_HASH_VERSION;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
+import org.eclipse.jgit.internal.storage.midx.PackIndexMerger.MidxMutableEntry;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Writes a collection of indexes as a multipack index.
+ * <p>
+ * 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 {
+
+	private static final int LIMIT_31_BITS = (1 << 31) - 1;
+
+	private static final int MIDX_HEADER_SIZE = 12;
+
+	/**
+	 * Writes the inputs in the multipack index format in the outputStream.
+	 *
+	 * @param monitor
+	 *            progress monitor
+	 * @param outputStream
+	 *            stream to write the multipack index file
+	 * @param inputs
+	 *            pairs of name and index for each pack to include in the
+	 *            multipack index.
+	 * @throws IOException
+	 *             Error writing to the stream
+	 */
+	public void write(ProgressMonitor monitor, OutputStream outputStream,
+			Map<String, PackIndex> inputs) throws IOException {
+		PackIndexMerger data = new PackIndexMerger(inputs);
+
+		// List of chunks in the order they need to be written
+		List<ChunkHeader> chunkHeaders = createChunkHeaders(data);
+		long expectedSize = calculateExpectedSize(chunkHeaders);
+		try (CancellableDigestOutputStream out = new CancellableDigestOutputStream(
+				monitor, outputStream)) {
+			writeHeader(out, chunkHeaders.size(), data.getPackCount());
+			writeChunkLookup(out, chunkHeaders);
+
+			WriteContext ctx = new WriteContext(out, data);
+			for (ChunkHeader chunk : chunkHeaders) {
+				chunk.writerFn.write(ctx);
+			}
+			writeCheckSum(out);
+			if (expectedSize != out.length()) {
+				throw new IllegalStateException(String.format(
+						JGitText.get().multiPackIndexUnexpectedSize,
+						Long.valueOf(expectedSize),
+						Long.valueOf(out.length())));
+			}
+		} catch (InterruptedIOException e) {
+			throw new IOException(JGitText.get().multiPackIndexWritingCancelled,
+					e);
+		}
+	}
+
+	private static long calculateExpectedSize(List<ChunkHeader> chunks) {
+		int chunkLookup = (chunks.size() + 1) * CHUNK_LOOKUP_WIDTH;
+		long chunkContent = chunks.stream().mapToLong(c -> c.size).sum();
+		return /* header */ 12 + chunkLookup + chunkContent + /* CRC */ 20;
+	}
+
+	private List<ChunkHeader> createChunkHeaders(PackIndexMerger data) {
+		List<ChunkHeader> chunkHeaders = new ArrayList<>();
+		chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OIDFANOUT,
+				MULTIPACK_INDEX_FANOUT_SIZE, this::writeFanoutTable));
+		chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OIDLOOKUP,
+				(long) data.getUniqueObjectCount() * OBJECT_ID_LENGTH,
+				this::writeOidLookUp));
+		chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OBJECTOFFSETS,
+				8L * data.getUniqueObjectCount(), this::writeObjectOffsets));
+		if (data.needsLargeOffsetsChunk()) {
+			chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_LARGEOFFSETS,
+					8L * data.getOffsetsOver31BitsCount(),
+					this::writeObjectLargeOffsets));
+		}
+		chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_REVINDEX,
+				4L * data.getUniqueObjectCount(), this::writeRidx));
+
+		int packNamesSize = data.getPackNames().stream()
+				.mapToInt(String::length).map(i -> i + 1 /* null at the end */)
+				.sum();
+		chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_PACKNAMES, packNamesSize,
+				this::writePackfileNames));
+		return chunkHeaders;
+	}
+
+	/**
+	 * Write the first 12 bytes of the multipack index.
+	 * <p>
+	 * These bytes include things like magic number, version, number of
+	 * chunks...
+	 *
+	 * @param out
+	 *            output stream to write
+	 * @param numChunks
+	 *            number of chunks this multipack index is going to have
+	 * @param packCount
+	 *            number of packs covered by this multipack index
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeHeader(CancellableDigestOutputStream out, int numChunks,
+			int packCount) throws IOException {
+		byte[] headerBuffer = new byte[MIDX_HEADER_SIZE];
+		NB.encodeInt32(headerBuffer, 0, MIDX_SIGNATURE);
+		byte[] buff = { MIDX_VERSION, OID_HASH_VERSION, (byte) numChunks,
+				(byte) 0 };
+		System.arraycopy(buff, 0, headerBuffer, 4, 4);
+		NB.encodeInt32(headerBuffer, 8, packCount);
+		out.write(headerBuffer, 0, headerBuffer.length);
+		out.flush();
+	}
+
+	/**
+	 * Write a table of "chunkId, start-offset", with a special value "0,
+	 * end-of-previous_chunk", to mark the end.
+	 *
+	 * @param out
+	 *            output stream to write
+	 * @param chunkHeaders
+	 *            list of chunks in the order they are expected to be written
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeChunkLookup(CancellableDigestOutputStream out,
+			List<ChunkHeader> chunkHeaders) throws IOException {
+
+		// first chunk will start at header + this lookup block
+		long chunkStart = MIDX_HEADER_SIZE
+				+ (long) (chunkHeaders.size() + 1) * CHUNK_LOOKUP_WIDTH;
+		byte[] chunkEntry = new byte[CHUNK_LOOKUP_WIDTH];
+		for (ChunkHeader chunkHeader : chunkHeaders) {
+			NB.encodeInt32(chunkEntry, 0, chunkHeader.chunkId);
+			NB.encodeInt64(chunkEntry, 4, chunkStart);
+			out.write(chunkEntry);
+			chunkStart += chunkHeader.size;
+		}
+		// Terminating label for the block
+		// (chunkid 0, offset where the next block would start)
+		NB.encodeInt32(chunkEntry, 0, 0);
+		NB.encodeInt64(chunkEntry, 4, chunkStart);
+		out.write(chunkEntry);
+	}
+
+	/**
+	 * Write the fanout table for the object ids
+	 * <p>
+	 * Table with 256 entries (one byte), where the ith entry, F[i], stores the
+	 * number of OIDs with first byte at most i. Thus, F[255] stores the total
+	 * number of objects.
+	 *
+	 * @param ctx
+	 *            write context
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+
+	private void writeFanoutTable(WriteContext ctx) throws IOException {
+		byte[] tmp = new byte[4];
+		int[] fanout = new int[256];
+		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		while (iterator.hasNext()) {
+			MidxMutableEntry e = iterator.next();
+			fanout[e.getObjectId().getFirstByte() & 0xff]++;
+		}
+		for (int i = 1; i < fanout.length; i++) {
+			fanout[i] += fanout[i - 1];
+		}
+		for (int n : fanout) {
+			NB.encodeInt32(tmp, 0, n);
+			ctx.out.write(tmp, 0, 4);
+		}
+	}
+
+	/**
+	 * Write the OID lookup chunk
+	 * <p>
+	 * A list of OIDs in sha1 order.
+	 *
+	 * @param ctx
+	 *            write context
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeOidLookUp(WriteContext ctx) throws IOException {
+		byte[] tmp = new byte[OBJECT_ID_LENGTH];
+
+		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		while (iterator.hasNext()) {
+			MidxMutableEntry e = iterator.next();
+			e.getObjectId().copyRawTo(tmp, 0);
+			ctx.out.write(tmp, 0, OBJECT_ID_LENGTH);
+		}
+	}
+
+	/**
+	 * Write the object offsets chunk
+	 * <p>
+	 * A list of offsets, parallel to the list of OIDs. If the offset is too
+	 * large (see {@link #fitsIn31bits(long)}), this contains the position in
+	 * the large offsets list (marked with a 1 in the most significant bit).
+	 *
+	 * @param ctx
+	 *            write context
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeObjectOffsets(WriteContext ctx) throws IOException {
+		byte[] entry = new byte[8];
+		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		while (iterator.hasNext()) {
+			MidxMutableEntry e = iterator.next();
+			NB.encodeInt32(entry, 0, e.getPackId());
+			if (!ctx.data.needsLargeOffsetsChunk()
+					|| fitsIn31bits(e.getOffset())) {
+				NB.encodeInt32(entry, 4, (int) e.getOffset());
+			} else {
+				int offloadedPosition = ctx.largeOffsets.append(e.getOffset());
+				NB.encodeInt32(entry, 4, offloadedPosition | (1 << 31));
+			}
+			ctx.out.write(entry);
+		}
+	}
+
+	/**
+	 * Writes the reverse index chunk
+	 * <p>
+	 * This stores the position of the objects in the main index, ordered first
+	 * by pack and then by offset
+	 *
+	 * @param ctx
+	 *            write context
+	 * @throws IOException
+	 *             erorr writing to the output stream
+	 */
+	private void writeRidx(WriteContext ctx) throws IOException {
+		Map<Integer, List<OffsetPosition>> packOffsets = new HashMap<>(
+				ctx.data.getPackCount());
+		// TODO(ifrade): Brute force solution loading all offsets/packs in
+		// memory. We could also iterate reverse indexes looking up
+		// their position in the midx (and discarding if the pack doesn't
+		// match).
+		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		int midxPosition = 0;
+		while (iterator.hasNext()) {
+			MidxMutableEntry e = iterator.next();
+			OffsetPosition op = new OffsetPosition(e.getOffset(), midxPosition);
+			midxPosition++;
+			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(Integer.valueOf(i));
+			if (offsetsForPack.isEmpty()) {
+				continue;
+			}
+			offsetsForPack.sort(Comparator.comparing(OffsetPosition::offset));
+			byte[] ridxForPack = new byte[4 * offsetsForPack.size()];
+			for (int j = 0; j < offsetsForPack.size(); j++) {
+				NB.encodeInt32(ridxForPack, j * 4,
+						offsetsForPack.get(j).position);
+			}
+			ctx.out.write(ridxForPack);
+		}
+	}
+
+	/**
+	 * Write the large offset chunk
+	 * <p>
+	 * A list of large offsets (long). The regular offset chunk will point to a
+	 * position here.
+	 *
+	 * @param ctx
+	 *            writer context
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeObjectLargeOffsets(WriteContext ctx) throws IOException {
+		ctx.out.write(ctx.largeOffsets.offsets, 0,
+				ctx.largeOffsets.bytePosition);
+	}
+
+	/**
+	 * Write the list of packfiles chunk
+	 * <p>
+	 * List of packfiles (in lexicographical order) with an \0 at the end
+	 *
+	 * @param ctx
+	 *            writer context
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writePackfileNames(WriteContext ctx) throws IOException {
+		for (String packName : ctx.data.getPackNames()) {
+			// Spec doesn't talk about encoding.
+			ctx.out.write(packName.getBytes(StandardCharsets.UTF_8));
+			ctx.out.write(0);
+		}
+	}
+
+	/**
+	 * Write final checksum of the data written to the stream
+	 *
+	 * @param out
+	 *            output stream used to write
+	 * @throws IOException
+	 *             error writing to the output stream
+	 */
+	private void writeCheckSum(CancellableDigestOutputStream out)
+			throws IOException {
+		out.write(out.getDigest());
+		out.flush();
+	}
+
+
+	private record OffsetPosition(long offset, int position) {
+	}
+
+	/**
+	 * If there is at least one offset value larger than 2^32-1, then the large
+	 * offset chunk must exist, and offsets larger than 2^31-1 must be stored in
+	 * it instead
+	 *
+	 * @param offset object offset
+	 *
+	 * @return true if the offset fits in 31 bits
+	 */
+	private static boolean fitsIn31bits(long offset) {
+		return offset <= LIMIT_31_BITS;
+	}
+
+	private static class LargeOffsets {
+		private final byte[] offsets;
+
+		private int bytePosition;
+
+		LargeOffsets(int largeOffsetsCount) {
+			offsets = new byte[largeOffsetsCount * 8];
+			bytePosition = 0;
+		}
+
+		/**
+		 * Add an offset to the large offset chunk
+		 *
+		 * @param largeOffset
+		 *            a large offset
+		 * @return the position of the just inserted offset (as in number of
+		 *         offsets, NOT in bytes)
+		 */
+		int append(long largeOffset) {
+			int at = bytePosition;
+			NB.encodeInt64(offsets, at, largeOffset);
+			bytePosition += 8;
+			return at / 8;
+		}
+	}
+
+	private record ChunkHeader(int chunkId, long size, ChunkWriter writerFn) {
+	}
+
+	@FunctionalInterface
+	private interface ChunkWriter {
+		void write(WriteContext ctx) throws IOException;
+	}
+
+	private static class WriteContext {
+		final CancellableDigestOutputStream out;
+
+		final PackIndexMerger data;
+
+		final LargeOffsets largeOffsets;
+
+		WriteContext(CancellableDigestOutputStream out, PackIndexMerger data) {
+			this.out = out;
+			this.data = data;
+			this.largeOffsets = new LargeOffsets(
+					data.getOffsetsOver31BitsCount());
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
new file mode 100644
index 0000000..89814af
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2025, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+
+/**
+ * Collect the stats and offers an iterator over the union of n-pack indexes.
+ * <p>
+ * The multipack index is a list of (sha1, packid, offset) ordered by sha1. We
+ * can build it from the individual pack indexes (sha1, offset) ordered by sha1,
+ * with a simple merge ignoring duplicates.
+ * <p>
+ * This class encapsulates the merging logic and precalculates the stats that
+ * the index needs (like total count of objects). To limit memory consumption,
+ * it does the merge as it goes during the iteration and iterators use mutable
+ * entries. The stats of the combined index are calculated in an iteration at
+ * construction time.
+ */
+class PackIndexMerger {
+
+	private static final int LIMIT_31_BITS = (1 << 31) - 1;
+
+	private static final long LIMIT_32_BITS = (1L << 32) - 1;
+
+	/**
+	 * Object returned by the iterator.
+	 * <p>
+	 * The iterator returns (on each next()) the same instance with different
+	 * values, to avoid allocating many short-lived objects. Callers should not
+	 * keep a reference to that returned value.
+	 */
+	static class MidxMutableEntry {
+		// The object id
+		private final MutableObjectId oid = new MutableObjectId();
+
+		// Position of the pack in the ordered list of pack in this merger
+		private int packId;
+
+		// Offset in its pack
+		private long offset;
+
+		public AnyObjectId getObjectId() {
+			return oid;
+		}
+
+		public int getPackId() {
+			return packId;
+		}
+
+		public long getOffset() {
+			return offset;
+		}
+
+		/**
+		 * Copy values from another mutable entry
+		 *
+		 * @param packId
+		 *            packId
+		 * @param other
+		 *            another mutable entry
+		 */
+		private void fill(int packId, PackIndex.MutableEntry other) {
+			other.copyOidTo(oid);
+			this.packId = packId;
+			this.offset = other.getOffset();
+		}
+	}
+
+	private final List<String> packNames;
+
+	private final List<PackIndex> indexes;
+
+	private final boolean needsLargeOffsetsChunk;
+
+	private final int offsetsOver31BitsCount;
+
+	private final int uniqueObjectCount;
+
+	PackIndexMerger(Map<String, PackIndex> packs) {
+		this.packNames = packs.keySet().stream().sorted()
+				.collect(Collectors.toUnmodifiableList());
+
+		this.indexes = packNames.stream().map(packs::get)
+				.collect(Collectors.toUnmodifiableList());
+
+		// Iterate for duplicates
+		int objectCount = 0;
+		boolean hasLargeOffsets = false;
+		int over31bits = 0;
+		MutableObjectId lastSeen = new MutableObjectId();
+		MultiIndexIterator it = new MultiIndexIterator(indexes);
+		while (it.hasNext()) {
+			MidxMutableEntry entry = it.next();
+			if (lastSeen.equals(entry.oid)) {
+				continue;
+			}
+			// If there is at least one offset value larger than 2^32-1, then
+			// the large offset chunk must exist, and offsets larger than
+			// 2^31-1 must be stored in it instead
+			if (entry.offset > LIMIT_32_BITS) {
+				hasLargeOffsets = true;
+			}
+			if (entry.offset > LIMIT_31_BITS) {
+				over31bits++;
+			}
+
+			lastSeen.fromObjectId(entry.oid);
+			objectCount++;
+		}
+		uniqueObjectCount = objectCount;
+		offsetsOver31BitsCount = over31bits;
+		needsLargeOffsetsChunk = hasLargeOffsets;
+	}
+
+	/**
+	 * Object count of the merged index (i.e. without duplicates)
+	 *
+	 * @return object count of the merged index
+	 */
+	int getUniqueObjectCount() {
+		return uniqueObjectCount;
+	}
+
+	/**
+	 * If any object in any of the indexes has an offset over 2^32-1
+	 *
+	 * @return true if there is any object with offset > 2^32 -1
+	 */
+	boolean needsLargeOffsetsChunk() {
+		return needsLargeOffsetsChunk;
+	}
+
+	/**
+	 * How many object have offsets over 2^31-1
+	 * <p>
+	 * Per multipack index spec, IF there is large offset chunk, all this
+	 * offsets should be there.
+	 *
+	 * @return number of objects with offsets over 2^31-1
+	 */
+	int getOffsetsOver31BitsCount() {
+		return offsetsOver31BitsCount;
+	}
+
+	/**
+	 * List of pack names in alphabetical order.
+	 * <p>
+	 * Order matters: In case of duplicates, the multipack index prefers the
+	 * first package with it. This is in the same order we are using to
+	 * prioritize duplicates.
+	 *
+	 * @return List of pack names, in the order used by the merge.
+	 */
+	List<String> getPackNames() {
+		return packNames;
+	}
+
+	/**
+	 * How many packs are being merged
+	 *
+	 * @return count of packs merged
+	 */
+	int getPackCount() {
+		return packNames.size();
+	}
+
+	/**
+	 * Iterator over the merged indexes in sha1 order without duplicates
+	 * <p>
+	 * The returned entry in the iterator is mutable, callers should NOT keep a
+	 * reference to it.
+	 *
+	 * @return an iterator in sha1 order without duplicates.
+	 */
+	Iterator<MidxMutableEntry> bySha1Iterator() {
+		return new DedupMultiIndexIterator(new MultiIndexIterator(indexes),
+				getUniqueObjectCount());
+	}
+
+	/**
+	 * For testing. Iterate all entries, not skipping duplicates (stable order)
+	 *
+	 * @return an iterator of all objects in sha1 order, including duplicates.
+	 */
+	Iterator<MidxMutableEntry> rawIterator() {
+		return new MultiIndexIterator(indexes);
+	}
+
+	/**
+	 * Iterator over n-indexes in ObjectId order.
+	 * <p>
+	 * It returns duplicates if the same object id is in different indexes. Wrap
+	 * it with {@link DedupMultiIndexIterator (Iterator, int)} to avoid
+	 * duplicates.
+	 */
+	private static final class MultiIndexIterator
+			implements Iterator<MidxMutableEntry> {
+
+		private final List<PackIndexPeekIterator> indexIterators;
+
+		private final MidxMutableEntry mutableEntry = new MidxMutableEntry();
+
+		MultiIndexIterator(List<PackIndex> indexes) {
+			this.indexIterators = new ArrayList<>(indexes.size());
+			for (int i = 0; i < indexes.size(); i++) {
+				PackIndexPeekIterator it = new PackIndexPeekIterator(i,
+						indexes.get(i));
+				// Position in the first element
+				if (it.next() != null) {
+					indexIterators.add(it);
+				}
+			}
+		}
+
+		@Override
+		public boolean hasNext() {
+			return !indexIterators.isEmpty();
+		}
+
+		@Override
+		public MidxMutableEntry next() {
+			PackIndexPeekIterator winner = null;
+			for (int index = 0; index < indexIterators.size(); index++) {
+				PackIndexPeekIterator current = indexIterators.get(index);
+				if (winner == null
+						|| current.peek().compareBySha1To(winner.peek()) < 0) {
+					winner = current;
+				}
+			}
+
+			if (winner == null) {
+				throw new NoSuchElementException();
+			}
+
+			mutableEntry.fill(winner.getPackId(), winner.peek());
+			if (winner.next() == null) {
+				indexIterators.remove(winner);
+			};
+			return mutableEntry;
+		}
+	}
+
+	private static class DedupMultiIndexIterator
+			implements Iterator<MidxMutableEntry> {
+		private final MultiIndexIterator src;
+
+		private int remaining;
+
+		private final MutableObjectId lastOid = new MutableObjectId();
+
+		DedupMultiIndexIterator(MultiIndexIterator src, int totalCount) {
+			this.src = src;
+			this.remaining = totalCount;
+		}
+
+		@Override
+		public boolean hasNext() {
+			return remaining > 0;
+		}
+
+		@Override
+		public MidxMutableEntry next() {
+			MidxMutableEntry next = src.next();
+			while (next != null && lastOid.equals(next.oid)) {
+				next = src.next();
+			}
+
+			if (next == null) {
+				throw new NoSuchElementException();
+			}
+
+			lastOid.fromObjectId(next.oid);
+			remaining--;
+			return next;
+		}
+	}
+
+	/**
+	 * Convenience around the PackIndex iterator to read the current value
+	 * multiple times without consuming it.
+	 * <p>
+	 * This is used to merge indexes in the multipack index, where we need to
+	 * compare the current value between indexes multiple times to find the
+	 * next.
+	 * <p>
+	 * We could also implement this keeping the position (int) and
+	 * MutableEntry#getObjectId, but that would create an ObjectId per entry.
+	 * This implementation reuses the MutableEntry and avoid instantiations.
+	 */
+	// Visible for testing
+	static class PackIndexPeekIterator {
+		private final Iterator<PackIndex.MutableEntry> it;
+
+		private final int packId;
+
+		PackIndex.MutableEntry current;
+
+		PackIndexPeekIterator(int packId, PackIndex index) {
+			it = index.iterator();
+			this.packId = packId;
+		}
+
+		PackIndex.MutableEntry next() {
+			if (it.hasNext()) {
+				current = it.next();
+			} else {
+				current = null;
+			}
+			return current;
+		}
+
+		PackIndex.MutableEntry peek() {
+			return current;
+		}
+
+		int getPackId() {
+			return packId;
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index dabc1f0..bf87c4c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -14,6 +14,7 @@
 import static org.eclipse.jgit.revwalk.RevFlag.SEEN;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -28,16 +29,16 @@
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.revwalk.AddUnseenToBitmapFilter;
 import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
+import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper;
-import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
 import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
 import org.eclipse.jgit.revwalk.BitmapWalker;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -99,10 +100,10 @@ class PackWriterBitmapPreparer {
 		this.excessiveBranchCount = config.getBitmapExcessiveBranchCount();
 		this.excessiveBranchTipCount = Math.max(excessiveBranchCount,
 				config.getBitmapExcessiveBranchTipCount());
-		long now = SystemReader.getInstance().getCurrentTime();
+		Instant now = SystemReader.getInstance().now();
 		long ageInSeconds = (long) config.getBitmapInactiveBranchAgeInDays()
 				* DAY_IN_SECONDS;
-		this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds;
+		this.inactiveBranchTimestamp = now.getEpochSecond() - ageInSeconds;
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
index d07713d..e9ff027 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
@@ -32,6 +32,8 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.Arrays;
 import java.util.zip.DataFormatException;
 import java.util.zip.Inflater;
@@ -245,9 +247,9 @@ private String readValueString() {
 	private PersonIdent readPersonIdent() {
 		String name = readValueString();
 		String email = readValueString();
-		long ms = readVarint64() * 1000;
-		int tz = readInt16();
-		return new PersonIdent(name, email, ms, tz);
+		long epochSeconds = readVarint64();
+		ZoneOffset tz = ZoneOffset.ofTotalSeconds(readInt16() * 60);
+		return new PersonIdent(name, email, Instant.ofEpochSecond(epochSeconds), tz);
 	}
 
 	void readBlock(BlockSource src, long pos, int fileBlockSize)
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/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
index f701a41..b1ba5df 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
@@ -119,7 +119,7 @@ private CommitConfig(Config rc) {
 		if (!StringUtils.isEmptyOrNull(comment)) {
 			if ("auto".equalsIgnoreCase(comment)) { //$NON-NLS-1$
 				autoCommentChar = true;
-			} else {
+			} else if (comment != null) {
 				char first = comment.charAt(0);
 				if (first > ' ' && first < 127) {
 					commentCharacter = first;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index 07c5fa4..345cb22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -34,6 +34,7 @@
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.events.ConfigChangedEvent;
 import org.eclipse.jgit.events.ConfigChangedListener;
@@ -254,9 +255,8 @@ static String escapeSubsection(String x) {
 	 *            default value to return if no value was present.
 	 * @return an integer value from the configuration, or defaultValue.
 	 */
-	public int getInt(final String section, final String name,
-			final int defaultValue) {
-		return typedGetter.getInt(this, section, null, name, defaultValue);
+	public int getInt(String section, String name, int defaultValue) {
+		return getInt(section, null, name, defaultValue);
 	}
 
 	/**
@@ -264,6 +264,23 @@ public int getInt(final String section, final String name,
 	 *
 	 * @param section
 	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Integer getInt(String section, String name) {
+		return getInt(section, null, name);
+	}
+
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
 	 * @param subsection
 	 *            subsection name, such a remote or branch name.
 	 * @param name
@@ -272,10 +289,30 @@ public int getInt(final String section, final String name,
 	 *            default value to return if no value was present.
 	 * @return an integer value from the configuration, or defaultValue.
 	 */
-	public int getInt(final String section, String subsection,
-			final String name, final int defaultValue) {
+	public int getInt(String section, String subsection, String name,
+			int defaultValue) {
+		Integer v = typedGetter.getInt(this, section, subsection, name,
+				Integer.valueOf(defaultValue));
+		return v == null ? defaultValue : v.intValue();
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Integer getInt(String section, String subsection, String name) {
 		return typedGetter.getInt(this, section, subsection, name,
-				defaultValue);
+				null);
 	}
 
 	/**
@@ -297,8 +334,30 @@ public int getInt(final String section, String subsection,
 	 */
 	public int getIntInRange(String section, String name, int minValue,
 			int maxValue, int defaultValue) {
-		return typedGetter.getIntInRange(this, section, null, name, minValue,
-				maxValue, defaultValue);
+		return getIntInRange(section, null, name,
+				minValue, maxValue, defaultValue);
+	}
+
+	/**
+	 * Obtain an integer value from the configuration which must be inside given
+	 * range.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimum value
+	 * @param maxValue
+	 *            maximum value
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Integer getIntInRange(String section, String name, int minValue,
+			int maxValue) {
+		return getIntInRange(section, null, name, minValue, maxValue);
 	}
 
 	/**
@@ -322,8 +381,34 @@ public int getIntInRange(String section, String name, int minValue,
 	 */
 	public int getIntInRange(String section, String subsection, String name,
 			int minValue, int maxValue, int defaultValue) {
+		Integer v = typedGetter.getIntInRange(this, section, subsection, name,
+				minValue, maxValue, Integer.valueOf(defaultValue));
+		return v == null ? defaultValue : v.intValue();
+	}
+
+	/**
+	 * Obtain an integer value from the configuration which must be inside given
+	 * range.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimum value
+	 * @param maxValue
+	 *            maximum value
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Integer getIntInRange(String section, String subsection, String name,
+			int minValue, int maxValue) {
 		return typedGetter.getIntInRange(this, section, subsection, name,
-				minValue, maxValue, defaultValue);
+				minValue, maxValue, null);
 	}
 
 	/**
@@ -338,7 +423,23 @@ public int getIntInRange(String section, String subsection, String name,
 	 * @return an integer value from the configuration, or defaultValue.
 	 */
 	public long getLong(String section, String name, long defaultValue) {
-		return typedGetter.getLong(this, section, null, name, defaultValue);
+		return getLong(section, null, name, defaultValue);
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Long getLong(String section, String name) {
+		return getLong(section, null, name);
 	}
 
 	/**
@@ -355,9 +456,28 @@ public long getLong(String section, String name, long defaultValue) {
 	 * @return an integer value from the configuration, or defaultValue.
 	 */
 	public long getLong(final String section, String subsection,
-			final String name, final long defaultValue) {
-		return typedGetter.getLong(this, section, subsection, name,
-				defaultValue);
+			String name, long defaultValue) {
+		Long v = typedGetter.getLong(this, section, subsection, name,
+				Long.valueOf(defaultValue));
+		return v == null ? defaultValue : v.longValue();
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @return an integer value from the configuration, or {@code null} if not
+	 *         set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Long getLong(String section, String subsection, String name) {
+		return typedGetter.getLong(this, section, subsection, name, null);
 	}
 
 	/**
@@ -372,9 +492,26 @@ public long getLong(final String section, String subsection,
 	 * @return true if any value or defaultValue is true, false for missing or
 	 *         explicit false
 	 */
-	public boolean getBoolean(final String section, final String name,
-			final boolean defaultValue) {
-		return typedGetter.getBoolean(this, section, null, name, defaultValue);
+	public boolean getBoolean(String section, String name,
+			boolean defaultValue) {
+		Boolean v = typedGetter.getBoolean(this, section, null, name,
+				Boolean.valueOf(defaultValue));
+		return v == null ? defaultValue : v.booleanValue();
+	}
+
+	/**
+	 * Get a boolean value from the git config
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @return configured boolean value, or {@code null} if not set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Boolean getBoolean(String section, String name) {
+		return getBoolean(section, null, name);
 	}
 
 	/**
@@ -391,10 +528,28 @@ public boolean getBoolean(final String section, final String name,
 	 * @return true if any value or defaultValue is true, false for missing or
 	 *         explicit false
 	 */
-	public boolean getBoolean(final String section, String subsection,
-			final String name, final boolean defaultValue) {
-		return typedGetter.getBoolean(this, section, subsection, name,
-				defaultValue);
+	public boolean getBoolean(String section, String subsection, String name,
+			boolean defaultValue) {
+		Boolean v = typedGetter.getBoolean(this, section, subsection, name,
+				Boolean.valueOf(defaultValue));
+		return v == null ? defaultValue : v.booleanValue();
+	}
+
+	/**
+	 * Get a boolean value from the git config
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @return configured boolean value, or {@code null} if not set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Boolean getBoolean(String section, String subsection, String name) {
+		return typedGetter.getBoolean(this, section, subsection, name, null);
 	}
 
 	/**
@@ -412,8 +567,8 @@ public boolean getBoolean(final String section, String subsection,
 	 *            default value to return if no value was present.
 	 * @return the selected enumeration value, or {@code defaultValue}.
 	 */
-	public <T extends Enum<?>> T getEnum(final String section,
-			final String subsection, final String name, final T defaultValue) {
+	public <T extends Enum<?>> T getEnum(String section, String subsection,
+			String name, @NonNull T defaultValue) {
 		final T[] all = allValuesOf(defaultValue);
 		return typedGetter.getEnum(this, all, section, subsection, name,
 				defaultValue);
@@ -448,14 +603,41 @@ public <T extends Enum<?>> T getEnum(final String section,
 	 * @param defaultValue
 	 *            default value to return if no value was present.
 	 * @return the selected enumeration value, or {@code defaultValue}.
+	 * @deprecated use {@link #getEnum(String, String, String, Enum)} or
+	 *             {{@link #getEnum(Enum[], String, String, String)}} instead.
 	 */
-	public <T extends Enum<?>> T getEnum(final T[] all, final String section,
-			final String subsection, final String name, final T defaultValue) {
+	@Nullable
+	@Deprecated
+	public <T extends Enum<?>> T getEnum(T[] all, String section,
+			String subsection, String name, @Nullable T defaultValue) {
 		return typedGetter.getEnum(this, all, section, subsection, name,
 				defaultValue);
 	}
 
 	/**
+	 * Parse an enumeration from the configuration.
+	 *
+	 * @param <T>
+	 *            type of the returned enum
+	 * @param all
+	 *            all possible values in the enumeration which should be
+	 *            recognized. Typically {@code EnumType.values()}.
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @return the selected enumeration value, or {@code null} if not set.
+	 * @since 7.2
+	 */
+	@Nullable
+	public <T extends Enum<?>> T getEnum(T[] all, String section,
+			String subsection, String name) {
+		return typedGetter.getEnum(this, all, section, subsection, name, null);
+	}
+
+	/**
 	 * Get string value or null if not found.
 	 *
 	 * @param section
@@ -466,8 +648,8 @@ public <T extends Enum<?>> T getEnum(final T[] all, final String section,
 	 *            the key name
 	 * @return a String value from the config, <code>null</code> if not found
 	 */
-	public String getString(final String section, String subsection,
-			final String name) {
+	@Nullable
+	public String getString(String section, String subsection, String name) {
 		return getRawString(section, subsection, name);
 	}
 
@@ -526,8 +708,34 @@ public String getString(final String section, String subsection,
 	 */
 	public long getTimeUnit(String section, String subsection, String name,
 			long defaultValue, TimeUnit wantUnit) {
+		Long v = typedGetter.getTimeUnit(this, section, subsection, name,
+				Long.valueOf(defaultValue), wantUnit);
+		return v == null ? defaultValue : v.longValue();
+
+	}
+
+	/**
+	 * Parse a numerical time unit, such as "1 minute", from the configuration.
+	 *
+	 * @param section
+	 *            section the key is in.
+	 * @param subsection
+	 *            subsection the key is in, or null if not in a subsection.
+	 * @param name
+	 *            the key name.
+	 * @param wantUnit
+	 *            the units of {@code defaultValue} and the return value, as
+	 *            well as the units to assume if the value does not contain an
+	 *            indication of the units.
+	 * @return the value, or {@code null} if not set, expressed in
+	 *         {@code units}.
+	 * @since 7.2
+	 */
+	@Nullable
+	public Long getTimeUnit(String section, String subsection, String name,
+			TimeUnit wantUnit) {
 		return typedGetter.getTimeUnit(this, section, subsection, name,
-				defaultValue, wantUnit);
+				null, wantUnit);
 	}
 
 	/**
@@ -555,8 +763,9 @@ public long getTimeUnit(String section, String subsection, String name,
 	 * @return the {@link Path}, or {@code defaultValue} if not set
 	 * @since 5.10
 	 */
+	@Nullable
 	public Path getPath(String section, String subsection, String name,
-			@NonNull FS fs, File resolveAgainst, Path defaultValue) {
+			@NonNull FS fs, File resolveAgainst, @Nullable Path defaultValue) {
 		return typedGetter.getPath(this, section, subsection, name, fs,
 				resolveAgainst, defaultValue);
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index a57f1b7..c455032 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -446,6 +446,13 @@ public final class ConfigConstants {
 	/** The "rebase" key */
 	public static final String CONFIG_KEY_REBASE = "rebase";
 
+	/**
+	 * The "checkout" key
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_CHECKOUT = "checkout";
+
 	/** The "url" key */
 	public static final String CONFIG_KEY_URL = "url";
 
@@ -593,11 +600,21 @@ public final class ConfigConstants {
 
 	/**
 	 * The "trustfolderstat" key in the "core" section
+	 *
 	 * @since 3.6
+	 * @deprecated use {CONFIG_KEY_TRUST_STAT} instead
 	 */
+	@Deprecated(since = "7.2", forRemoval = true)
 	public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
 
 	/**
+	 * The "trustfilestat" key in the "core"section
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_TRUST_STAT = "truststat";
+
+	/**
 	 * The "supportsAtomicFileCreation" key in the "core" section
 	 *
 	 * @since 4.5
@@ -1016,6 +1033,27 @@ public final class ConfigConstants {
 	public static final String CONFIG_KEY_TRUST_LOOSE_REF_STAT = "trustLooseRefStat";
 
 	/**
+	 * The "trustLooseRefStat" key
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_TRUST_PACK_STAT = "trustPackStat";
+
+	/**
+	 * The "trustLooseObjectFileStat" key
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT = "trustLooseObjectStat";
+
+	/**
+	 * The "trustTablesListStat" key
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_TRUST_TABLESLIST_STAT = "trustTablesListStat";
+
+	/**
 	 * The "pack.preserveOldPacks" key
 	 *
 	 * @since 5.13.2
@@ -1063,4 +1101,18 @@ public final class ConfigConstants {
 	 * @since 7.1
 	 */
 	public static final String CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL = "loadRevIndexInParallel";
+
+	/**
+	 * The "reftable" section
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_REFTABLE_SECTION = "reftable";
+
+	/**
+	 * The "autorefresh" key
+	 *
+	 * @since 7.2
+	 */
+	public static final String CONFIG_KEY_AUTOREFRESH = "autorefresh";
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
index 49602a7..0e27b27 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -17,12 +17,16 @@
 
 import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
 
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config.SectionParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This class keeps git repository core parameters.
  */
 public class CoreConfig {
+	private static final Logger LOG = LoggerFactory.getLogger(CoreConfig.class);
 	/** Key for {@link Config#get(SectionParser)}. */
 	public static final Config.SectionParser<CoreConfig> KEY = CoreConfig::new;
 
@@ -127,7 +131,9 @@ public enum LogRefUpdates {
 	 * Permissible values for {@code core.trustPackedRefsStat}.
 	 *
 	 * @since 6.1.1
+	 * @deprecated use {@link TrustStat} instead
 	 */
+	@Deprecated(since = "7.2", forRemoval = true)
 	public enum TrustPackedRefsStat {
 		/** Do not trust file attributes of the packed-refs file. */
 		NEVER,
@@ -135,12 +141,15 @@ public enum TrustPackedRefsStat {
 		/** Trust file attributes of the packed-refs file. */
 		ALWAYS,
 
-		/** Open and close the packed-refs file to refresh its file attributes
-		 * and then trust it. */
+		/**
+		 * Open and close the packed-refs file to refresh its file attributes
+		 * and then trust it.
+		 */
 		AFTER_OPEN,
 
-		/** {@code core.trustPackedRefsStat} defaults to this when it is
-		 * not set */
+		/**
+		 * {@code core.trustPackedRefsStat} defaults to this when it is not set
+		 */
 		UNSET
 	}
 
@@ -148,17 +157,44 @@ public enum TrustPackedRefsStat {
 	 * Permissible values for {@code core.trustLooseRefStat}.
 	 *
 	 * @since 6.9
+	 * @deprecated use {@link TrustStat} instead
 	 */
+	@Deprecated(since = "7.2", forRemoval = true)
 	public enum TrustLooseRefStat {
 
 		/** Trust file attributes of the loose ref. */
 		ALWAYS,
 
-		/** Open and close parent directories of the loose ref file until the
-		 * repository root to refresh its file attributes and then trust it. */
+		/**
+		 * Open and close parent directories of the loose ref file until the
+		 * repository root to refresh its file attributes and then trust it.
+		 */
 		AFTER_OPEN,
 	}
 
+	/**
+	 * Values for {@code core.trustXXX} options.
+	 *
+	 * @since 7.2
+	 */
+	public enum TrustStat {
+		/** Do not trust file attributes of a File. */
+		NEVER,
+
+		/** Always trust file attributes of a File. */
+		ALWAYS,
+
+		/** Open and close the File to refresh its file attributes
+		 * and then trust it. */
+		AFTER_OPEN,
+
+		/**
+		 * Used for specific options to inherit value from value set for
+		 * core.trustStat.
+		 */
+		INHERIT
+	}
+
 	private final int compression;
 
 	private final int packIndexVersion;
@@ -169,6 +205,18 @@ public enum TrustLooseRefStat {
 
 	private final boolean commitGraph;
 
+	private final TrustStat trustStat;
+
+	private final TrustStat trustPackedRefsStat;
+
+	private final TrustStat trustLooseRefStat;
+
+	private final TrustStat trustPackStat;
+
+	private final TrustStat trustLooseObjectStat;
+
+	private final TrustStat trustTablesListStat;
+
 	/**
 	 * Options for symlink handling
 	 *
@@ -198,7 +246,13 @@ public enum HideDotFiles {
 		DOTGITONLY
 	}
 
-	private CoreConfig(Config rc) {
+	/**
+	 * Create a new core configuration from the passed configuration.
+	 *
+	 * @param rc
+	 *            git configuration
+	 */
+	CoreConfig(Config rc) {
 		compression = rc.getInt(ConfigConstants.CONFIG_CORE_SECTION,
 				ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION);
 		packIndexVersion = rc.getInt(ConfigConstants.CONFIG_PACK_SECTION,
@@ -210,6 +264,68 @@ private CoreConfig(Config rc) {
 		commitGraph = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
 				ConfigConstants.CONFIG_COMMIT_GRAPH,
 				DEFAULT_COMMIT_GRAPH_ENABLE);
+
+		trustStat = parseTrustStat(rc);
+		trustPackedRefsStat = parseTrustPackedRefsStat(rc);
+		trustLooseRefStat = parseTrustLooseRefStat(rc);
+		trustPackStat = parseTrustPackFileStat(rc);
+		trustLooseObjectStat = parseTrustLooseObjectFileStat(rc);
+		trustTablesListStat = parseTablesListStat(rc);
+	}
+
+	private static TrustStat parseTrustStat(Config rc) {
+		Boolean tfs = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+				ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT);
+		TrustStat ts = rc.getEnum(TrustStat.values(),
+				ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUST_STAT);
+		if (tfs != null) {
+			if (ts == null) {
+				LOG.warn(JGitText.get().deprecatedTrustFolderStat);
+				return tfs.booleanValue() ? TrustStat.ALWAYS : TrustStat.NEVER;
+			}
+			LOG.warn(JGitText.get().precedenceTrustConfig);
+		}
+		if (ts == null) {
+			ts = TrustStat.ALWAYS;
+		} else if (ts == TrustStat.INHERIT) {
+			LOG.warn(JGitText.get().invalidTrustStat);
+			ts = TrustStat.ALWAYS;
+		}
+		return ts;
+	}
+
+	private TrustStat parseTrustPackedRefsStat(Config rc) {
+		return inheritParseTrustStat(rc,
+				ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT);
+	}
+
+	private TrustStat parseTrustLooseRefStat(Config rc) {
+		return inheritParseTrustStat(rc,
+				ConfigConstants.CONFIG_KEY_TRUST_LOOSE_REF_STAT);
+	}
+
+	private TrustStat parseTrustPackFileStat(Config rc) {
+		return inheritParseTrustStat(rc,
+				ConfigConstants.CONFIG_KEY_TRUST_PACK_STAT);
+	}
+
+	private TrustStat parseTrustLooseObjectFileStat(Config rc) {
+		return inheritParseTrustStat(rc,
+				ConfigConstants.CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT);
+	}
+
+	private TrustStat inheritParseTrustStat(Config rc, String key) {
+		TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, key,
+				TrustStat.INHERIT);
+		return t == TrustStat.INHERIT ? trustStat : t;
+	}
+
+	private TrustStat parseTablesListStat(Config rc) {
+		TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+				ConfigConstants.CONFIG_KEY_TRUST_TABLESLIST_STAT,
+				TrustStat.INHERIT);
+		return t == TrustStat.INHERIT ? trustStat : t;
 	}
 
 	/**
@@ -260,4 +376,70 @@ public String getAttributesFile() {
 	public boolean enableCommitGraph() {
 		return commitGraph;
 	}
+
+	/**
+	 * Get how far we can trust file attributes of packed-refs file which is
+	 * used to store {@link org.eclipse.jgit.lib.Ref}s in
+	 * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+	 *
+	 * @return how far we can trust file attributes of packed-refs file.
+	 *
+	 * @since 7.2
+	 */
+	public TrustStat getTrustPackedRefsStat() {
+		return trustPackedRefsStat;
+	}
+
+	/**
+	 * Get how far we can trust file attributes of loose ref files which are
+	 * used to store {@link org.eclipse.jgit.lib.Ref}s in
+	 * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+	 *
+	 * @return how far we can trust file attributes of loose ref files.
+	 *
+	 * @since 7.2
+	 */
+	public TrustStat getTrustLooseRefStat() {
+		return trustLooseRefStat;
+	}
+
+	/**
+	 * Get how far we can trust file attributes of packed-refs file which is
+	 * used to store {@link org.eclipse.jgit.lib.Ref}s in
+	 * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+	 *
+	 * @return how far we can trust file attributes of packed-refs file.
+	 *
+	 * @since 7.2
+	 */
+	public TrustStat getTrustPackStat() {
+		return trustPackStat;
+	}
+
+	/**
+	 * Get how far we can trust file attributes of loose ref files which are
+	 * used to store {@link org.eclipse.jgit.lib.Ref}s in
+	 * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+	 *
+	 * @return how far we can trust file attributes of loose ref files.
+	 *
+	 * @since 7.2
+	 */
+	public TrustStat getTrustLooseObjectStat() {
+		return trustLooseObjectStat;
+	}
+
+	/**
+	 * Get how far we can trust file attributes of the "tables.list" file which
+	 * is used to store the list of filenames of the files storing
+	 * {@link org.eclipse.jgit.internal.storage.reftable.Reftable}s in
+	 * {@link org.eclipse.jgit.internal.storage.file.FileReftableDatabase}.
+	 *
+	 * @return how far we can trust file attributes of the "tables.list" file.
+	 *
+	 * @since 7.2
+	 */
+	public TrustStat getTrustTablesListStat() {
+		return trustTablesListStat;
+	}
 }
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 a71549c..3059f28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -18,6 +18,7 @@
 import java.util.regex.Pattern;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Config.ConfigEnum;
 import org.eclipse.jgit.transport.RefSpec;
@@ -31,27 +32,37 @@
  */
 public class DefaultTypedConfigGetter implements TypedConfigGetter {
 
+	@SuppressWarnings("boxed")
 	@Override
 	public boolean getBoolean(Config config, String section, String subsection,
 			String name, boolean defaultValue) {
+		return neverNull(getBoolean(config, section, subsection, name,
+				Boolean.valueOf(defaultValue)));
+	}
+
+	@Nullable
+	@Override
+	public Boolean getBoolean(Config config, String section, String subsection,
+			String name, @Nullable Boolean defaultValue) {
 		String n = config.getString(section, subsection, name);
 		if (n == null) {
 			return defaultValue;
 		}
 		if (Config.isMissing(n)) {
-			return true;
+			return Boolean.TRUE;
 		}
 		try {
-			return StringUtils.toBoolean(n);
+			return Boolean.valueOf(StringUtils.toBoolean(n));
 		} catch (IllegalArgumentException err) {
 			throw new IllegalArgumentException(MessageFormat.format(
 					JGitText.get().invalidBooleanValue, section, name, n), err);
 		}
 	}
 
+	@Nullable
 	@Override
 	public <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
-			String subsection, String name, T defaultValue) {
+			String subsection, String name, @Nullable T defaultValue) {
 		String value = config.getString(section, subsection, name);
 		if (value == null) {
 			return defaultValue;
@@ -107,9 +118,27 @@ 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) {
-		long val = config.getLong(section, subsection, name, defaultValue);
+		return neverNull(getInt(config, section, subsection, name,
+				Integer.valueOf(defaultValue)));
+	}
+
+	@Nullable
+	@Override
+	@SuppressWarnings("boxing")
+	public Integer getInt(Config config, String section, String subsection,
+			String name, @Nullable Integer defaultValue) {
+		Long longDefault = defaultValue != null
+				? Long.valueOf(defaultValue.longValue())
+				: null;
+		Long val = config.getLong(section, subsection, name);
+		if (val == null) {
+			val = longDefault;
+		}
+		if (val == null) {
+			return null;
+		}
 		if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) {
-			return (int) val;
+			return Integer.valueOf(Math.toIntExact(val));
 		}
 		throw new IllegalArgumentException(MessageFormat
 				.format(JGitText.get().integerValueOutOfRange, section, name));
@@ -118,37 +147,56 @@ public int 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) {
-		int val = getInt(config, section, subsection, name, defaultValue);
+		return neverNull(getIntInRange(config, section, subsection, name,
+				minValue, maxValue, Integer.valueOf(defaultValue)));
+	}
+
+	@Override
+	@SuppressWarnings("boxing")
+	public Integer getIntInRange(Config config, String section,
+			String subsection, String name, int minValue, int maxValue,
+			Integer defaultValue) {
+		Integer val = getInt(config, section, subsection, name, defaultValue);
+		if (val == null) {
+			return null;
+		}
 		if ((val >= minValue && val <= maxValue) || val == UNSET_INT) {
 			return val;
 		}
 		if (subsection == null) {
-			throw new IllegalArgumentException(MessageFormat.format(
-					JGitText.get().integerValueNotInRange, section, name,
-					Integer.valueOf(val), Integer.valueOf(minValue),
-					Integer.valueOf(maxValue)));
+			throw new IllegalArgumentException(
+					MessageFormat.format(JGitText.get().integerValueNotInRange,
+							section, name, val, minValue, maxValue));
 		}
 		throw new IllegalArgumentException(MessageFormat.format(
 				JGitText.get().integerValueNotInRangeSubSection, section,
-				subsection, name, Integer.valueOf(val),
-				Integer.valueOf(minValue), Integer.valueOf(maxValue)));
+				subsection, name, val, minValue, maxValue));
 	}
 
 	@Override
 	public long getLong(Config config, String section, String subsection,
 			String name, long defaultValue) {
-		final String str = config.getString(section, subsection, name);
+		return neverNull(getLong(config, section, subsection, name,
+				Long.valueOf(defaultValue)));
+	}
+
+	@Nullable
+	@Override
+	public Long getLong(Config config, String section, String subsection,
+			String name, @Nullable Long defaultValue) {
+		String str = config.getString(section, subsection, name);
 		if (str == null) {
 			return defaultValue;
 		}
 		try {
-			return StringUtils.parseLongWithSuffix(str, false);
+			return Long.valueOf(StringUtils.parseLongWithSuffix(str, false));
 		} catch (StringIndexOutOfBoundsException e) {
 			// 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);
 		}
 	}
@@ -156,6 +204,13 @@ 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) {
+		return neverNull(getTimeUnit(config, section, subsection, name,
+				Long.valueOf(defaultValue), wantUnit));
+	}
+
+	@Override
+	public Long getTimeUnit(Config config, String section, String subsection,
+			String name, @Nullable Long defaultValue, TimeUnit wantUnit) {
 		String valueString = config.getString(section, subsection, name);
 
 		if (valueString == null) {
@@ -232,8 +287,8 @@ public long getTimeUnit(Config config, String section, String subsection,
 		}
 
 		try {
-			return wantUnit.convert(Long.parseLong(digits) * inputMul,
-					inputUnit);
+			return Long.valueOf(wantUnit
+					.convert(Long.parseLong(digits) * inputMul, inputUnit));
 		} catch (NumberFormatException nfe) {
 			IllegalArgumentException iae = notTimeUnit(section, subsection,
 					unitName, valueString);
@@ -274,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/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
index 5d3db9e..50f4a83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -12,7 +12,10 @@
 
 package org.eclipse.jgit.lib;
 
+import static java.time.ZoneOffset.UTC;
+
 import java.io.Serializable;
+import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
@@ -42,7 +45,9 @@ public class PersonIdent implements Serializable {
 	 *            timezone offset as in {@link #getTimeZoneOffset()}.
 	 * @return time zone object for the given offset.
 	 * @since 4.1
+	 * @deprecated use {@link #getZoneId(int)} instead
 	 */
+	@Deprecated(since = "7.2")
 	public static TimeZone getTimeZone(int tzOffset) {
 		StringBuilder tzId = new StringBuilder(8);
 		tzId.append("GMT"); //$NON-NLS-1$
@@ -54,11 +59,15 @@ public static TimeZone getTimeZone(int tzOffset) {
 	 * Translate a minutes offset into a ZoneId
 	 *
 	 * @param tzOffset as minutes east of UTC
-	 * @return a ZoneId  for this offset
+	 * @return a ZoneId for this offset (UTC if invalid)
 	 * @since 7.1
 	 */
 	public static ZoneId getZoneId(int tzOffset) {
-		return ZoneOffset.ofHoursMinutes(tzOffset / 60, tzOffset % 60);
+		try {
+			return ZoneOffset.ofHoursMinutes(tzOffset / 60, tzOffset % 60);
+		} catch (DateTimeException e) {
+			return UTC;
+		}
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 09cb5a8..49d5224 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -356,6 +356,40 @@ public List<Ref> getRefs() throws IOException {
 	}
 
 	/**
+	 * Get the reflog reader
+	 *
+	 * @param refName
+	 *            a {@link java.lang.String} object.
+	 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied
+	 *         refname, or {@code null} if the named ref does not exist.
+	 * @throws java.io.IOException
+	 *             the ref could not be accessed.
+	 * @since 7.2
+	 */
+	@Nullable
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		Ref ref = exactRef(refName);
+		if (ref == null) {
+			return null;
+		}
+		return getReflogReader(ref);
+	}
+
+	/**
+	 * Get the reflog reader.
+	 *
+	 * @param ref
+	 *            a Ref
+	 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref.
+	 * @throws IOException
+	 *             if an IO error occurred
+	 * @since 7.2
+	 */
+	@NonNull
+	public abstract ReflogReader getReflogReader(@NonNull Ref ref)
+			throws IOException;
+
+	/**
 	 * Get a section of the reference namespace.
 	 *
 	 * @param prefix
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index 0562840..c9dc6da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -26,6 +26,8 @@
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -33,10 +35,12 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Pattern;
 
 import org.eclipse.jgit.annotations.NonNull;
@@ -132,6 +136,8 @@ public static ListenerList getGlobalListenerList() {
 
 	private final String initialBranch;
 
+	private final AtomicReference<Boolean> caseInsensitiveWorktree = new AtomicReference<>();
+
 	/**
 	 * Initialize a new repository instance.
 	 *
@@ -1577,6 +1583,40 @@ public File getWorkTree() throws NoWorkTreeException {
 	}
 
 	/**
+	 * Tells whether the work tree is on a case-insensitive file system.
+	 *
+	 * @return {@code true} if the work tree is case-insensitive; {@code false}
+	 *         otherwise
+	 * @throws NoWorkTreeException
+	 *             if the repository is bare
+	 * @since 7.2
+	 */
+	public boolean isWorkTreeCaseInsensitive() throws NoWorkTreeException {
+		Boolean flag = caseInsensitiveWorktree.get();
+		if (flag == null) {
+			File directory = getWorkTree();
+			// See if we can find ".git" also as ".GIT".
+			File dotGit = new File(directory, Constants.DOT_GIT);
+			if (Files.exists(dotGit.toPath(), LinkOption.NOFOLLOW_LINKS)) {
+				dotGit = new File(directory,
+						Constants.DOT_GIT.toUpperCase(Locale.ROOT));
+				flag = Boolean.valueOf(Files.exists(dotGit.toPath(),
+						LinkOption.NOFOLLOW_LINKS));
+			} else {
+				// Fall back to a mostly sane default. On Mac, HFS+ and APFS
+				// partitions are case-insensitive by default but can be
+				// configured to be case-sensitive.
+				SystemReader system = SystemReader.getInstance();
+				flag = Boolean.valueOf(system.isWindows() || system.isMacOS());
+			}
+			if (!caseInsensitiveWorktree.compareAndSet(null, flag)) {
+				flag = caseInsensitiveWorktree.get();
+			}
+		}
+		return flag.booleanValue();
+	}
+
+	/**
 	 * Force a scan for changed refs. Fires an IndexChangedEvent(false) if
 	 * changes are detected.
 	 *
@@ -1692,10 +1732,13 @@ public void setGitwebDescription(@Nullable String description)
 	 * @throws java.io.IOException
 	 *             the ref could not be accessed.
 	 * @since 3.0
+	 * @deprecated use {@code #getRefDatabase().getReflogReader(String)} instead
 	 */
+	@Deprecated(since = "7.2")
 	@Nullable
-	public abstract ReflogReader getReflogReader(String refName)
-			throws IOException;
+	public ReflogReader getReflogReader(String refName) throws IOException {
+		return getRefDatabase().getReflogReader(refName);
+	}
 
 	/**
 	 * Get the reflog reader. Subclasses should override this method and provide
@@ -1703,15 +1746,17 @@ public abstract ReflogReader getReflogReader(String refName)
 	 *
 	 * @param ref
 	 *            a Ref
-	 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref,
-	 *         or {@code null} if the ref does not exist.
+	 * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref.
 	 * @throws IOException
 	 *             if an IO error occurred
 	 * @since 5.13.2
+	 * @deprecated use {@code #getRefDatabase().getReflogReader(Ref)} instead
 	 */
-	public @Nullable ReflogReader getReflogReader(@NonNull	Ref ref)
+	@Deprecated(since = "7.2")
+	@NonNull
+	public ReflogReader getReflogReader(@NonNull Ref ref)
 			throws IOException {
-		return getReflogReader(ref.getName());
+		return getRefDatabase().getReflogReader(ref);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
index 0c03adc..3d4e0d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -17,6 +17,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.transport.RefSpec;
 import org.eclipse.jgit.util.FS;
 
@@ -50,11 +51,36 @@ public interface TypedConfigGetter {
 	 *            default value to return if no value was present.
 	 * @return true if any value or defaultValue is true, false for missing or
 	 *         explicit false
+	 * @deprecated use
+	 *             {@link #getBoolean(Config, String, String, String, Boolean)}
+	 *             instead
 	 */
+	@Deprecated
 	boolean getBoolean(Config config, String section, String subsection,
 			String name, boolean defaultValue);
 
 	/**
+	 * Get a boolean value from a git {@link Config}.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return true if any value or defaultValue is true, false for missing or
+	 *         explicit false
+	 * @since 7.2
+	 */
+	@Nullable
+	Boolean getBoolean(Config config, String section, String subsection,
+			String name, @Nullable Boolean defaultValue);
+
+	/**
 	 * Parse an enumeration from a git {@link Config}.
 	 *
 	 * @param <T>
@@ -74,8 +100,9 @@ boolean getBoolean(Config config, String section, String subsection,
 	 *            default value to return if no value was present.
 	 * @return the selected enumeration value, or {@code defaultValue}.
 	 */
+	@Nullable
 	<T extends Enum<?>> T getEnum(Config config, T[] all, String section,
-			String subsection, String name, T defaultValue);
+			String subsection, String name, @Nullable T defaultValue);
 
 	/**
 	 * Obtain an integer value from a git {@link Config}.
@@ -91,11 +118,34 @@ <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
 	 * @param defaultValue
 	 *            default value to return if no value was present.
 	 * @return an integer value from the configuration, or defaultValue.
+	 * @deprecated use {@link #getInt(Config, String, String, String, Integer)}
+	 *             instead
 	 */
+	@Deprecated
 	int getInt(Config config, String section, String subsection, String name,
 			int defaultValue);
 
 	/**
+	 * Obtain an integer value from a git {@link Config}.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 * @since 7.2
+	 */
+	@Nullable
+	Integer getInt(Config config, String section, String subsection,
+			String name, @Nullable Integer defaultValue);
+
+	/**
 	 * Obtain an integer value from a git {@link Config} which must be in given
 	 * range.
 	 *
@@ -117,11 +167,43 @@ int getInt(Config config, String section, String subsection, String name,
 	 * @return an integer value from the configuration, or defaultValue.
 	 *         {@code #UNSET_INT} if unset.
 	 * @since 6.1
+	 * @deprecated use
+	 *             {@link #getIntInRange(Config, String, String, String, int, int, Integer)}
+	 *             instead
 	 */
+	@Deprecated
 	int getIntInRange(Config config, String section, String subsection,
 			String name, int minValue, int maxValue, int defaultValue);
 
 	/**
+	 * Obtain an integer value from a git {@link Config} which must be in given
+	 * range.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param minValue
+	 *            minimal value
+	 * @param maxValue
+	 *            maximum value
+	 * @param defaultValue
+	 *            default value to return if no value was present. Use
+	 *            {@code #UNSET_INT} to set the default to unset.
+	 * @return an integer value from the configuration, or defaultValue.
+	 *         {@code #UNSET_INT} if unset.
+	 * @since 7.2
+	 */
+	@Nullable
+	Integer getIntInRange(Config config, String section, String subsection,
+			String name, int minValue, int maxValue,
+			@Nullable Integer defaultValue);
+
+	/**
 	 * Obtain a long value from a git {@link Config}.
 	 *
 	 * @param config
@@ -135,11 +217,34 @@ int getIntInRange(Config config, String section, String subsection,
 	 * @param defaultValue
 	 *            default value to return if no value was present.
 	 * @return a long value from the configuration, or defaultValue.
+	 * @deprecated use {@link #getLong(Config, String, String, String, Long)}
+	 *             instead
 	 */
+	@Deprecated
 	long getLong(Config config, String section, String subsection, String name,
 			long defaultValue);
 
 	/**
+	 * Obtain a long value from a git {@link Config}.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return a long value from the configuration, or defaultValue.
+	 * @since 7.2
+	 */
+	@Nullable
+	Long getLong(Config config, String section, String subsection, String name,
+			@Nullable Long defaultValue);
+
+	/**
 	 * Parse a numerical time unit, such as "1 minute", from a git
 	 * {@link Config}.
 	 *
@@ -159,11 +264,41 @@ long getLong(Config config, String section, String subsection, String name,
 	 *            indication of the units.
 	 * @return the value, or {@code defaultValue} if not set, expressed in
 	 *         {@code units}.
+	 * @deprecated use
+	 *             {@link #getTimeUnit(Config, String, String, String, Long, TimeUnit)}
+	 *             instead
 	 */
+	@Deprecated
 	long getTimeUnit(Config config, String section, String subsection,
 			String name, long defaultValue, TimeUnit wantUnit);
 
 	/**
+	 * Parse a numerical time unit, such as "1 minute", from a git
+	 * {@link Config}.
+	 *
+	 * @param config
+	 *            to get the value from
+	 * @param section
+	 *            section the key is in.
+	 * @param subsection
+	 *            subsection the key is in, or null if not in a subsection.
+	 * @param name
+	 *            the key name.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @param wantUnit
+	 *            the units of {@code defaultValue} and the return value, as
+	 *            well as the units to assume if the value does not contain an
+	 *            indication of the units.
+	 * @return the value, or {@code defaultValue} if not set, expressed in
+	 *         {@code units}.
+	 * @since 7.2
+	 */
+	@Nullable
+	Long getTimeUnit(Config config, String section, String subsection,
+			String name, @Nullable Long defaultValue, TimeUnit wantUnit);
+
+	/**
 	 * Parse a string value from a git {@link Config} and treat it as a file
 	 * path, replacing a ~/ prefix by the user's home directory.
 	 * <p>
@@ -189,9 +324,10 @@ long getTimeUnit(Config config, String section, String subsection,
 	 * @return the {@link Path}, or {@code defaultValue} if not set
 	 * @since 5.10
 	 */
+	@Nullable
 	default Path getPath(Config config, String section, String subsection,
 			String name, @NonNull FS fs, File resolveAgainst,
-			Path defaultValue) {
+			@Nullable Path defaultValue) {
 		String value = config.getString(section, subsection, name);
 		if (value == null) {
 			return defaultValue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index fc5ab62..f58ef4f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -18,10 +18,10 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.ZoneOffset;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
-import java.util.TimeZone;
 import java.util.stream.Collectors;
 
 import org.eclipse.jgit.dircache.DirCache;
@@ -233,12 +233,11 @@ private RevCommit createCommitForTree(ObjectId tree, List<RevCommit> parents)
 	private static PersonIdent mockAuthor(List<RevCommit> parents) {
 		String name = RecursiveMerger.class.getSimpleName();
 		int time = 0;
-		for (RevCommit p : parents)
+		for (RevCommit p : parents) {
 			time = Math.max(time, p.getCommitTime());
-		return new PersonIdent(
-				name, name + "@JGit", //$NON-NLS-1$
-				new Date((time + 1) * 1000L),
-				TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
+		}
+		return new PersonIdent(name, name + "@JGit", //$NON-NLS-1$
+				Instant.ofEpochSecond(time+1), ZoneOffset.UTC);
 	}
 
 	private String failingPathsMessage() {
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 743a8cc..871545f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -1,6 +1,6 @@
 /*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2009 Google Inc.
+ * Copyright (C) 2008, 2024 Shawn O. Pearce <spearce@spearce.org> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -401,13 +401,13 @@ public RevCommit getParent(int nth) {
 	 * @since 5.1
 	 */
 	public final byte[] getRawGpgSignature() {
-		final byte[] raw = buffer;
-		final byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
-		final int start = RawParseUtils.headerStart(header, raw, 0);
+		byte[] raw = buffer;
+		byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
+		int start = RawParseUtils.headerStart(header, raw, 0);
 		if (start < 0) {
 			return null;
 		}
-		final int end = RawParseUtils.headerEnd(raw, start);
+		int end = RawParseUtils.nextLfSkippingSplitLines(raw, start);
 		return RawParseUtils.headerValue(raw, start, end);
 	}
 
@@ -524,6 +524,30 @@ static boolean hasLF(byte[] r, int b, int e) {
 	}
 
 	/**
+	 * Parse the commit message and return its first line, i.e., everything up
+	 * to but not including the first newline, if any.
+	 *
+	 * @return the first line of the decoded commit message as a string; never
+	 *         {@code null}.
+	 * @since 7.2
+	 */
+	public final String getFirstMessageLine() {
+		int msgB = RawParseUtils.commitMessage(buffer, 0);
+		if (msgB < 0) {
+			return ""; //$NON-NLS-1$
+		}
+		int msgE = msgB;
+		byte[] raw = buffer;
+		while (msgE < raw.length && raw[msgE] != '\n') {
+			msgE++;
+		}
+		if (msgE > msgB && msgE > 0 && raw[msgE - 1] == '\r') {
+			msgE--;
+		}
+		return RawParseUtils.decode(guessEncoding(buffer), buffer, msgB, msgE);
+	}
+
+	/**
 	 * Determine the encoding of the commit message buffer.
 	 * <p>
 	 * Locates the "encoding" header (if present) and returns its value. Due to
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
index 4100e87..c9186b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
@@ -12,6 +12,7 @@
 package org.eclipse.jgit.revwalk.filter;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Date;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -30,9 +31,24 @@ public abstract class CommitTimeRevFilter extends RevFilter {
 	 * @param ts
 	 *            the point in time to cut on.
 	 * @return a new filter to select commits on or before <code>ts</code>.
+	 *
+	 * @deprecated Use {@link #before(Instant)} instead.
 	 */
+	@Deprecated(since="7.2")
 	public static final RevFilter before(Date ts) {
-		return before(ts.getTime());
+		return before(ts.toInstant());
+	}
+
+	/**
+	 * Create a new filter to select commits before a given date/time.
+	 *
+	 * @param ts
+	 *            the point in time to cut on.
+	 * @return a new filter to select commits on or before <code>ts</code>.
+	 * @since 7.2
+	 */
+	public static RevFilter before(Instant ts) {
+		return new Before(ts);
 	}
 
 	/**
@@ -43,7 +59,7 @@ public static final RevFilter before(Date ts) {
 	 * @return a new filter to select commits on or before <code>ts</code>.
 	 */
 	public static final RevFilter before(long ts) {
-		return new Before(ts);
+		return new Before(Instant.ofEpochMilli(ts));
 	}
 
 	/**
@@ -52,9 +68,24 @@ public static final RevFilter before(long ts) {
 	 * @param ts
 	 *            the point in time to cut on.
 	 * @return a new filter to select commits on or after <code>ts</code>.
+	 *
+	 * @deprecated Use {@link #after(Instant)} instead.
 	 */
+	@Deprecated(since="7.2")
 	public static final RevFilter after(Date ts) {
-		return after(ts.getTime());
+		return after(ts.toInstant());
+	}
+
+	/**
+	 * Create a new filter to select commits after a given date/time.
+	 *
+	 * @param ts
+	 *            the point in time to cut on.
+	 * @return a new filter to select commits on or after <code>ts</code>.
+	 * @since 7.2
+	 */
+	public static RevFilter after(Instant ts) {
+		return new After(ts);
 	}
 
 	/**
@@ -65,7 +96,7 @@ public static final RevFilter after(Date ts) {
 	 * @return a new filter to select commits on or after <code>ts</code>.
 	 */
 	public static final RevFilter after(long ts) {
-		return new After(ts);
+		return after(Instant.ofEpochMilli(ts));
 	}
 
 	/**
@@ -75,9 +106,28 @@ public static final RevFilter after(long ts) {
 	 * @param since the point in time to cut on.
 	 * @param until the point in time to cut off.
 	 * @return a new filter to select commits between the given date/times.
+	 *
+	 * @deprecated Use {@link #between(Instant, Instant)} instead.
 	 */
+	@Deprecated(since="7.2")
 	public static final RevFilter between(Date since, Date until) {
-		return between(since.getTime(), until.getTime());
+		return between(since.toInstant(), until.toInstant());
+	}
+
+	/**
+	 * Create a new filter to select commits after or equal a given date/time
+	 * <code>since</code> and before or equal a given date/time
+	 * <code>until</code>.
+	 *
+	 * @param since
+	 *            the point in time to cut on.
+	 * @param until
+	 *            the point in time to cut off.
+	 * @return a new filter to select commits between the given date/times.
+	 * @since 7.2
+	 */
+	public static RevFilter between(Instant since, Instant until) {
+		return new Between(since, until);
 	}
 
 	/**
@@ -87,9 +137,12 @@ public static final RevFilter between(Date since, Date until) {
 	 * @param since the point in time to cut on, in milliseconds.
 	 * @param until the point in time to cut off, in millisconds.
 	 * @return a new filter to select commits between the given date/times.
+	 *
+	 * @deprecated Use {@link #between(Instant, Instant)} instead.
 	 */
+	@Deprecated(since="7.2")
 	public static final RevFilter between(long since, long until) {
-		return new Between(since, until);
+		return new Between(Instant.ofEpochMilli(since), Instant.ofEpochMilli(until));
 	}
 
 	final int when;
@@ -98,6 +151,10 @@ public static final RevFilter between(long since, long until) {
 		when = (int) (ts / 1000);
 	}
 
+	CommitTimeRevFilter(Instant t) {
+		when = (int) t.getEpochSecond();
+	}
+
 	@Override
 	public RevFilter clone() {
 		return this;
@@ -109,8 +166,8 @@ public boolean requiresCommitBody() {
 	}
 
 	private static class Before extends CommitTimeRevFilter {
-		Before(long ts) {
-			super(ts);
+		Before(Instant t) {
+			super(t);
 		}
 
 		@Override
@@ -123,14 +180,12 @@ public boolean include(RevWalk walker, RevCommit cmit)
 		@SuppressWarnings("nls")
 		@Override
 		public String toString() {
-			return super.toString() + "(" + new Date(when * 1000L) + ")";
+			return super.toString() + "(" + Instant.ofEpochSecond(when) + ")";
 		}
 	}
 
 	private static class After extends CommitTimeRevFilter {
-		After(long ts) {
-			super(ts);
-		}
+		After(Instant t) { super(t); }
 
 		@Override
 		public boolean include(RevWalk walker, RevCommit cmit)
@@ -148,16 +203,16 @@ public boolean include(RevWalk walker, RevCommit cmit)
 		@SuppressWarnings("nls")
 		@Override
 		public String toString() {
-			return super.toString() + "(" + new Date(when * 1000L) + ")";
+			return super.toString() + "(" + Instant.ofEpochSecond(when) + ")";
 		}
 	}
 
 	private static class Between extends CommitTimeRevFilter {
 		private final int until;
 
-		Between(long since, long until) {
+		Between(Instant since, Instant until) {
 			super(since);
-			this.until = (int) (until / 1000);
+			this.until = (int) until.getEpochSecond();
 		}
 
 		@Override
@@ -170,8 +225,8 @@ public boolean include(RevWalk walker, RevCommit cmit)
 		@SuppressWarnings("nls")
 		@Override
 		public String toString() {
-			return super.toString() + "(" + new Date(when * 1000L) + " - "
-					+ new Date(until * 1000L) + ")";
+			return super.toString() + "(" + Instant.ofEpochSecond(when) + " - "
+					+ Instant.ofEpochSecond(until) + ")";
 		}
 
 	}
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/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index 469a3d6..be0d37b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -12,10 +12,10 @@
 
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END;
 import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
@@ -32,7 +32,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -715,7 +714,7 @@ private void markReachable(Collection<Ref> want, Set<ObjectId> have,
 			// wind up later matching up against things we want and we
 			// can avoid asking for something we already happen to have.
 			//
-			final Date maxWhen = new Date(maxTime * 1000L);
+			Instant maxWhen = Instant.ofEpochSecond(maxTime);
 			walk.sort(RevSort.COMMIT_TIME_DESC);
 			walk.markStart(reachableCommits);
 			walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
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/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index a9e93b6..6bdaf0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -329,7 +330,7 @@ public RefUpdate.Result save() throws IOException {
 		if (newId == null) {
 			return RefUpdate.Result.NO_CHANGE;
 		}
-		try (ObjectInserter inserter = db.newObjectInserter()) {
+		try {
 			RefUpdate.Result result = updateRef(newId);
 			switch (result) {
 				case FAST_FORWARD:
@@ -404,8 +405,8 @@ private ObjectId write() throws IOException {
 	}
 
 	private static void sortPending(List<PendingCert> pending) {
-		Collections.sort(pending, (PendingCert a, PendingCert b) -> Long.signum(
-				a.ident.getWhen().getTime() - b.ident.getWhen().getTime()));
+		Collections.sort(pending,
+				Comparator.comparing((PendingCert a) -> a.ident.getWhenAsInstant()));
 	}
 
 	private DirCache newDirCache() throws IOException {
@@ -503,7 +504,7 @@ private static String buildMessage(PushCertificate cert) {
 		} else {
 			sb.append(MessageFormat.format(
 					JGitText.get().storePushCertMultipleRefs,
-					Integer.valueOf(cert.getCommands().size())));
+					cert.getCommands().size()));
 		}
 		return sb.append('\n').toString();
 	}
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$
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index d972067..41ab8ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -2226,7 +2226,7 @@ private boolean wantSatisfied(RevObject want) throws IOException {
 		walk.resetRetain(SAVE);
 		walk.markStart((RevCommit) want);
 		if (oldestTime != 0)
-			walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
+			walk.setRevFilter(CommitTimeRevFilter.after(Instant.ofEpochSecond(oldestTime)));
 		for (;;) {
 			final RevCommit c = walk.next();
 			if (c == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index b789c51..31c216b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -38,12 +38,12 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.MutableObjectId;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.treewalk.filter.TreeFilter;
@@ -1594,6 +1594,9 @@ private String getFilterCommandDefinition(String filterDriverName,
 		String filterCommand = filterCommandsByNameDotType.get(key);
 		if (filterCommand != null)
 			return filterCommand;
+		if (config == null) {
+			return null;
+		}
 		filterCommand = config.getString(ConfigConstants.CONFIG_FILTER_SECTION,
 				filterDriverName, filterCommandType);
 		boolean useBuiltin = config.getBoolean(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 12af374..c8421d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -86,8 +86,8 @@ public static ObjectId computeChangeId(final ObjectId treeId,
 		}
 	}
 
-	private static final Pattern issuePattern = Pattern
-			.compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$"); //$NON-NLS-1$
+	private static final Pattern signedOffByPattern = Pattern
+			.compile("^Signed-off-by:.*$"); //$NON-NLS-1$
 
 	private static final Pattern footerPattern = Pattern
 			.compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$
@@ -159,7 +159,7 @@ public static String insertId(String message, ObjectId changeId,
 		int footerFirstLine = indexOfFirstFooterLine(lines);
 		int insertAfter = footerFirstLine;
 		for (int i = footerFirstLine; i < lines.length; ++i) {
-			if (issuePattern.matcher(lines[i]).matches()) {
+			if (!signedOffByPattern.matcher(lines[i]).matches()) {
 				insertAfter = i + 1;
 				continue;
 			}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
index e6bf497..332e659 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
@@ -10,10 +10,10 @@
 
 package org.eclipse.jgit.util;
 
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.Locale;
-import java.util.TimeZone;
 
 import org.eclipse.jgit.lib.PersonIdent;
 
@@ -26,9 +26,9 @@
  */
 public class GitDateFormatter {
 
-	private DateFormat dateTimeInstance;
+	private DateTimeFormatter dateTimeFormat;
 
-	private DateFormat dateTimeInstance2;
+	private DateTimeFormatter dateTimeFormat2;
 
 	private final Format format;
 
@@ -96,30 +96,34 @@ public GitDateFormatter(Format format) {
 		default:
 			break;
 		case DEFAULT: // Not default:
-			dateTimeInstance = new SimpleDateFormat(
+			dateTimeFormat = DateTimeFormatter.ofPattern(
 					"EEE MMM dd HH:mm:ss yyyy Z", Locale.US); //$NON-NLS-1$
 			break;
 		case ISO:
-			dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$
+			dateTimeFormat = DateTimeFormatter.ofPattern(
+					"yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$
 					Locale.US);
 			break;
 		case LOCAL:
-			dateTimeInstance = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$
+			dateTimeFormat = DateTimeFormatter.ofPattern(
+					"EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$
 					Locale.US);
 			break;
 		case RFC:
-			dateTimeInstance = new SimpleDateFormat(
+			dateTimeFormat = DateTimeFormatter.ofPattern(
 					"EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); //$NON-NLS-1$
 			break;
 		case SHORT:
-			dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd", Locale.US); //$NON-NLS-1$
+			dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd", //$NON-NLS-1$
+					Locale.US);
 			break;
 		case LOCALE:
 		case LOCALELOCAL:
-			SystemReader systemReader = SystemReader.getInstance();
-			dateTimeInstance = systemReader.getDateTimeInstance(
-					DateFormat.DEFAULT, DateFormat.DEFAULT);
-			dateTimeInstance2 = systemReader.getSimpleDateFormat("Z"); //$NON-NLS-1$
+			dateTimeFormat = DateTimeFormatter
+					.ofLocalizedDateTime(FormatStyle.MEDIUM)
+					.withLocale(Locale.US);
+			dateTimeFormat2 = DateTimeFormatter.ofPattern("Z", //$NON-NLS-1$
+					Locale.US);
 			break;
 		}
 	}
@@ -135,39 +139,45 @@ public GitDateFormatter(Format format) {
 	@SuppressWarnings("boxing")
 	public String formatDate(PersonIdent ident) {
 		switch (format) {
-		case RAW:
-			int offset = ident.getTimeZoneOffset();
+		case RAW: {
+			int offset = ident.getZoneOffset().getTotalSeconds();
 			String sign = offset < 0 ? "-" : "+"; //$NON-NLS-1$ //$NON-NLS-2$
 			int offset2;
-			if (offset < 0)
+			if (offset < 0) {
 				offset2 = -offset;
-			else
+			} else {
 				offset2 = offset;
-			int hours = offset2 / 60;
-			int minutes = offset2 % 60;
+			}
+			int minutes = (offset2 / 60) % 60;
+			int hours = offset2 / 60 / 60;
 			return String.format("%d %s%02d%02d", //$NON-NLS-1$
-					ident.getWhen().getTime() / 1000, sign, hours, minutes);
+					ident.getWhenAsInstant().getEpochSecond(), sign, hours,
+					minutes);
+		}
 		case RELATIVE:
-			return RelativeDateFormatter.format(ident.getWhen());
+			return RelativeDateFormatter.format(ident.getWhenAsInstant());
 		case LOCALELOCAL:
 		case LOCAL:
-			dateTimeInstance.setTimeZone(SystemReader.getInstance()
-					.getTimeZone());
-			return dateTimeInstance.format(ident.getWhen());
-		case LOCALE:
-			TimeZone tz = ident.getTimeZone();
-			if (tz == null)
-				tz = SystemReader.getInstance().getTimeZone();
-			dateTimeInstance.setTimeZone(tz);
-			dateTimeInstance2.setTimeZone(tz);
-			return dateTimeInstance.format(ident.getWhen()) + " " //$NON-NLS-1$
-					+ dateTimeInstance2.format(ident.getWhen());
-		default:
-			tz = ident.getTimeZone();
-			if (tz == null)
-				tz = SystemReader.getInstance().getTimeZone();
-			dateTimeInstance.setTimeZone(ident.getTimeZone());
-			return dateTimeInstance.format(ident.getWhen());
+			return dateTimeFormat
+					.withZone(SystemReader.getInstance().getTimeZoneId())
+					.format(ident.getWhenAsInstant());
+		case LOCALE: {
+			ZoneId tz = ident.getZoneId();
+			if (tz == null) {
+				tz = SystemReader.getInstance().getTimeZoneId();
+			}
+			return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant())
+					+ " " //$NON-NLS-1$
+					+ dateTimeFormat2.withZone(tz)
+							.format(ident.getWhenAsInstant());
+		}
+		default: {
+			ZoneId tz = ident.getZoneId();
+			if (tz == null) {
+				tz = SystemReader.getInstance().getTimeZoneId();
+			}
+			return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant());
+		}
 		}
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
index 7d00fcd..acaa1ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
@@ -11,6 +11,7 @@
 
 import java.text.MessageFormat;
 import java.text.ParseException;
+import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -97,6 +98,40 @@ public static LocalDateTime parse(String dateStr) throws ParseException {
 		return parse(dateStr, SystemReader.getInstance().civilNow());
 	}
 
+	/**
+	 * Parses a string into a {@link java.time.Instant} using the default
+	 * locale. Since this parser also supports relative formats (e.g.
+	 * "yesterday") the caller can specify the reference date. These types of
+	 * strings can be parsed:
+	 * <ul>
+	 * <li>"never"</li>
+	 * <li>"now"</li>
+	 * <li>"yesterday"</li>
+	 * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br>
+	 * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of '
+	 * ' one can use '.' to separate the words</li>
+	 * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li>
+	 * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li>
+	 * <li>"yyyy-MM-dd"</li>
+	 * <li>"yyyy.MM.dd"</li>
+	 * <li>"MM/dd/yyyy",</li>
+	 * <li>"dd.MM.yyyy"</li>
+	 * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li>
+	 * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li>
+	 * </ul>
+	 *
+	 * @param dateStr
+	 *            the string to be parsed
+	 * @return the parsed {@link java.time.Instant}
+	 * @throws java.text.ParseException
+	 *             if the given dateStr was not recognized
+	 * @since 7.2
+	 */
+	public static Instant parseInstant(String dateStr) throws ParseException {
+		return parse(dateStr).atZone(SystemReader.getInstance().getTimeZoneId())
+				.toInstant();
+	}
+
 	// Only tests seem to use this method
 	static LocalDateTime parse(String dateStr, LocalDateTime now)
 			throws ParseException {
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 2ce8690..3ed7251 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -13,6 +13,8 @@
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
+import static java.time.ZoneOffset.UTC;
 import static org.eclipse.jgit.lib.ObjectChecker.author;
 import static org.eclipse.jgit.lib.ObjectChecker.committer;
 import static org.eclipse.jgit.lib.ObjectChecker.encoding;
@@ -30,6 +32,10 @@
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.UnsupportedCharsetException;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -459,6 +465,29 @@ public static final int parseTimeZoneOffset(final byte[] b, int ptr,
 	}
 
 	/**
+	 * Parse a Git style timezone string in [+-]hhmm format
+	 *
+	 * @param b
+	 *            buffer to scan.
+	 * @param ptr
+	 *            position within buffer to start parsing digits at.
+	 * @param ptrResult
+	 *            optional location to return the new ptr value through. If null
+	 *            the ptr value will be discarded.
+	 * @return the ZoneOffset represention of the timezone offset string.
+	 *         Invalid offsets default to UTC.
+	 */
+	private static ZoneId parseZoneOffset(final byte[] b, int ptr,
+			MutableInteger ptrResult) {
+		int hhmm = parseBase10(b, ptr, ptrResult);
+		try {
+			return ZoneOffset.ofHoursMinutes(hhmm / 100, hhmm % 100);
+		} catch (DateTimeException e) {
+			return UTC;
+		}
+	}
+
+	/**
 	 * Locate the first position after a given character.
 	 *
 	 * @param b
@@ -1027,17 +1056,19 @@ public static PersonIdent parsePersonIdent(byte[] raw, int nameB) {
 		// character if there is no trailing LF.
 		final int tzBegin = lastIndexOfTrim(raw, ' ',
 				nextLF(raw, emailE - 1) - 2) + 1;
-		if (tzBegin <= emailE) // No time/zone, still valid
-			return new PersonIdent(name, email, 0, 0);
+		if (tzBegin <= emailE) { // No time/zone, still valid
+			return new PersonIdent(name, email, EPOCH, UTC);
+		}
 
 		final int whenBegin = Math.max(emailE,
 				lastIndexOfTrim(raw, ' ', tzBegin - 1) + 1);
-		if (whenBegin >= tzBegin - 1) // No time/zone, still valid
-			return new PersonIdent(name, email, 0, 0);
+		if (whenBegin >= tzBegin - 1) { // No time/zone, still valid
+			return new PersonIdent(name, email, EPOCH, UTC);
+		}
 
-		final long when = parseLongBase10(raw, whenBegin, null);
-		final int tz = parseTimeZoneOffset(raw, tzBegin);
-		return new PersonIdent(name, email, when * 1000L, tz);
+		long when = parseLongBase10(raw, whenBegin, null);
+		return new PersonIdent(name, email, Instant.ofEpochSecond(when),
+				parseZoneOffset(raw, tzBegin, null));
 	}
 
 	/**
@@ -1075,16 +1106,16 @@ public static PersonIdent parsePersonIdentOnly(final byte[] raw,
 			name = decode(raw, nameB, stop);
 
 		final MutableInteger ptrout = new MutableInteger();
-		long when;
-		int tz;
+		Instant when;
+		ZoneId tz;
 		if (emailE < stop) {
-			when = parseLongBase10(raw, emailE + 1, ptrout);
-			tz = parseTimeZoneOffset(raw, ptrout.value);
+			when = Instant.ofEpochSecond(parseLongBase10(raw, emailE + 1, ptrout));
+			tz = parseZoneOffset(raw, ptrout.value, null);
 		} else {
-			when = 0;
-			tz = 0;
+			when = EPOCH;
+			tz = UTC;
 		}
-		return new PersonIdent(name, email, when * 1000L, tz);
+		return new PersonIdent(name, email, when, tz);
 	}
 
 	/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
index 5611b1e..b6b19e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
@@ -10,6 +10,8 @@
 package org.eclipse.jgit.util;
 
 import java.text.MessageFormat;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Date;
 
 import org.eclipse.jgit.internal.JGitText;
@@ -42,12 +44,29 @@ public class RelativeDateFormatter {
 	 * @return age of given {@link java.util.Date} compared to now formatted in
 	 *         the same relative format as returned by
 	 *         {@code git log --relative-date}
+	 * @deprecated Use {@link #format(Instant)} instead.
 	 */
+	@Deprecated(since = "7.2")
 	@SuppressWarnings("boxing")
 	public static String format(Date when) {
+		return format(when.toInstant());
+	}
 
-		long ageMillis = SystemReader.getInstance().getCurrentTime()
-				- when.getTime();
+	/**
+	 * Get age of given {@link java.time.Instant} compared to now formatted in the
+	 * same relative format as returned by {@code git log --relative-date}
+	 *
+	 * @param when
+	 *            an instant to format
+	 * @return age of given instant compared to now formatted in
+	 *         the same relative format as returned by
+	 *         {@code git log --relative-date}
+	 * @since 7.2
+	 */
+	@SuppressWarnings("boxing")
+	public static String format(Instant when) {
+		long ageMillis = Duration
+				.between(when, SystemReader.getInstance().now()).toMillis();
 
 		// shouldn't happen in a perfect world
 		if (ageMillis < 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
index 90524db..e3e3e04 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
@@ -45,12 +45,15 @@ private SignatureUtils() {
 	public static String toString(SignatureVerification verification,
 			PersonIdent creator, GitDateFormatter formatter) {
 		StringBuilder result = new StringBuilder();
-		// Use the creator's timezone for the signature date
-		PersonIdent dateId = new PersonIdent(creator,
-				verification.creationDate());
-		result.append(MessageFormat.format(JGitText.get().verifySignatureMade,
-				formatter.formatDate(dateId)));
-		result.append('\n');
+		if (verification.creationDate() != null) {
+			// Use the creator's timezone for the signature date
+			PersonIdent dateId = new PersonIdent(creator,
+					verification.creationDate().toInstant());
+			result.append(
+					MessageFormat.format(JGitText.get().verifySignatureMade,
+							formatter.formatDate(dateId)));
+			result.append('\n');
+		}
 		result.append(MessageFormat.format(
 				JGitText.get().verifySignatureKey,
 				verification.keyFingerprint().toUpperCase(Locale.ROOT)));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
index 2fbd12d..e381a3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -278,6 +278,44 @@ public static String join(Collection<String> parts, String separator,
 	}
 
 	/**
+	 * Remove the specified character from beginning and end of a string
+	 * <p>
+	 * If the character repeats, all copies
+	 *
+	 * @param str input string
+	 * @param c character to remove
+	 * @return the input string with c
+	 * @since 7.2
+	 */
+	public static String trim(String str, char c) {
+		if (str == null || str.length() == 0) {
+			return str;
+		}
+
+		int endPos = str.length()-1;
+		while (endPos >= 0 && str.charAt(endPos) == c) {
+			endPos--;
+		}
+
+		// Whole string is c
+		if (endPos == -1) {
+			return EMPTY;
+		}
+
+		int startPos = 0;
+		while (startPos < endPos && str.charAt(startPos) == c) {
+			startPos++;
+		}
+
+		if (startPos == 0 && endPos == str.length()-1) {
+			// No need to copy
+			return str;
+		}
+
+		return str.substring(startPos, endPos+1);
+	}
+
+	/**
 	 * Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends
 	 * with that suffix.
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index 55cc878..22b82b3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -523,7 +523,7 @@ private void updateAll(Config config)
 	 *
 	 * @deprecated Use {@link #now()}
 	 */
-	@Deprecated
+	@Deprecated(since = "7.1")
 	public abstract long getCurrentTime();
 
 	/**
@@ -569,7 +569,7 @@ public MonotonicClock getClock() {
 	 *
 	 * @deprecated Use {@link #getTimeZoneAt(Instant)} instead.
 	 */
-	@Deprecated
+	@Deprecated(since = "7.1")
 	public abstract int getTimezone(long when);
 
 	/**
@@ -592,7 +592,7 @@ public ZoneOffset getTimeZoneAt(Instant when) {
 	 *
 	 * @deprecated Use {@link #getTimeZoneId()}
 	 */
-	@Deprecated
+	@Deprecated(since = "7.1")
 	public TimeZone getTimeZone() {
 		return TimeZone.getDefault();
 	}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
index a5ee107..a20eaaf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
@@ -138,7 +138,10 @@ public Instant instant() {
 	 * Get time since epoch, with up to microsecond resolution.
 	 *
 	 * @return time since epoch, with up to microsecond resolution.
+	 *
+	 * @deprecated Use instant() instead
 	 */
+	@Deprecated(since = "7.2")
 	public Timestamp timestamp() {
 		return Timestamp.from(instant());
 	}
@@ -147,7 +150,10 @@ public Timestamp timestamp() {
 	 * Get time since epoch, with up to millisecond resolution.
 	 *
 	 * @return time since epoch, with up to millisecond resolution.
+	 *
+	 * @deprecated Use instant() instead
 	 */
+	@Deprecated(since = "7.2")
 	public Date date() {
 		return new Date(millis());
 	}
diff --git a/pom.xml b/pom.xml
index 9a0770c..3e4ab20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
   <groupId>org.eclipse.jgit</groupId>
   <artifactId>org.eclipse.jgit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>7.1.2-SNAPSHOT</version>
+  <version>7.2.2-SNAPSHOT</version>
 
   <name>JGit - Parent</name>
   <url>${jgit-url}</url>
@@ -118,9 +118,9 @@
 
     <project.build.outputTimestamp>${commit.time.iso}</project.build.outputTimestamp>
 
-    <jgit-last-release-version>7.0.0.202409031743-r</jgit-last-release-version>
+    <jgit-last-release-version>7.1.0.202411261347-r</jgit-last-release-version>
     <ant-version>1.10.15</ant-version>
-    <apache-sshd-version>2.14.0</apache-sshd-version>
+    <apache-sshd-version>2.15.0</apache-sshd-version>
     <jsch-version>0.1.55</jsch-version>
     <jzlib-version>1.1.3</jzlib-version>
     <javaewah-version>1.2.3</javaewah-version>
@@ -130,25 +130,25 @@
     <commons-compress-version>1.27.1</commons-compress-version>
     <osgi-core-version>6.0.0</osgi-core-version>
     <servlet-api-version>6.1.0</servlet-api-version>
-    <jetty-version>12.0.15</jetty-version>
-    <japicmp-version>0.21.2</japicmp-version>
+    <jetty-version>12.0.16</jetty-version>
+    <japicmp-version>0.23.1</japicmp-version>
     <httpclient-version>4.5.14</httpclient-version>
     <httpcore-version>4.4.16</httpcore-version>
     <slf4j-version>1.7.36</slf4j-version>
-    <maven-javadoc-plugin-version>3.6.3</maven-javadoc-plugin-version>
-    <gson-version>2.11.0</gson-version>
-    <bouncycastle-version>1.79</bouncycastle-version>
-    <spotbugs-maven-plugin-version>4.8.5.0</spotbugs-maven-plugin-version>
-    <maven-project-info-reports-plugin-version>3.5.1</maven-project-info-reports-plugin-version>
-    <maven-jxr-plugin-version>3.3.2</maven-jxr-plugin-version>
-    <maven-surefire-plugin-version>3.2.5</maven-surefire-plugin-version>
+    <maven-javadoc-plugin-version>3.11.2</maven-javadoc-plugin-version>
+    <gson-version>2.12.1</gson-version>
+    <bouncycastle-version>1.80</bouncycastle-version>
+    <spotbugs-maven-plugin-version>4.9.1.0</spotbugs-maven-plugin-version>
+    <maven-project-info-reports-plugin-version>3.8.0</maven-project-info-reports-plugin-version>
+    <maven-jxr-plugin-version>3.6.0</maven-jxr-plugin-version>
+    <maven-surefire-plugin-version>3.5.2</maven-surefire-plugin-version>
     <maven-surefire-report-plugin-version>${maven-surefire-plugin-version}</maven-surefire-report-plugin-version>
-    <maven-compiler-plugin-version>3.13.0</maven-compiler-plugin-version>
+    <maven-compiler-plugin-version>3.14.0</maven-compiler-plugin-version>
     <plexus-compiler-version>2.13.0</plexus-compiler-version>
     <hamcrest-version>2.2</hamcrest-version>
-    <assertj-version>3.26.3</assertj-version>
-    <jna-version>5.15.0</jna-version>
-    <byte-buddy-version>1.15.10</byte-buddy-version>
+    <assertj-version>3.27.3</assertj-version>
+    <jna-version>5.16.0</jna-version>
+    <byte-buddy-version>1.17.1</byte-buddy-version>
 
     <!-- Properties to enable jacoco code coverage analysis -->
     <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -184,7 +184,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
-          <version>3.4.1</version>
+          <version>3.4.2</version>
           <configuration>
             <archive>
               <manifestEntries>
@@ -208,13 +208,13 @@
 
         <plugin>
           <artifactId>maven-clean-plugin</artifactId>
-          <version>3.3.2</version>
+          <version>3.4.1</version>
         </plugin>
 
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>3.5.3</version>
+          <version>3.6.0</version>
         </plugin>
 
         <plugin>
@@ -226,7 +226,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-dependency-plugin</artifactId>
-          <version>3.6.1</version>
+          <version>3.8.1</version>
         </plugin>
 
         <plugin>
@@ -277,7 +277,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
-          <version>3.22.0</version>
+          <version>3.26.0</version>
           <configuration>
             <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
             <minimumTokens>100</minimumTokens>
@@ -300,7 +300,7 @@
         <plugin>
           <groupId>org.eclipse.cbi.maven.plugins</groupId>
           <artifactId>eclipse-jarsigner-plugin</artifactId>
-          <version>1.4.3</version>
+          <version>1.5.2</version>
         </plugin>
         <plugin>
           <groupId>org.jacoco</groupId>
@@ -310,7 +310,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-site-plugin</artifactId>
-          <version>4.0.0-M14</version>
+          <version>4.0.0-M16</version>
           <dependencies>
             <dependency><!-- add support for ssh/scp -->
               <groupId>org.apache.maven.wagon</groupId>
@@ -337,12 +337,12 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-deploy-plugin</artifactId>
-          <version>3.1.2</version>
+          <version>3.1.3</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-install-plugin</artifactId>
-          <version>3.1.2</version>
+          <version>3.1.3</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
@@ -357,7 +357,7 @@
         <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
-          <version>2.7.18</version>
+          <version>3.4.3</version>
         </plugin>
         <plugin>
           <groupId>org.eclipse.dash</groupId>
@@ -367,12 +367,12 @@
         <plugin>
           <groupId>org.cyclonedx</groupId>
           <artifactId>cyclonedx-maven-plugin</artifactId>
-          <version>2.8.0</version>
+          <version>2.9.1</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-artifact-plugin</artifactId>
-          <version>3.5.1</version>
+          <version>3.6.0</version>
           <configuration>
             <ignore>**/*cyclonedx.json</ignore>
             <reproducible>true</reproducible>
@@ -381,7 +381,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-enforcer-plugin</artifactId>
-          <version>3.4.1</version>
+          <version>3.5.0</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -636,7 +636,7 @@
       <plugin>
         <groupId>io.github.git-commit-id</groupId>
         <artifactId>git-commit-id-maven-plugin</artifactId>
-        <version>8.0.2</version>
+        <version>9.0.1</version>
         <executions>
           <execution>
             <id>get-the-git-infos</id>
@@ -655,7 +655,7 @@
       <plugin>
         <groupId>org.codehaus.gmavenplus</groupId>
         <artifactId>gmavenplus-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>4.1.1</version>
         <dependencies>
           <dependency>
             <groupId>org.apache.groovy</groupId>
@@ -899,7 +899,7 @@
       <dependency>
         <groupId>commons-codec</groupId>
         <artifactId>commons-codec</artifactId>
-        <version>1.17.1</version>
+        <version>1.18.0</version>
       </dependency>
 
       <dependency>
@@ -911,13 +911,13 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-io</artifactId>
-        <version>2.17.0</version>
+        <version>2.18.0</version>
       </dependency>
 
       <dependency>
         <groupId>commons-logging</groupId>
         <artifactId>commons-logging</artifactId>
-        <version>1.3.4</version>
+        <version>1.3.5</version>
       </dependency>
 
       <dependency>
@@ -1020,7 +1020,7 @@
       <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
-        <version>5.14.2</version>
+        <version>5.15.2</version>
       </dependency>
 
       <dependency>
@@ -1129,7 +1129,7 @@
               <dependency>
                 <groupId>org.eclipse.jdt</groupId>
                 <artifactId>ecj</artifactId>
-                <version>3.38.0</version>
+                <version>3.40.0</version>
               </dependency>
             </dependencies>
           </plugin>