Merge branch 'stable-7.0' into stable-7.1
* stable-7.0:
Do not always refresh packed-refs during ref updates
Change-Id: I6f249ceb085105ae3309c6d1312ffa9482569fb6
diff --git a/Documentation/config-options.md b/Documentation/config-options.md
index b30b958..2904824 100644
--- a/Documentation/config-options.md
+++ b/Documentation/config-options.md
@@ -31,6 +31,7 @@
| `core.dfs.blockSize` | `64 kiB` | ⃞ | Size in bytes of a single window read in from the pack file into the DFS block cache. |
| `core.dfs.concurrencyLevel` | `32` | ⃞ | The estimated number of threads concurrently accessing the DFS block cache. |
| `core.dfs.deltaBaseCacheLimit` | `10 MiB` | ⃞ | Maximum number of bytes to hold in per-reader DFS delta base cache. |
+| `core.dfs.loadRevIndexInParallel` | false; | ⃞ | Try to load the reverse index in parallel with the bitmap index. |
| `core.dfs.streamFileThreshold` | `50 MiB` | ⃞ | The size threshold beyond which objects must be streamed. |
| `core.dfs.streamBuffer` | Block size of the pack | ⃞ | Number of bytes to use for buffering when streaming a pack file during copying. If 0 the block size of the pack is used|
| `core.dfs.streamRatio` | `0.30` | ⃞ | Ratio of DFS block cache to occupy with a copied pack. Values between `0` and `1.0`. |
diff --git a/WORKSPACE b/WORKSPACE
index 34caf9d..e1adb8a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -122,18 +122,18 @@
sha1 = "c070ac920e72023ae9ab0a3f3a866bece284b470",
)
-JNA_VERS = "5.14.0"
+JNA_VERS = "5.15.0"
maven_jar(
name = "jna",
artifact = "net.java.dev.jna:jna:" + JNA_VERS,
- sha1 = "67bf3eaea4f0718cb376a181a629e5f88fa1c9dd",
+ sha1 = "01ee1d80ff44f08280188f7c0e740d57207841ac",
)
maven_jar(
name = "jna-platform",
artifact = "net.java.dev.jna:jna-platform:" + JNA_VERS,
- sha1 = "28934d48aed814f11e4c584da55c49fa7032b31b",
+ sha1 = "86b502cad57d45da172b5e3231c537b042e296ef",
)
maven_jar(
@@ -174,14 +174,14 @@
maven_jar(
name = "commons-lang3",
- artifact = "org.apache.commons:commons-lang3:3.16.0",
- sha1 = "3eb54effe40946dfb06dc5cd6c7ce4116cd51ea4",
+ artifact = "org.apache.commons:commons-lang3:3.17.0",
+ sha1 = "b17d2136f0460dcc0d2016ceefca8723bdf4ee70",
)
maven_jar(
name = "commons-io",
- artifact = "commons-io:commons-io:2.16.1",
- sha1 = "377d592e740dc77124e0901291dbfaa6810a200e",
+ artifact = "commons-io:commons-io:2.17.0",
+ sha1 = "ddcc8433eb019fb48fe25207c0278143f3e1d7e2",
)
maven_jar(
@@ -210,8 +210,8 @@
maven_jar(
name = "mockito",
- artifact = "org.mockito:mockito-core:5.12.0",
- sha1 = "22f8bbaf478e6789164787fa411a3b5ed986e110",
+ artifact = "org.mockito:mockito-core:5.14.2",
+ sha1 = "f7bf936008d7664e2002c3faf0c02071c8d10e7c",
)
maven_jar(
@@ -220,18 +220,18 @@
sha1 = "0d26263eb7524252d98e602fc6942996a3195e29",
)
-BYTE_BUDDY_VERSION = "1.15.0"
+BYTE_BUDDY_VERSION = "1.15.10"
maven_jar(
name = "bytebuddy",
artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
- sha1 = "a5b1159b91c5334015de0f22ab4b1188cd42bbff",
+ sha1 = "635c873fadd853c084f84fdc3cbd58c5dd8537f9",
)
maven_jar(
name = "bytebuddy-agent",
artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
- sha1 = "e32740c43acebaac9d55b86399ecf6a5df3c17fb",
+ sha1 = "0e8eb255b2c378b9a6c7341e7b0e12f0a5636377",
)
maven_jar(
@@ -246,90 +246,82 @@
sha1 = "527175ca6d81050b53bdd4c457a6d6e017626b0e",
)
-JETTY_VER = "12.0.12"
+JETTY_VER = "12.0.15"
maven_jar(
name = "jetty-servlet",
artifact = "org.eclipse.jetty.ee10:jetty-ee10-servlet:" + JETTY_VER,
- sha1 = "12f25f260a8f9fb519b6d3058260564277e618cd",
- src_sha1 = "16a22f6ed585c6dcab07d111de290301373db1c7",
+ sha1 = "a9362717fa1756f9f1f18e0ef2ce671e742b7afb",
)
maven_jar(
name = "jetty-security",
artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER,
- sha1 = "962d2c5748f750aae667399481915bd02a9746e0",
- src_sha1 = "e953d0e19e6e420f5e7c1f62424c4bbe0d385d15",
+ sha1 = "e15efd84ed53277f8e893574edaed4734c161f44",
)
maven_jar(
name = "jetty-server",
artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER,
- sha1 = "77591ab10113d7de7eb8a01bb4d9d23b234aebd1",
- src_sha1 = "952873398a59bc5a964d9d2d130227af807559e9",
+ sha1 = "2bd3742c6831e42c6ebfa9c990386a8dca71dee8",
)
maven_jar(
name = "jetty-session",
artifact = "org.eclipse.jetty:jetty-session:" + JETTY_VER,
- sha1 = "fceae460fb4677a6cec80d3b70b1fc3ae2a114a7",
- src_sha1 = "ba95d5e17a3d7f09ca908d4c0773e913f3612425",
+ sha1 = "2819021282ff2f7fbaa53feb2fe063130bd4613c",
)
maven_jar(
name = "jetty-http",
artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER,
- sha1 = "549fe58ae50b9c061d09803fb0d2659a93ce0ecd",
- src_sha1 = "c7afad980b9eade12b473172a9d069d96d016677",
+ sha1 = "a36fcfde8316b374102c5b43d7247ec501e906d8",
)
maven_jar(
name = "jetty-io",
artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER,
- sha1 = "4e2cd5c23e8ba550238f35c361c22ffb1e7bf00c",
- src_sha1 = "42397797c496a51520900a529ba7e1b315aba768",
+ sha1 = "e1657f842a0e362171a8a41f35d85cdba1a47872",
)
maven_jar(
name = "jetty-util",
artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER,
- sha1 = "f7b1e4f835c38d12668b426d327118e767d2f1d4",
- src_sha1 = "0fdb95a123cc50f6adf4154edc5316cabca05f58",
+ sha1 = "9fbd3ac58607af034ad065b366798798bb5f7b5e",
)
maven_jar(
name = "jetty-util-ajax",
artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER,
- sha1 = "330e692032c82c305cd52c6a8e9bfba0f1da2555",
- src_sha1 = "d6b9274b3ef0419a6ba869c828fd0c59c570cd9d",
+ sha1 = "d5c2f0ea177c5c178874101c186cc9729cf7ea92",
)
-BOUNCYCASTLE_VER = "1.78.1"
+BOUNCYCASTLE_VER = "1.79"
maven_jar(
name = "bcpg",
artifact = "org.bouncycastle:bcpg-jdk18on:" + BOUNCYCASTLE_VER,
- sha1 = "6c8dbcec20355278ec54840e735f63db2479150e",
- src_sha1 = "2ddef60d84dd8c14ebce4c13100f0bc55fed6922",
+ sha1 = "904dd8a8e1c9f7d58d1ffa7f4ca3fb00736a601f",
+ src_sha1 = "9e372826141edb213d5921131ee68dc276dc99ef",
)
maven_jar(
name = "bcprov",
artifact = "org.bouncycastle:bcprov-jdk18on:" + BOUNCYCASTLE_VER,
- sha1 = "39e9e45359e20998eb79c1828751f94a818d25f8",
- src_sha1 = "70f58ec93da543dda6a21614b768cb2e386fd512",
+ sha1 = "4d8e2732bcee15f1db93df266c3f5b70ce5cac21",
+ src_sha1 = "8647816d667ee526a8e3a456229ac5f9f96d2315",
)
maven_jar(
name = "bcutil",
artifact = "org.bouncycastle:bcutil-jdk18on:" + BOUNCYCASTLE_VER,
- sha1 = "5353ca39fe2f148dab9ca1d637a43d0750456254",
- src_sha1 = "8d2e0747f5d806f39a602f7f91610444d88c4e2c",
+ sha1 = "ecfc5aef97cc7676ea0de5c53c407b9f533f0ad5",
+ src_sha1 = "00df03977fb0b80395da655623abca9d7d7dcb66",
)
maven_jar(
name = "bcpkix",
artifact = "org.bouncycastle:bcpkix-jdk18on:" + BOUNCYCASTLE_VER,
- sha1 = "17b3541f736df97465f87d9f5b5dfa4991b37bb3",
- src_sha1 = "3aeaf221772ad0c9c04593688cb86c6eb74d48b9",
+ sha1 = "7693cec3b8779b74b35466dcaeeaac7409872954",
+ src_sha1 = "57a60d1d9f75320eef70a095dfae679d97ade1c2",
)
diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF
index 251a2f7..3644186 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-17
Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.ant.tasks;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
+ 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.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 e32e54f..a145760 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 b59b2be..b22bdb9 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
Import-Package: org.apache.tools.ant,
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)"
+ org.eclipse.jgit.storage.file;version="[7.1.2,7.2.0)"
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.ant;version="7.0.2",
- org.eclipse.jgit.ant.tasks;version="7.0.2";
+Export-Package: org.eclipse.jgit.ant;version="7.1.2",
+ org.eclipse.jgit.ant.tasks;version="7.1.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 2a78006..9fb41bb 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ant;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ant;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml
index b60743d..0376cef 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 1318c44..c9d4352 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.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.0.2";
+Export-Package: org.eclipse.jgit.archive;version="7.1.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.0.2";x-internal:=true
+ org.eclipse.jgit.archive.internal;version="7.1.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 95915c2..9d11d98 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.archive;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.archive;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml
index aa1e770..f557b01 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 dbf35a3..60f83cb 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.benchmarks</artifactId>
diff --git a/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java
new file mode 100644
index 0000000..19297eb
--- /dev/null
+++ b/org.eclipse.jgit.benchmarks/src/org/eclipse/jgit/benchmarks/RawTextBenchmark.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.benchmarks;
+
+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;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.eclipse.jgit.diff.RawText.getBufferSize;
+import static org.eclipse.jgit.diff.RawText.isBinary;
+import static org.eclipse.jgit.diff.RawText.isCrLfText;
+
+@State(Scope.Thread)
+public class RawTextBenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+
+ @Param({"1", "2", "3", "4", "5", "6"})
+ int testIndex;
+
+ @Param({"false", "true"})
+ boolean complete;
+
+ byte[] bytes;
+
+ @Setup
+ public void setupBenchmark() {
+ switch (testIndex) {
+ case 1: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ bytes = tmpBytes;
+ break;
+ }
+ case 2: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ byte[] tmpBytes2 = new byte[tmpBytes.length + 1];
+ System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length);
+ tmpBytes2[500] = '\0';
+ tmpBytes2[tmpBytes.length] = '\0';
+ bytes = tmpBytes2;
+ break;
+ }
+ case 3: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ byte[] tmpBytes2 = new byte[tmpBytes.length + 1];
+ System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length);
+ tmpBytes2[500] = '\r';
+ tmpBytes2[tmpBytes.length] = '\r';
+ bytes = tmpBytes2;
+ break;
+ }
+ case 4: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ byte[] tmpBytes2 = new byte[tmpBytes.length + 1];
+ System.arraycopy(tmpBytes, 0, tmpBytes2, 0, tmpBytes.length);
+ tmpBytes2[499] = '\r';
+ tmpBytes2[500] = '\n';
+ tmpBytes2[tmpBytes.length - 1] = '\r';
+ tmpBytes2[tmpBytes.length] = '\n';
+ bytes = tmpBytes2;
+ break;
+ }
+ case 5: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ tmpBytes[0] = '\0';
+ bytes = tmpBytes;
+ break;
+ }
+ case 6: {
+ byte[] tmpBytes = "a".repeat(102400).getBytes();
+ tmpBytes[0] = '\r';
+ bytes = tmpBytes;
+ break;
+ }
+ default:
+ }
+ }
+
+ @TearDown
+ public void teardown() {
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsCrLfTextOld(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isCrLfTextOld(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsCrLfTextNewCandidate1(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isCrLfTextNewCandidate1(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsCrLfTextNewCandidate2(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isCrLfTextNewCandidate2(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsCrLfTextNewCandidate3(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isCrLfTextNewCandidate3(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsCrLfTextNew(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isCrLfText(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsBinaryOld(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isBinaryOld(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+
+ @Benchmark
+ @BenchmarkMode({Mode.AverageTime})
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
+ @Fork(1)
+ public void testIsBinaryNew(Blackhole blackhole, BenchmarkState state) {
+ blackhole.consume(
+ isBinary(
+ state.bytes,
+ state.bytes.length,
+ state.complete
+ )
+ );
+ }
+
+
+ /**
+ * Determine heuristically whether a byte array represents binary (as
+ * opposed to text) content.
+ *
+ * @param raw
+ * the raw file content.
+ * @param length
+ * number of bytes in {@code raw} to evaluate. This should be
+ * {@code raw.length} unless {@code raw} was over-allocated by
+ * the caller.
+ * @param complete
+ * whether {@code raw} contains the whole data
+ * @return true if raw is likely to be a binary file, false otherwise
+ * @since 6.0
+ */
+ public static boolean isBinaryOld(byte[] raw, int length, boolean complete) {
+ // Similar heuristic as C Git. Differences:
+ // - limited buffer size; may be only the beginning of a large blob
+ // - no counting of printable vs. non-printable bytes < 0x20 and 0x7F
+ int maxLength = getBufferSize();
+ boolean isComplete = complete;
+ if (length > maxLength) {
+ // We restrict the length in all cases to getBufferSize() to get
+ // predictable behavior. Sometimes we load streams, and sometimes we
+ // have the full data in memory. With streams, we never look at more
+ // than the first getBufferSize() bytes. If we looked at more when
+ // we have the full data, different code paths in JGit might come to
+ // different conclusions.
+ length = maxLength;
+ isComplete = false;
+ }
+ byte last = 'x'; // Just something inconspicuous.
+ for (int ptr = 0; ptr < length; ptr++) {
+ byte curr = raw[ptr];
+ if (isBinary(curr, last)) {
+ return true;
+ }
+ last = curr;
+ }
+ if (isComplete) {
+ // Buffer contains everything...
+ return last == '\r'; // ... so this must be a lone CR
+ }
+ return false;
+ }
+
+ /**
+ * Determine heuristically whether a byte array represents text content
+ * using CR-LF as line separator.
+ *
+ * @param raw the raw file content.
+ * @param length number of bytes in {@code raw} to evaluate.
+ * @param complete whether {@code raw} contains the whole data
+ * @return {@code true} if raw is likely to be CR-LF delimited text,
+ * {@code false} otherwise
+ * @since 6.0
+ */
+ public static boolean isCrLfTextOld(byte[] raw, int length, boolean complete) {
+ boolean has_crlf = false;
+ byte last = 'x'; // Just something inconspicuous
+ for (int ptr = 0; ptr < length; ptr++) {
+ byte curr = raw[ptr];
+ if (isBinary(curr, last)) {
+ return false;
+ }
+ if (curr == '\n' && last == '\r') {
+ has_crlf = true;
+ }
+ last = curr;
+ }
+ if (last == '\r') {
+ if (complete) {
+ // Lone CR: it's binary after all.
+ return false;
+ }
+ // Tough call. If the next byte, which we don't have, would be a
+ // '\n', it'd be a CR-LF text, otherwise it'd be binary. Just decide
+ // based on what we already scanned; it wasn't binary until now.
+ }
+ return has_crlf;
+ }
+
+ /**
+ * Determine heuristically whether a byte array represents text content
+ * using CR-LF as line separator.
+ *
+ * @param raw
+ * the raw file content.
+ * @param length
+ * number of bytes in {@code raw} to evaluate.
+ * @return {@code true} if raw is likely to be CR-LF delimited text,
+ * {@code false} otherwise
+ * @param complete
+ * whether {@code raw} contains the whole data
+ * @since 6.0
+ */
+ public static boolean isCrLfTextNewCandidate1(byte[] raw, int length, boolean complete) {
+ boolean has_crlf = false;
+
+ // first detect empty
+ if (length <= 0) {
+ return false;
+ }
+
+ // next detect '\0'
+ for (int reversePtr = length - 1; reversePtr >= 0; --reversePtr) {
+ if (raw[reversePtr] == '\0') {
+ return false;
+ }
+ }
+
+ // if '\r' be last, then if complete then return non-crlf
+ if (raw[length - 1] == '\r' && complete) {
+ return false;
+ }
+
+ for (int ptr = 0; ptr < length - 1; ptr++) {
+ byte curr = raw[ptr];
+ if (curr == '\r') {
+ byte next = raw[ptr + 1];
+ if (next != '\n') {
+ return false;
+ }
+ // else
+ // we have crlf here
+ has_crlf = true;
+ // as next is '\n', it can never be '\r', just skip it from next check
+ ++ptr;
+ }
+ }
+
+ return has_crlf;
+ }
+
+ /**
+ * Determine heuristically whether a byte array represents text content
+ * using CR-LF as line separator.
+ *
+ * @param raw
+ * the raw file content.
+ * @param length
+ * number of bytes in {@code raw} to evaluate.
+ * @return {@code true} if raw is likely to be CR-LF delimited text,
+ * {@code false} otherwise
+ * @param complete
+ * whether {@code raw} contains the whole data
+ * @since 6.0
+ */
+ public static boolean isCrLfTextNewCandidate2(byte[] raw, int length, boolean complete) {
+ boolean has_crlf = false;
+
+ // first detect empty
+ if (length <= 0) {
+ return false;
+ }
+
+ // if '\r' be last, then if complete then return non-crlf
+ byte last = raw[length - 1];
+ if (last == '\0' || last == '\r' && complete) {
+ return false;
+ }
+
+ for (int ptr = 0; ptr < length - 1; ptr++) {
+ byte b = raw[ptr];
+ switch (b) {
+ case '\0':
+ return false;
+ case '\r': {
+ ++ptr;
+ b = raw[ptr];
+ if (b != '\n') {
+ return false;
+ }
+ // else
+ // we have crlf here
+ has_crlf = true;
+ // as next is '\n', it can never be '\r', just skip it from next check
+ break;
+ }
+ default:
+ // do nothing;
+ break;
+ }
+ }
+
+ return has_crlf;
+ }
+
+ /**
+ * Determine heuristically whether a byte array represents text content
+ * using CR-LF as line separator.
+ *
+ * @param raw
+ * the raw file content.
+ * @param length
+ * number of bytes in {@code raw} to evaluate.
+ * @return {@code true} if raw is likely to be CR-LF delimited text,
+ * {@code false} otherwise
+ * @param complete
+ * whether {@code raw} contains the whole data
+ * @since 6.0
+ */
+ public static boolean isCrLfTextNewCandidate3(byte[] raw, int length, boolean complete) {
+ boolean has_crlf = false;
+
+ int ptr = -1;
+ byte current;
+ while (ptr < length - 2) {
+ current = raw[++ptr];
+ if ('\0' == current || '\r' == current && (raw[++ptr] != '\n' || !(has_crlf = true))) {
+ return false;
+ }
+ }
+
+ if (ptr == length - 2) {
+ // if '\r' be last, then if isComplete then return binary
+ current = raw[++ptr];
+ if('\0' == current || '\r' == current && complete){
+ return false;
+ }
+ }
+
+ return has_crlf;
+ }
+
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(RawTextBenchmark.class.getSimpleName())
+ .forks(1).jvmArgs("-ea").build();
+ new Runner(opt).run();
+ }
+}
diff --git a/org.eclipse.jgit.coverage/pom.xml b/org.eclipse.jgit.coverage/pom.xml
index 499c0de..da71786 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.0.2-SNAPSHOT</version>
+ <version>7.1.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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ant</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.archive</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.apache</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.server</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.lfs</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.lfs.server</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.pgm</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ui</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ant.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.http.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.pgm.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.lfs.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.lfs.server.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
- <version>7.0.2-SNAPSHOT</version>
+ <version>7.1.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 707c55b..c934a4f 100644
--- a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
@@ -3,19 +3,20 @@
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit.gpg.bc.test
Bundle-SymbolicName: org.eclipse.jgit.gpg.bc.test
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.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.bouncycastle.jce.provider;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)",
- org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)",
- org.eclipse.jgit.gpg.bc.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.gpg.bc.internal.keys;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.sha1;version="[7.0.2,7.1.0)",
+Import-Package: org.bouncycastle.asn1.cryptlib;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jce.provider;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp;version="[1.79.0,2.0.0)",
+ 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.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 2310b6f..d821f19 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.gpg.bc.test</artifactId>
diff --git a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
index fed0610..d486c97 100644
--- a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
+++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 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
@@ -9,10 +9,7 @@
*/
package org.eclipse.jgit.gpg.bc.internal.keys;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -20,8 +17,6 @@
import java.security.Security;
import java.util.Iterator;
-import javax.crypto.Cipher;
-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
@@ -49,39 +44,15 @@ public static void ensureBC() {
}
}
- private static volatile Boolean haveOCB;
-
- private static boolean ocbAvailable() {
- Boolean haveIt = haveOCB;
- if (haveIt != null) {
- return haveIt.booleanValue();
- }
- try {
- Cipher c = Cipher.getInstance("AES/OCB/NoPadding"); //$NON-NLS-1$
- if (c == null) {
- haveOCB = Boolean.FALSE;
- return false;
- }
- } catch (NoClassDefFoundError | Exception e) {
- haveOCB = Boolean.FALSE;
- return false;
- }
- haveOCB = Boolean.TRUE;
- return true;
- }
-
private static class TestData {
final String name;
final boolean encrypted;
- final boolean keyValue;
-
- TestData(String name, boolean encrypted, boolean keyValue) {
+ TestData(String name, boolean encrypted) {
this.name = name;
this.encrypted = encrypted;
- this.keyValue = keyValue;
}
@Override
@@ -93,19 +64,12 @@ public String toString() {
@Parameters(name = "{0}")
public static TestData[] initTestData() {
return new TestData[] {
- new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false),
- new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true),
- new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true),
- new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true),
- new TestData("62D43D7F117F7A5E4998ECB6617EE9942D069C14", true, true),
- new TestData("faked", false, true) };
- }
-
- private static byte[] readTestKey(String filename) throws Exception {
- try (InputStream in = new BufferedInputStream(
- SecretKeysTest.class.getResourceAsStream(filename))) {
- return SecretKeys.keyFromNameValueFormat(in);
- }
+ new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false),
+ new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false),
+ new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true),
+ new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true),
+ new TestData("62D43D7F117F7A5E4998ECB6617EE9942D069C14", true),
+ new TestData("faked", false) };
}
private static PGPPublicKey readAsc(InputStream in)
@@ -131,11 +95,6 @@ private static PGPPublicKey readAsc(InputStream in)
@Test
public void testKeyRead() throws Exception {
- if (data.keyValue) {
- byte[] bytes = readTestKey(data.name + ".key");
- assertEquals('(', bytes[0]);
- assertEquals(')', bytes[bytes.length - 1]);
- }
try (InputStream pubIn = this.getClass()
.getResourceAsStream(data.name + ".asc")) {
if (pubIn != null) {
@@ -151,11 +110,6 @@ public void testKeyRead() throws Exception {
: null,
publicKey);
assertNotNull(secretKey);
- } catch (PGPException e) {
- // Currently we may not be able to load OCB-encrypted keys.
- assertTrue(e.toString(), e.getMessage().contains("OCB"));
- assertTrue(data.encrypted);
- assertFalse(ocbAvailable());
}
}
}
diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
index 6bed05d..77fd134 100644
--- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
@@ -3,31 +3,28 @@
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.0.2,7.1.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[7.1.2,7.2.0)"
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: OSGI-INF/l10n/gpg_bc
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.bouncycastle.asn1;version="[1.69.0,2.0.0)",
- org.bouncycastle.asn1.x9;version="[1.69.0,2.0.0)",
- org.bouncycastle.bcpg;version="[1.69.0,2.0.0)",
- org.bouncycastle.bcpg.sig;version="[1.69.0,2.0.0)",
- org.bouncycastle.crypto.ec;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg.keybox;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg.keybox.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.jcajce.interfaces;version="[1.69.0,2.0.0)",
- org.bouncycastle.jcajce.util;version="[1.69.0,2.0.0)",
- org.bouncycastle.jce.provider;version="[1.69.0,2.0.0)",
- org.bouncycastle.math.ec;version="[1.69.0,2.0.0)",
- org.bouncycastle.math.field;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.operator;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.operator.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.util;version="[1.69.0,2.0.0)",
- org.bouncycastle.util.encoders;version="[1.69.0,2.0.0)",
- org.bouncycastle.util.io;version="[1.69.0,2.0.0)",
+Import-Package: org.bouncycastle.asn1;version="[1.79.0,2.0.0)",
+ org.bouncycastle.asn1.x9;version="[1.79.0,2.0.0)",
+ org.bouncycastle.bcpg;version="[1.79.0,2.0.0)",
+ org.bouncycastle.bcpg.sig;version="[1.79.0,2.0.0)",
+ org.bouncycastle.crypto.ec;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg.keybox;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg.keybox.jcajce;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jcajce.interfaces;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jcajce.util;version="[1.79.0,2.0.0)",
+ org.bouncycastle.math.ec;version="[1.79.0,2.0.0)",
+ org.bouncycastle.math.field;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.jcajce;version="[1.79.0,2.0.0)",
+ 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.slf4j;version="[1.7.0,3.0.0)"
-Export-Package: org.eclipse.jgit.gpg.bc.internal;version="7.0.2";x-friends:="org.eclipse.jgit.gpg.bc.test",
- org.eclipse.jgit.gpg.bc.internal.keys;version="7.0.2";x-friends:="org.eclipse.jgit.gpg.bc.test"
+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"
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 9dc9a9f..e8d547c 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.gpg.bc;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.gpg.bc/about.html b/org.eclipse.jgit.gpg.bc/about.html
index fc527d5..92b9409 100644
--- a/org.eclipse.jgit.gpg.bc/about.html
+++ b/org.eclipse.jgit.gpg.bc/about.html
@@ -58,32 +58,6 @@
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.</p>
-<hr>
-<p><b>org.eclipse.jgit.gpg.bc.internal.keys.SExprParser - MIT</b></p>
-
-<p>Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc.
-(<a href="https://www.bouncycastle.org">https://www.bouncycastle.org</a>)</p>
-
-<p>
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software
-and associated documentation files (the "Software"), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-</p>
-<p>
-The above copyright notice and this permission notice shall be included in all copies or substantial
-portions of the Software.
-</p>
-<p>
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-</p>
-
</body>
</html>
diff --git a/org.eclipse.jgit.gpg.bc/pom.xml b/org.eclipse.jgit.gpg.bc/pom.xml
index f51b978..e4c535d 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.gpg.bc</artifactId>
diff --git a/org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties b/org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
index 77ca2cd..9e7f98c 100644
--- a/org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
+++ b/org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
@@ -1,7 +1,5 @@
corrupt25519Key=Ed25519/Curve25519 public key has wrong length: {0}
credentialPassphrase=Passphrase
-cryptCipherError=Cannot create cipher to decrypt: {0}
-cryptWrongDecryptedLength=Decrypted key has wrong length; expected {0} bytes, got only {1} bytes
gpgFailedToParseSecretKey=Failed to parse secret key file {0}. Is the entered passphrase correct?
gpgNoCredentialsProvider=missing credentials provider
gpgNoKeygrip=Cannot find key {0}: cannot determine key grip
@@ -9,22 +7,14 @@
gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0}
gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0}
gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0}
-gpgNoSuchAlgorithm=Cannot decrypt encrypted secret key: encryption algorithm {0} is not available
gpgNotASigningKey=Secret key ({0}) is not suitable for signing
gpgKeyInfo=GPG Key (fingerprint {0})
gpgSigningCancelled=Signing was cancelled
+keyAlgorithmMismatch=Secret key has a different algorithm than the public key
+keyMismatch=Secret key does not match public key; public key is {0} {1} while secret key is for {2} {3}
logWarnGnuPGHome=Cannot access GPG home directory given by environment variable GNUPGHOME={}
logWarnGpgHomeProperty=Cannot access GPG home directory given by Java system property jgit.gpg.home={}
nonSignatureError=Signature does not decode into a signature object
-secretKeyTooShort=Secret key file corrupt; only {0} bytes read
-sexprHexNotClosed=Hex number in s-expression not closed
-sexprHexOdd=Hex number in s-expression has an odd number of digits
-sexprStringInvalidEscape=Invalid escape {0} in s-expression
-sexprStringInvalidEscapeAtEnd=Invalid s-expression: quoted string ends with escape character
-sexprStringInvalidHexEscape=Invalid hex escape in s-expression
-sexprStringInvalidOctalEscape=Invalid octal escape in s-expression
-sexprStringNotClosed=String in s-expression not closed
-sexprUnhandled=Unhandled token {0} in s-expression
signatureInconsistent=Inconsistent signature; key ID {0} does not match issuer fingerprint {1}
signatureKeyLookupError=Error occurred while looking for public key
signatureNoKeyInfo=No way to determine a public key from the signature
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
index 705e195..fcae7c2 100644
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
+++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2021 Salesforce and others
+ * Copyright (C) 2018, 2024 Salesforce 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
@@ -30,8 +30,6 @@ public static BCText get() {
// @formatter:off
/***/ public String corrupt25519Key;
/***/ public String credentialPassphrase;
- /***/ public String cryptCipherError;
- /***/ public String cryptWrongDecryptedLength;
/***/ public String gpgFailedToParseSecretKey;
/***/ public String gpgNoCredentialsProvider;
/***/ public String gpgNoKeygrip;
@@ -39,22 +37,14 @@ public static BCText get() {
/***/ public String gpgNoKeyInLegacySecring;
/***/ public String gpgNoPublicKeyFound;
/***/ public String gpgNoSecretKeyForPublicKey;
- /***/ public String gpgNoSuchAlgorithm;
/***/ public String gpgNotASigningKey;
/***/ public String gpgKeyInfo;
/***/ public String gpgSigningCancelled;
+ /***/ public String keyAlgorithmMismatch;
+ /***/ public String keyMismatch;
/***/ public String logWarnGnuPGHome;
/***/ public String logWarnGpgHomeProperty;
/***/ public String nonSignatureError;
- /***/ public String secretKeyTooShort;
- /***/ public String sexprHexNotClosed;
- /***/ public String sexprHexOdd;
- /***/ public String sexprStringInvalidEscape;
- /***/ public String sexprStringInvalidEscapeAtEnd;
- /***/ public String sexprStringInvalidHexEscape;
- /***/ public String sexprStringInvalidOctalEscape;
- /***/ public String sexprStringNotClosed;
- /***/ public String sexprUnhandled;
/***/ public String signatureInconsistent;
/***/ public String signatureKeyLookupError;
/***/ public String signatureNoKeyInfo;
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
index 1d187a5..adac9b1 100644
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
+++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
@@ -105,7 +105,8 @@ public GpgSignature sign(Repository repository, GpgConfig config,
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(
publicKey.getAlgorithm(),
- HashAlgorithmTags.SHA256));
+ HashAlgorithmTags.SHA256),
+ publicKey);
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator();
subpackets.setIssuerFingerprint(false, publicKey);
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java
deleted file mode 100644
index 3924d68..0000000
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> 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.gpg.bc.internal.keys;
-
-import java.security.NoSuchAlgorithmException;
-import java.text.MessageFormat;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
-import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
-import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.bouncycastle.util.Arrays;
-import org.eclipse.jgit.gpg.bc.internal.BCText;
-
-/**
- * A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption.
- * It accepts an AAD in the factory's constructor, so the factory can be used to
- * create a {@link PBESecretKeyDecryptor} only for a particular input.
- * <p>
- * For JGit's needs, this is sufficient, but for a general upstream
- * implementation that limitation might not be acceptable.
- * </p>
- */
-class OCBPBEProtectionRemoverFactory
- implements PBEProtectionRemoverFactory {
-
- private final PGPDigestCalculatorProvider calculatorProvider;
-
- private final char[] passphrase;
-
- private final byte[] aad;
-
- /**
- * Creates a new factory instance with the given parameters.
- * <p>
- * Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s
- * created by the factory can be used to decrypt only a particular input
- * matching this AAD.
- * </p>
- *
- * @param passphrase to use for secret key derivation
- * @param calculatorProvider for computing digests
- * @param aad for the OCB decryption
- */
- OCBPBEProtectionRemoverFactory(char[] passphrase,
- PGPDigestCalculatorProvider calculatorProvider, byte[] aad) {
- this.calculatorProvider = calculatorProvider;
- this.passphrase = passphrase;
- this.aad = aad;
- }
-
- @Override
- public PBESecretKeyDecryptor createDecryptor(String protection)
- throws PGPException {
- return new PBESecretKeyDecryptor(passphrase, calculatorProvider) {
-
- @Override
- public byte[] recoverKeyData(int encAlgorithm, byte[] key,
- byte[] iv, byte[] encrypted, int encryptedOffset,
- int encryptedLength) throws PGPException {
- String algorithmName = PGPUtil
- .getSymmetricCipherName(encAlgorithm);
- byte[] decrypted = null;
- try {
- // errorprone: "Dynamically constructed transformation
- // strings are also flagged, as they may conceal an instance
- // of ECB mode."
- @SuppressWarnings("InsecureCryptoUsage")
- Cipher c = Cipher
- .getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$
- SecretKey secretKey = new SecretKeySpec(key, algorithmName);
- c.init(Cipher.DECRYPT_MODE, secretKey,
- new IvParameterSpec(iv));
- c.updateAAD(aad);
- decrypted = new byte[c.getOutputSize(encryptedLength)];
- int decryptedLength = c.update(encrypted, encryptedOffset,
- encryptedLength, decrypted);
- // doFinal() for OCB will check the MAC and throw an
- // exception if it doesn't match
- decryptedLength += c.doFinal(decrypted, decryptedLength);
- if (decryptedLength != decrypted.length) {
- throw new PGPException(MessageFormat.format(
- BCText.get().cryptWrongDecryptedLength,
- Integer.valueOf(decryptedLength),
- Integer.valueOf(decrypted.length)));
- }
- byte[] result = decrypted;
- decrypted = null; // Don't clear in finally
- return result;
- } catch (NoClassDefFoundError e) {
- String msg = MessageFormat.format(
- BCText.get().gpgNoSuchAlgorithm,
- algorithmName + "/OCB"); //$NON-NLS-1$
- throw new PGPException(msg,
- new NoSuchAlgorithmException(msg, e));
- } catch (PGPException e) {
- throw e;
- } catch (Exception e) {
- throw new PGPException(
- MessageFormat.format(BCText.get().cryptCipherError,
- e.getLocalizedMessage()),
- e);
- } finally {
- if (decrypted != null) {
- // Prevent halfway decrypted data leaking.
- Arrays.fill(decrypted, (byte) 0);
- }
- }
- }
- };
- }
-}
\ No newline at end of file
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java
deleted file mode 100644
index fd030ee..0000000
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java
+++ /dev/null
@@ -1,859 +0,0 @@
-/*
- * Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
- * <p>
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in the Software without restriction,
- *including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- * </p>
- * <p>
- * The above copyright notice and this permission notice shall be included in all copies or substantial
- * portions of the Software.
- * </p>
- * <p>
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * </p>
- */
-package org.eclipse.jgit.gpg.bc.internal.keys;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.util.Date;
-
-import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.x9.ECNamedCurveTable;
-import org.bouncycastle.bcpg.DSAPublicBCPGKey;
-import org.bouncycastle.bcpg.DSASecretBCPGKey;
-import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
-import org.bouncycastle.bcpg.ECPublicBCPGKey;
-import org.bouncycastle.bcpg.ECSecretBCPGKey;
-import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
-import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
-import org.bouncycastle.bcpg.PublicKeyPacket;
-import org.bouncycastle.bcpg.RSAPublicBCPGKey;
-import org.bouncycastle.bcpg.RSASecretBCPGKey;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.bcpg.SecretKeyPacket;
-import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
-import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
-import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
-import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
-import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Strings;
-
-/**
- * A parser for secret keys stored in s-expressions. Original BouncyCastle code
- * modified by the JGit team to:
- * <ul>
- * <li>handle unencrypted DSA, EC, and ElGamal keys (upstream only handles
- * unencrypted RSA)</li>
- * <li>handle secret keys using AES/OCB as encryption (those don't have a
- * hash)</li>
- * <li>fix EC parsing to account for "flags" sub-list present for ed25519 and
- * curve25519</li>
- * <li>add support for ed25519 OIDs unknown to BouncyCastle</li>
- * </ul>
- */
-@SuppressWarnings("nls")
-public class SExprParser {
- private final PGPDigestCalculatorProvider digestProvider;
-
- /**
- * Base constructor.
- *
- * @param digestProvider
- * a provider for digest calculations. Used to confirm key
- * protection hashes.
- */
- public SExprParser(PGPDigestCalculatorProvider digestProvider) {
- this.digestProvider = digestProvider;
- }
-
- /**
- * Parse a secret key from one of the GPG S expression keys associating it
- * with the passed in public key.
- *
- * @param inputStream
- * to read from
- * @param keyProtectionRemoverFactory
- * for decrypting encrypted keys
- * @param pubKey
- * the private key should belong to
- *
- * @return a secret key object.
- * @throws IOException
- * if an IO error occurred
- * @throws PGPException
- * if some PGP error occurred
- */
- public PGPSecretKey parseSecretKey(InputStream inputStream,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory,
- PGPPublicKey pubKey) throws IOException, PGPException {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String type;
-
- type = SXprUtils.readString(inputStream, inputStream.read());
- if (type.equals("protected-private-key")
- || type.equals("private-key")) {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String keyType = SXprUtils.readString(inputStream,
- inputStream.read());
- if (keyType.equals("ecc")) {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String curveID = SXprUtils.readString(inputStream,
- inputStream.read());
- String curveName = SXprUtils.readString(inputStream,
- inputStream.read());
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- byte[] qVal;
-
- SXprUtils.skipOpenParenthesis(inputStream);
-
- type = SXprUtils.readString(inputStream, inputStream.read());
- // JGit: c.f. https://github.com/bcgit/bc-java/issues/1590.
- // There may be a flags sub-list here for ed25519 or curve25519.
- if (type.equals("flags")) {
- SXprUtils.readString(inputStream, inputStream.read());
- SXprUtils.skipCloseParenthesis(inputStream);
- SXprUtils.skipOpenParenthesis(inputStream);
- type = SXprUtils.readString(inputStream,
- inputStream.read());
- }
- if (type.equals("q")) {
- qVal = SXprUtils.readBytes(inputStream, inputStream.read());
- } else {
- throw new PGPException("no q value found");
- }
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- BigInteger d = processECSecretKey(inputStream, curveID,
- curveName, qVal, keyProtectionRemoverFactory);
-
- if (curveName.startsWith("NIST ")) {
- curveName = curveName.substring("NIST ".length());
- }
-
- // JGit: BC doesn't know Ed25519 curve name.
- ASN1ObjectIdentifier curveOid = ECNamedCurveTable
- .getOID(curveName);
- if (curveOid == null) {
- curveOid = ObjectIds.getByName(curveName);
- }
- ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey(
- curveOid,
- new BigInteger(1, qVal));
- ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey
- .getPublicKeyPacket().getKey();
- if (!ObjectIds.match(basePubKey.getCurveOID(),
- assocPubKey.getCurveOID())
- || !basePubKey.getEncodedPoint()
- .equals(assocPubKey.getEncodedPoint())) {
- throw new PGPException(
- "passed in public key does not match secret key");
- }
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubKey.getPublicKeyPacket(),
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new ECSecretBCPGKey(d).getEncoded()),
- pubKey);
- } else if (keyType.equals("dsa")) {
- BigInteger p = readBigInteger("p", inputStream);
- BigInteger q = readBigInteger("q", inputStream);
- BigInteger g = readBigInteger("g", inputStream);
-
- BigInteger y = readBigInteger("y", inputStream);
-
- BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
- keyProtectionRemoverFactory);
-
- DSAPublicBCPGKey basePubKey = new DSAPublicBCPGKey(p, q, g, y);
- DSAPublicBCPGKey assocPubKey = (DSAPublicBCPGKey) pubKey
- .getPublicKeyPacket().getKey();
- if (!basePubKey.getP().equals(assocPubKey.getP())
- || !basePubKey.getQ().equals(assocPubKey.getQ())
- || !basePubKey.getG().equals(assocPubKey.getG())
- || !basePubKey.getY().equals(assocPubKey.getY())) {
- throw new PGPException(
- "passed in public key does not match secret key");
- }
- return new PGPSecretKey(
- new SecretKeyPacket(pubKey.getPublicKeyPacket(),
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new DSASecretBCPGKey(x).getEncoded()),
- pubKey);
- } else if (keyType.equals("elg")) {
- BigInteger p = readBigInteger("p", inputStream);
- BigInteger g = readBigInteger("g", inputStream);
-
- BigInteger y = readBigInteger("y", inputStream);
-
- BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
- keyProtectionRemoverFactory);
-
- ElGamalPublicBCPGKey basePubKey = new ElGamalPublicBCPGKey(p, g,
- y);
- ElGamalPublicBCPGKey assocPubKey = (ElGamalPublicBCPGKey) pubKey
- .getPublicKeyPacket().getKey();
- if (!basePubKey.getP().equals(assocPubKey.getP())
- || !basePubKey.getG().equals(assocPubKey.getG())
- || !basePubKey.getY().equals(assocPubKey.getY())) {
- throw new PGPException(
- "passed in public key does not match secret key");
- }
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubKey.getPublicKeyPacket(),
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new ElGamalSecretBCPGKey(x).getEncoded()),
- pubKey);
- } else if (keyType.equals("rsa")) {
- BigInteger n = readBigInteger("n", inputStream);
- BigInteger e = readBigInteger("e", inputStream);
-
- BigInteger[] values = processRSASecretKey(inputStream, n, e,
- keyProtectionRemoverFactory);
-
- // TODO: type of RSA key?
- RSAPublicBCPGKey basePubKey = new RSAPublicBCPGKey(n, e);
- RSAPublicBCPGKey assocPubKey = (RSAPublicBCPGKey) pubKey
- .getPublicKeyPacket().getKey();
- if (!basePubKey.getModulus().equals(assocPubKey.getModulus())
- || !basePubKey.getPublicExponent()
- .equals(assocPubKey.getPublicExponent())) {
- throw new PGPException(
- "passed in public key does not match secret key");
- }
-
- return new PGPSecretKey(new SecretKeyPacket(
- pubKey.getPublicKeyPacket(),
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new RSASecretBCPGKey(values[0], values[1], values[2])
- .getEncoded()),
- pubKey);
- } else {
- throw new PGPException("unknown key type: " + keyType);
- }
- }
-
- throw new PGPException("unknown key type found");
- }
-
- /**
- * Parse a secret key from one of the GPG S expression keys.
- *
- * @param inputStream
- * to read from
- * @param keyProtectionRemoverFactory
- * for decrypting encrypted keys
- * @param fingerPrintCalculator
- * for calculating key fingerprints
- *
- * @return a secret key object.
- * @throws IOException
- * if an IO error occurred
- * @throws PGPException
- * if a PGP error occurred
- */
- public PGPSecretKey parseSecretKey(InputStream inputStream,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory,
- KeyFingerPrintCalculator fingerPrintCalculator)
- throws IOException, PGPException {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String type;
-
- type = SXprUtils.readString(inputStream, inputStream.read());
- if (type.equals("protected-private-key")
- || type.equals("private-key")) {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String keyType = SXprUtils.readString(inputStream,
- inputStream.read());
- if (keyType.equals("ecc")) {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String curveID = SXprUtils.readString(inputStream,
- inputStream.read());
- String curveName = SXprUtils.readString(inputStream,
- inputStream.read());
-
- if (curveName.startsWith("NIST ")) {
- curveName = curveName.substring("NIST ".length());
- }
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- byte[] qVal;
-
- SXprUtils.skipOpenParenthesis(inputStream);
-
- type = SXprUtils.readString(inputStream, inputStream.read());
- // JGit: c.f. https://github.com/bcgit/bc-java/issues/1590.
- // There may be a flags sub-list here for ed25519 or curve25519.
- if (type.equals("flags")) {
- SXprUtils.readString(inputStream, inputStream.read());
- SXprUtils.skipCloseParenthesis(inputStream);
- SXprUtils.skipOpenParenthesis(inputStream);
- type = SXprUtils.readString(inputStream,
- inputStream.read());
- }
- if (type.equals("q")) {
- qVal = SXprUtils.readBytes(inputStream, inputStream.read());
- } else {
- throw new PGPException("no q value found");
- }
-
- PublicKeyPacket pubPacket = new PublicKeyPacket(
- PublicKeyAlgorithmTags.ECDSA, new Date(),
- new ECDSAPublicBCPGKey(
- ECNamedCurveTable.getOID(curveName),
- new BigInteger(1, qVal)));
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- BigInteger d = processECSecretKey(inputStream, curveID,
- curveName, qVal, keyProtectionRemoverFactory);
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubPacket,
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new ECSecretBCPGKey(d).getEncoded()),
- new PGPPublicKey(pubPacket, fingerPrintCalculator));
- } else if (keyType.equals("dsa")) {
- BigInteger p = readBigInteger("p", inputStream);
- BigInteger q = readBigInteger("q", inputStream);
- BigInteger g = readBigInteger("g", inputStream);
-
- BigInteger y = readBigInteger("y", inputStream);
-
- BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
- keyProtectionRemoverFactory);
-
- PublicKeyPacket pubPacket = new PublicKeyPacket(
- PublicKeyAlgorithmTags.DSA, new Date(),
- new DSAPublicBCPGKey(p, q, g, y));
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubPacket,
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new DSASecretBCPGKey(x).getEncoded()),
- new PGPPublicKey(pubPacket, fingerPrintCalculator));
- } else if (keyType.equals("elg")) {
- BigInteger p = readBigInteger("p", inputStream);
- BigInteger g = readBigInteger("g", inputStream);
-
- BigInteger y = readBigInteger("y", inputStream);
-
- BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
- keyProtectionRemoverFactory);
-
- PublicKeyPacket pubPacket = new PublicKeyPacket(
- PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, new Date(),
- new ElGamalPublicBCPGKey(p, g, y));
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubPacket,
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new ElGamalSecretBCPGKey(x).getEncoded()),
- new PGPPublicKey(pubPacket, fingerPrintCalculator));
- } else if (keyType.equals("rsa")) {
- BigInteger n = readBigInteger("n", inputStream);
- BigInteger e = readBigInteger("e", inputStream);
-
- BigInteger[] values = processRSASecretKey(inputStream, n, e,
- keyProtectionRemoverFactory);
-
- // TODO: type of RSA key?
- PublicKeyPacket pubPacket = new PublicKeyPacket(
- PublicKeyAlgorithmTags.RSA_GENERAL, new Date(),
- new RSAPublicBCPGKey(n, e));
-
- return new PGPSecretKey(
- new SecretKeyPacket(pubPacket,
- SymmetricKeyAlgorithmTags.NULL, null, null,
- new RSASecretBCPGKey(values[0], values[1],
- values[2]).getEncoded()),
- new PGPPublicKey(pubPacket, fingerPrintCalculator));
- } else {
- throw new PGPException("unknown key type: " + keyType);
- }
- }
-
- throw new PGPException("unknown key type found");
- }
-
- private BigInteger readBigInteger(String expectedType,
- InputStream inputStream) throws IOException, PGPException {
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String type = SXprUtils.readString(inputStream, inputStream.read());
- if (!type.equals(expectedType)) {
- throw new PGPException(expectedType + " value expected");
- }
-
- byte[] nBytes = SXprUtils.readBytes(inputStream, inputStream.read());
- BigInteger v = new BigInteger(1, nBytes);
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- return v;
- }
-
- private static byte[][] extractData(InputStream inputStream,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory)
- throws PGPException, IOException {
- byte[] data;
- byte[] protectedAt = null;
-
- SXprUtils.skipOpenParenthesis(inputStream);
-
- String type = SXprUtils.readString(inputStream, inputStream.read());
- if (type.equals("protected")) {
- String protection = SXprUtils.readString(inputStream,
- inputStream.read());
-
- SXprUtils.skipOpenParenthesis(inputStream);
-
- S2K s2k = SXprUtils.parseS2K(inputStream);
-
- byte[] iv = SXprUtils.readBytes(inputStream, inputStream.read());
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- byte[] secKeyData = SXprUtils.readBytes(inputStream,
- inputStream.read());
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- PBESecretKeyDecryptor keyDecryptor = keyProtectionRemoverFactory
- .createDecryptor(protection);
-
- // TODO: recognise other algorithms
- byte[] key = keyDecryptor.makeKeyFromPassPhrase(
- SymmetricKeyAlgorithmTags.AES_128, s2k);
-
- data = keyDecryptor.recoverKeyData(
- SymmetricKeyAlgorithmTags.AES_128, key, iv, secKeyData, 0,
- secKeyData.length);
-
- // check if protected at is present
- if (inputStream.read() == '(') {
- ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-
- bOut.write('(');
- int ch;
- while ((ch = inputStream.read()) >= 0 && ch != ')') {
- bOut.write(ch);
- }
-
- if (ch != ')') {
- throw new IOException("unexpected end to SExpr");
- }
-
- bOut.write(')');
-
- protectedAt = bOut.toByteArray();
- }
-
- SXprUtils.skipCloseParenthesis(inputStream);
- SXprUtils.skipCloseParenthesis(inputStream);
- } else if (type.equals("d") || type.equals("x")) {
- // JGit modification: unencrypted DSA or ECC keys can have an "x"
- // here
- return null;
- } else {
- throw new PGPException("protected block not found");
- }
-
- return new byte[][] { data, protectedAt };
- }
-
- private BigInteger processDSASecretKey(InputStream inputStream,
- BigInteger p, BigInteger q, BigInteger g, BigInteger y,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory)
- throws IOException, PGPException {
- String type;
- byte[][] basicData = extractData(inputStream,
- keyProtectionRemoverFactory);
-
- // JGit modification: handle unencrypted DSA keys
- if (basicData == null) {
- byte[] nBytes = SXprUtils.readBytes(inputStream,
- inputStream.read());
- BigInteger x = new BigInteger(1, nBytes);
- SXprUtils.skipCloseParenthesis(inputStream);
- return x;
- }
-
- byte[] keyData = basicData[0];
- byte[] protectedAt = basicData[1];
-
- //
- // parse the secret key S-expr
- //
- InputStream keyIn = new ByteArrayInputStream(keyData);
-
- SXprUtils.skipOpenParenthesis(keyIn);
- SXprUtils.skipOpenParenthesis(keyIn);
-
- BigInteger x = readBigInteger("x", keyIn);
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- // JGit modification: OCB-encrypted keys don't have and don't need a
- // hash
- if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
- return x;
- }
-
- SXprUtils.skipOpenParenthesis(keyIn);
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("hash")) {
- throw new PGPException("hash keyword expected");
- }
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("sha1")) {
- throw new PGPException("hash keyword expected");
- }
-
- byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- if (digestProvider != null) {
- PGPDigestCalculator digestCalculator = digestProvider
- .get(HashAlgorithmTags.SHA1);
-
- OutputStream dOut = digestCalculator.getOutputStream();
-
- dOut.write(Strings.toByteArray("(3:dsa"));
- writeCanonical(dOut, "p", p);
- writeCanonical(dOut, "q", q);
- writeCanonical(dOut, "g", g);
- writeCanonical(dOut, "y", y);
- writeCanonical(dOut, "x", x);
-
- // check protected-at
- if (protectedAt != null) {
- dOut.write(protectedAt);
- }
-
- dOut.write(Strings.toByteArray(")"));
-
- byte[] check = digestCalculator.getDigest();
- if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
- throw new PGPException(
- "checksum on protected data failed in SExpr");
- }
- }
-
- return x;
- }
-
- private BigInteger processElGamalSecretKey(InputStream inputStream,
- BigInteger p, BigInteger g, BigInteger y,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory)
- throws IOException, PGPException {
- String type;
- byte[][] basicData = extractData(inputStream,
- keyProtectionRemoverFactory);
-
- // JGit modification: handle unencrypted EC keys
- if (basicData == null) {
- byte[] nBytes = SXprUtils.readBytes(inputStream,
- inputStream.read());
- BigInteger x = new BigInteger(1, nBytes);
- SXprUtils.skipCloseParenthesis(inputStream);
- return x;
- }
-
- byte[] keyData = basicData[0];
- byte[] protectedAt = basicData[1];
-
- //
- // parse the secret key S-expr
- //
- InputStream keyIn = new ByteArrayInputStream(keyData);
-
- SXprUtils.skipOpenParenthesis(keyIn);
- SXprUtils.skipOpenParenthesis(keyIn);
-
- BigInteger x = readBigInteger("x", keyIn);
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- // JGit modification: OCB-encrypted keys don't have and don't need a
- // hash
- if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
- return x;
- }
-
- SXprUtils.skipOpenParenthesis(keyIn);
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("hash")) {
- throw new PGPException("hash keyword expected");
- }
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("sha1")) {
- throw new PGPException("hash keyword expected");
- }
-
- byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- if (digestProvider != null) {
- PGPDigestCalculator digestCalculator = digestProvider
- .get(HashAlgorithmTags.SHA1);
-
- OutputStream dOut = digestCalculator.getOutputStream();
-
- dOut.write(Strings.toByteArray("(3:elg"));
- writeCanonical(dOut, "p", p);
- writeCanonical(dOut, "g", g);
- writeCanonical(dOut, "y", y);
- writeCanonical(dOut, "x", x);
-
- // check protected-at
- if (protectedAt != null) {
- dOut.write(protectedAt);
- }
-
- dOut.write(Strings.toByteArray(")"));
-
- byte[] check = digestCalculator.getDigest();
- if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
- throw new PGPException(
- "checksum on protected data failed in SExpr");
- }
- }
-
- return x;
- }
-
- private BigInteger processECSecretKey(InputStream inputStream,
- String curveID, String curveName, byte[] qVal,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory)
- throws IOException, PGPException {
- String type;
-
- byte[][] basicData = extractData(inputStream,
- keyProtectionRemoverFactory);
-
- // JGit modification: handle unencrypted EC keys
- if (basicData == null) {
- byte[] nBytes = SXprUtils.readBytes(inputStream,
- inputStream.read());
- BigInteger d = new BigInteger(1, nBytes);
- SXprUtils.skipCloseParenthesis(inputStream);
- return d;
- }
-
- byte[] keyData = basicData[0];
- byte[] protectedAt = basicData[1];
-
- //
- // parse the secret key S-expr
- //
- InputStream keyIn = new ByteArrayInputStream(keyData);
-
- SXprUtils.skipOpenParenthesis(keyIn);
- SXprUtils.skipOpenParenthesis(keyIn);
- BigInteger d = readBigInteger("d", keyIn);
- SXprUtils.skipCloseParenthesis(keyIn);
-
- // JGit modification: OCB-encrypted keys don't have and don't need a
- // hash
- if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
- return d;
- }
-
- SXprUtils.skipOpenParenthesis(keyIn);
-
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("hash")) {
- throw new PGPException("hash keyword expected");
- }
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("sha1")) {
- throw new PGPException("hash keyword expected");
- }
-
- byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- if (digestProvider != null) {
- PGPDigestCalculator digestCalculator = digestProvider
- .get(HashAlgorithmTags.SHA1);
-
- OutputStream dOut = digestCalculator.getOutputStream();
-
- dOut.write(Strings.toByteArray("(3:ecc"));
-
- dOut.write(Strings.toByteArray("(" + curveID.length() + ":"
- + curveID + curveName.length() + ":" + curveName + ")"));
-
- writeCanonical(dOut, "q", qVal);
- writeCanonical(dOut, "d", d);
-
- // check protected-at
- if (protectedAt != null) {
- dOut.write(protectedAt);
- }
-
- dOut.write(Strings.toByteArray(")"));
-
- byte[] check = digestCalculator.getDigest();
-
- if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
- throw new PGPException(
- "checksum on protected data failed in SExpr");
- }
- }
-
- return d;
- }
-
- private BigInteger[] processRSASecretKey(InputStream inputStream,
- BigInteger n, BigInteger e,
- PBEProtectionRemoverFactory keyProtectionRemoverFactory)
- throws IOException, PGPException {
- String type;
- byte[][] basicData = extractData(inputStream,
- keyProtectionRemoverFactory);
-
- byte[] keyData;
- byte[] protectedAt = null;
-
- InputStream keyIn;
- BigInteger d;
-
- if (basicData == null) {
- keyIn = inputStream;
- byte[] nBytes = SXprUtils.readBytes(inputStream,
- inputStream.read());
- d = new BigInteger(1, nBytes);
-
- SXprUtils.skipCloseParenthesis(inputStream);
-
- } else {
- keyData = basicData[0];
- protectedAt = basicData[1];
-
- keyIn = new ByteArrayInputStream(keyData);
-
- SXprUtils.skipOpenParenthesis(keyIn);
- SXprUtils.skipOpenParenthesis(keyIn);
- d = readBigInteger("d", keyIn);
- }
-
- //
- // parse the secret key S-expr
- //
-
- BigInteger p = readBigInteger("p", keyIn);
- BigInteger q = readBigInteger("q", keyIn);
- BigInteger u = readBigInteger("u", keyIn);
-
- // JGit modification: OCB-encrypted keys don't have and don't need a
- // hash
- if (basicData == null
- || keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
- return new BigInteger[] { d, p, q, u };
- }
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- SXprUtils.skipOpenParenthesis(keyIn);
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("hash")) {
- throw new PGPException("hash keyword expected");
- }
- type = SXprUtils.readString(keyIn, keyIn.read());
-
- if (!type.equals("sha1")) {
- throw new PGPException("hash keyword expected");
- }
-
- byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());
-
- SXprUtils.skipCloseParenthesis(keyIn);
-
- if (digestProvider != null) {
- PGPDigestCalculator digestCalculator = digestProvider
- .get(HashAlgorithmTags.SHA1);
-
- OutputStream dOut = digestCalculator.getOutputStream();
-
- dOut.write(Strings.toByteArray("(3:rsa"));
-
- writeCanonical(dOut, "n", n);
- writeCanonical(dOut, "e", e);
- writeCanonical(dOut, "d", d);
- writeCanonical(dOut, "p", p);
- writeCanonical(dOut, "q", q);
- writeCanonical(dOut, "u", u);
-
- // check protected-at
- if (protectedAt != null) {
- dOut.write(protectedAt);
- }
-
- dOut.write(Strings.toByteArray(")"));
-
- byte[] check = digestCalculator.getDigest();
-
- if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
- throw new PGPException(
- "checksum on protected data failed in SExpr");
- }
- }
-
- return new BigInteger[] { d, p, q, u };
- }
-
- private void writeCanonical(OutputStream dOut, String label, BigInteger i)
- throws IOException {
- writeCanonical(dOut, label, i.toByteArray());
- }
-
- private void writeCanonical(OutputStream dOut, String label, byte[] data)
- throws IOException {
- dOut.write(Strings.toByteArray(
- "(" + label.length() + ":" + label + data.length + ":"));
- dOut.write(data);
- dOut.write(Strings.toByteArray(")"));
- }
-}
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java
deleted file mode 100644
index 220aa28..0000000
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
- * <p>
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in the Software without restriction,
- *including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- * </p>
- * <p>
- * The above copyright notice and this permission notice shall be included in all copies or substantial
- * portions of the Software.
- * </p>
- * <p>
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
- * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * </p>
- */
-package org.eclipse.jgit.gpg.bc.internal.keys;
-
-// This class is an unmodified copy from Bouncy Castle; needed because it's package-visible only and used by SExprParser.
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.util.io.Streams;
-
-/**
- * Utility functions for looking a S-expression keys. This class will move when
- * it finds a better home!
- * <p>
- * Format documented here:
- * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
- * </p>
- */
-class SXprUtils {
- private static int readLength(InputStream in, int ch) throws IOException {
- int len = ch - '0';
-
- while ((ch = in.read()) >= 0 && ch != ':') {
- len = len * 10 + ch - '0';
- }
-
- return len;
- }
-
- static String readString(InputStream in, int ch) throws IOException {
- int len = readLength(in, ch);
-
- char[] chars = new char[len];
-
- for (int i = 0; i != chars.length; i++) {
- chars[i] = (char) in.read();
- }
-
- return new String(chars);
- }
-
- static byte[] readBytes(InputStream in, int ch) throws IOException {
- int len = readLength(in, ch);
-
- byte[] data = new byte[len];
-
- Streams.readFully(in, data);
-
- return data;
- }
-
- static S2K parseS2K(InputStream in) throws IOException {
- skipOpenParenthesis(in);
-
- // Algorithm is hard-coded to SHA1 below anyway.
- readString(in, in.read());
- byte[] iv = readBytes(in, in.read());
- final long iterationCount = Long.parseLong(readString(in, in.read()));
-
- skipCloseParenthesis(in);
-
- // we have to return the actual iteration count provided.
- S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, (int) iterationCount) {
- @Override
- public long getIterationCount() {
- return iterationCount;
- }
- };
-
- return s2k;
- }
-
- static void skipOpenParenthesis(InputStream in) throws IOException {
- int ch = in.read();
- if (ch != '(') {
- throw new IOException(
- "unknown character encountered: " + (char) ch); //$NON-NLS-1$
- }
- }
-
- static void skipCloseParenthesis(InputStream in) throws IOException {
- int ch = in.read();
- if (ch != ')') {
- throw new IOException("unknown character encountered"); //$NON-NLS-1$
- }
- }
-}
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java
index a659d38..a56e418 100644
--- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java
+++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 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
@@ -9,34 +9,36 @@
*/
package org.eclipse.jgit.gpg.bc.internal.keys;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.StreamCorruptedException;
import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
-import java.util.Arrays;
+import org.bouncycastle.bcpg.ECPublicBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.gpg.PGPSecretKeyParser;
+import org.bouncycastle.gpg.SExprParser;
+import org.bouncycastle.openpgp.OpenedPGPKeyData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory;
-import org.bouncycastle.util.io.Streams;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.gpg.bc.internal.BCText;
-import org.eclipse.jgit.util.RawParseUtils;
/**
* Utilities for reading GPG secret keys from a gpg-agent key file.
*/
public final class SecretKeys {
+ // Maximum nesting depth of sub-lists in an S-Expression for a secret key.
+ private static final int MAX_SEXPR_NESTING = 20;
+
private SecretKeys() {
// No instantiation.
}
@@ -64,12 +66,6 @@ public interface PassphraseSupplier {
UnsupportedCredentialItem, URISyntaxException;
}
- private static final byte[] PROTECTED_KEY = "protected-private-key" //$NON-NLS-1$
- .getBytes(StandardCharsets.US_ASCII);
-
- private static final byte[] OCB_PROTECTED = "openpgp-s2k3-ocb-aes" //$NON-NLS-1$
- .getBytes(StandardCharsets.US_ASCII);
-
/**
* Reads a GPG secret key from the given stream.
*
@@ -99,500 +95,59 @@ public static PGPSecretKey readSecretKey(InputStream in,
PassphraseSupplier passphraseSupplier, PGPPublicKey publicKey)
throws IOException, PGPException, CanceledException,
UnsupportedCredentialItem, URISyntaxException {
- byte[] data = Streams.readAll(in);
- if (data.length == 0) {
- throw new EOFException();
- } else if (data.length < 4 + PROTECTED_KEY.length) {
- // +4 for "(21:" for a binary protected key
- throw new IOException(
- MessageFormat.format(BCText.get().secretKeyTooShort,
- Integer.toUnsignedString(data.length)));
+ OpenedPGPKeyData data;
+ try (InputStream keyIn = new BufferedInputStream(in)) {
+ data = PGPSecretKeyParser.parse(keyIn, MAX_SEXPR_NESTING);
}
- SExprParser parser = new SExprParser(calculatorProvider);
- byte firstChar = data[0];
- try {
- if (firstChar == '(') {
- // Binary format.
- PBEProtectionRemoverFactory decryptor = null;
- if (matches(data, 4, PROTECTED_KEY)) {
- // AES/CBC encrypted.
- decryptor = new JcePBEProtectionRemoverFactory(
- passphraseSupplier.getPassphrase(),
- calculatorProvider);
- }
- try (InputStream sIn = new ByteArrayInputStream(data)) {
- return parser.parseSecretKey(sIn, decryptor, publicKey);
- }
- }
- // Assume it's the new key-value format.
- try (ByteArrayInputStream keyIn = new ByteArrayInputStream(data)) {
- byte[] rawData = keyFromNameValueFormat(keyIn);
- if (!matches(rawData, 1, PROTECTED_KEY)) {
- // Not encrypted human-readable format.
- try (InputStream sIn = new ByteArrayInputStream(
- convertSexpression(rawData))) {
- return parser.parseSecretKey(sIn, null, publicKey);
- }
- }
- // An encrypted key from a key-value file. Most likely AES/OCB
- // encrypted.
- boolean isOCB[] = { false };
- byte[] sExp = convertSexpression(rawData, isOCB);
- PBEProtectionRemoverFactory decryptor;
- if (isOCB[0]) {
- decryptor = new OCBPBEProtectionRemoverFactory(
- passphraseSupplier.getPassphrase(),
- calculatorProvider, getAad(sExp));
- } else {
- decryptor = new JcePBEProtectionRemoverFactory(
- passphraseSupplier.getPassphrase(),
- calculatorProvider);
- }
- try (InputStream sIn = new ByteArrayInputStream(sExp)) {
- return parser.parseSecretKey(sIn, decryptor, publicKey);
- }
- }
- } catch (IOException e) {
- throw new PGPException(e.getLocalizedMessage(), e);
+ PBEProtectionRemoverFactory decryptor = null;
+ if (isProtected(data)) {
+ decryptor = new JcePBEProtectionRemoverFactory(
+ passphraseSupplier.getPassphrase(), calculatorProvider);
}
- }
-
- /**
- * Extract the AAD for the OCB decryption from an s-expression.
- *
- * @param sExp
- * buffer containing a valid binary s-expression
- * @return the AAD
- */
- private static byte[] getAad(byte[] sExp) {
- // Given a key
- // @formatter:off
- // (protected-private-key (rsa ... (protected openpgp-s2k3-ocb-aes ... )(protected-at ...)))
- // A B C D
- // The AAD is [A..B)[C..D). (From the binary serialized form.)
- // @formatter:on
- int i = 1; // Skip initial '('
- while (sExp[i] != '(') {
- i++;
- }
- int aadStart = i++;
- int aadEnd = skip(sExp, aadStart);
- byte[] protectedPrefix = "(9:protected" //$NON-NLS-1$
- .getBytes(StandardCharsets.US_ASCII);
- while (!matches(sExp, i, protectedPrefix)) {
- i++;
- }
- int protectedStart = i;
- int protectedEnd = skip(sExp, protectedStart);
- byte[] aadData = new byte[aadEnd - aadStart
- - (protectedEnd - protectedStart)];
- System.arraycopy(sExp, aadStart, aadData, 0, protectedStart - aadStart);
- System.arraycopy(sExp, protectedEnd, aadData, protectedStart - aadStart,
- aadEnd - protectedEnd);
- return aadData;
- }
-
- /**
- * Skips a list including nested lists.
- *
- * @param sExp
- * buffer containing valid binary s-expression data
- * @param start
- * index of the opening '(' of the list to skip
- * @return the index after the closing ')' of the skipped list
- */
- private static int skip(byte[] sExp, int start) {
- int i = start + 1;
- int depth = 1;
- while (depth > 0) {
- switch (sExp[i]) {
- case '(':
- depth++;
- break;
- case ')':
- depth--;
- break;
- default:
- // We must be on a length
- int j = i;
- while (sExp[j] >= '0' && sExp[j] <= '9') {
- j++;
- }
- // j is on the colon
- int length = Integer.parseInt(
- new String(sExp, i, j - i, StandardCharsets.US_ASCII));
- i = j + length;
+ switch (publicKey.getAlgorithm()) {
+ case PublicKeyAlgorithmTags.EDDSA_LEGACY:
+ case PublicKeyAlgorithmTags.Ed25519:
+ // If we let Bouncy Castle check whether the secret key matches the
+ // given public key it may get into trouble in some cases with
+ // ed25519 keys. It appears that we may end up with secret keys
+ // using the official RFC 8410 OID for ed25519, "1.3.101.112", while
+ // the public key passed in may have a non-standard OpenPGP-specific
+ // OID "1.3.6.1.4.1.11591.15.1", or vice versa. Bouncy Castle then
+ // throws an exception because of the different OIDs.
+ //
+ // The work-around is to just read the secret key, and double-check
+ // later that the OIDs are compatible and the curve points match.
+ PGPSecretKey secret = data.getKeyData(null, calculatorProvider,
+ decryptor, new JcaKeyFingerprintCalculator(),
+ MAX_SEXPR_NESTING);
+ PGPPublicKey pubKeyRead = secret.getPublicKey();
+ int algoRead = pubKeyRead.getAlgorithm();
+ if (algoRead != PublicKeyAlgorithmTags.EDDSA_LEGACY
+ && algoRead != PublicKeyAlgorithmTags.Ed25519) {
+ throw new PGPException(BCText.get().keyAlgorithmMismatch);
}
- i++;
- }
- return i;
- }
-
- /**
- * Checks whether the {@code needle} matches {@code src} at offset
- * {@code from}.
- *
- * @param src
- * to match against {@code needle}
- * @param from
- * position in {@code src} to start matching
- * @param needle
- * to match against
- * @return {@code true} if {@code src} contains {@code needle} at position
- * {@code from}, {@code false} otherwise
- */
- private static boolean matches(byte[] src, int from, byte[] needle) {
- if (from < 0 || from + needle.length > src.length) {
- return false;
- }
- return org.bouncycastle.util.Arrays.constantTimeAreEqual(needle.length,
- src, from, needle, 0);
- }
-
- /**
- * Converts a human-readable serialized s-expression into a binary
- * serialized s-expression.
- *
- * @param humanForm
- * to convert
- * @return the converted s-expression
- * @throws IOException
- * if the conversion fails
- */
- private static byte[] convertSexpression(byte[] humanForm)
- throws IOException {
- boolean[] isOCB = { false };
- return convertSexpression(humanForm, isOCB);
- }
-
- /**
- * Converts a human-readable serialized s-expression into a binary
- * serialized s-expression.
- *
- * @param humanForm
- * to convert
- * @param isOCB
- * returns whether the s-expression specified AES/OCB encryption
- * @return the converted s-expression
- * @throws IOException
- * if the conversion fails
- */
- private static byte[] convertSexpression(byte[] humanForm, boolean[] isOCB)
- throws IOException {
- int pos = 0;
- try (ByteArrayOutputStream out = new ByteArrayOutputStream(
- humanForm.length)) {
- while (pos < humanForm.length) {
- byte b = humanForm[pos];
- if (b == '(' || b == ')') {
- out.write(b);
- pos++;
- } else if (isGpgSpace(b)) {
- pos++;
- } else if (b == '#') {
- // Hex value follows up to the next #
- int i = ++pos;
- while (i < humanForm.length && isHex(humanForm[i])) {
- i++;
- }
- if (i == pos || humanForm[i] != '#') {
- throw new StreamCorruptedException(
- BCText.get().sexprHexNotClosed);
- }
- if ((i - pos) % 2 != 0) {
- throw new StreamCorruptedException(
- BCText.get().sexprHexOdd);
- }
- int l = (i - pos) / 2;
- out.write(Integer.toString(l)
- .getBytes(StandardCharsets.US_ASCII));
- out.write(':');
- while (pos < i) {
- int x = (nibble(humanForm[pos]) << 4)
- | nibble(humanForm[pos + 1]);
- pos += 2;
- out.write(x);
- }
- pos = i + 1;
- } else if (isTokenChar(b)) {
- // Scan the token
- int start = pos++;
- while (pos < humanForm.length
- && isTokenChar(humanForm[pos])) {
- pos++;
- }
- int l = pos - start;
- if (pos - start == OCB_PROTECTED.length
- && matches(humanForm, start, OCB_PROTECTED)) {
- isOCB[0] = true;
- }
- out.write(Integer.toString(l)
- .getBytes(StandardCharsets.US_ASCII));
- out.write(':');
- out.write(humanForm, start, pos - start);
- } else if (b == '"') {
- // Potentially quoted string.
- int start = ++pos;
- boolean escaped = false;
- while (pos < humanForm.length
- && (escaped || humanForm[pos] != '"')) {
- int ch = humanForm[pos++];
- escaped = !escaped && ch == '\\';
- }
- if (pos >= humanForm.length) {
- throw new StreamCorruptedException(
- BCText.get().sexprStringNotClosed);
- }
- // start is on the first character of the string, pos on the
- // closing quote.
- byte[] dq = dequote(humanForm, start, pos);
- out.write(Integer.toString(dq.length)
- .getBytes(StandardCharsets.US_ASCII));
- out.write(':');
- out.write(dq);
- pos++;
- } else {
- throw new StreamCorruptedException(
- MessageFormat.format(BCText.get().sexprUnhandled,
- Integer.toHexString(b & 0xFF)));
- }
+ ECPublicBCPGKey ec1 = (ECPublicBCPGKey) publicKey
+ .getPublicKeyPacket().getKey();
+ ECPublicBCPGKey ec2 = (ECPublicBCPGKey) pubKeyRead
+ .getPublicKeyPacket().getKey();
+ if (!ObjectIds.match(ec1.getCurveOID(), ec2.getCurveOID())
+ || !ec1.getEncodedPoint().equals(ec2.getEncodedPoint())) {
+ throw new PGPException(
+ MessageFormat.format(BCText.get().keyMismatch,
+ ec1.getCurveOID(), ec1.getEncodedPoint(),
+ ec2.getCurveOID(), ec2.getEncodedPoint()));
}
- return out.toByteArray();
- }
- }
-
- /**
- * GPG-style string de-quoting, which is basically C-style, with some
- * literal CR/LF escaping.
- *
- * @param in
- * buffer containing the quoted string
- * @param from
- * index after the opening quote in {@code in}
- * @param to
- * index of the closing quote in {@code in}
- * @return the dequoted raw string value
- * @throws StreamCorruptedException
- * if object stream is corrupt
- */
- private static byte[] dequote(byte[] in, int from, int to)
- throws StreamCorruptedException {
- // Result must be shorter or have the same length
- byte[] out = new byte[to - from];
- int j = 0;
- int i = from;
- while (i < to) {
- byte b = in[i++];
- if (b != '\\') {
- out[j++] = b;
- continue;
- }
- if (i == to) {
- throw new StreamCorruptedException(
- BCText.get().sexprStringInvalidEscapeAtEnd);
- }
- b = in[i++];
- switch (b) {
- case 'b':
- out[j++] = '\b';
- break;
- case 'f':
- out[j++] = '\f';
- break;
- case 'n':
- out[j++] = '\n';
- break;
- case 'r':
- out[j++] = '\r';
- break;
- case 't':
- out[j++] = '\t';
- break;
- case 'v':
- out[j++] = 0x0B;
- break;
- case '"':
- case '\'':
- case '\\':
- out[j++] = b;
- break;
- case '\r':
- // Escaped literal line end. If an LF is following, skip that,
- // too.
- if (i < to && in[i] == '\n') {
- i++;
- }
- break;
- case '\n':
- // Same for LF possibly followed by CR.
- if (i < to && in[i] == '\r') {
- i++;
- }
- break;
- case 'x':
- if (i + 1 >= to || !isHex(in[i]) || !isHex(in[i + 1])) {
- throw new StreamCorruptedException(
- BCText.get().sexprStringInvalidHexEscape);
- }
- out[j++] = (byte) ((nibble(in[i]) << 4) | nibble(in[i + 1]));
- i += 2;
- break;
- case '0':
- case '1':
- case '2':
- case '3':
- if (i + 2 >= to || !isOctal(in[i]) || !isOctal(in[i + 1])
- || !isOctal(in[i + 2])) {
- throw new StreamCorruptedException(
- BCText.get().sexprStringInvalidOctalEscape);
- }
- out[j++] = (byte) (((((in[i] - '0') << 3)
- | (in[i + 1] - '0')) << 3) | (in[i + 2] - '0'));
- i += 3;
- break;
- default:
- throw new StreamCorruptedException(MessageFormat.format(
- BCText.get().sexprStringInvalidEscape,
- Integer.toHexString(b & 0xFF)));
- }
- }
- return Arrays.copyOf(out, j);
- }
-
- /**
- * Extracts the key from a GPG name-value-pair key file.
- * <p>
- * Package-visible for tests only.
- * </p>
- *
- * @param in
- * {@link InputStream} to read from; should be buffered
- * @return the raw key data as extracted from the file
- * @throws IOException
- * if the {@code in} stream cannot be read or does not contain a
- * key
- */
- static byte[] keyFromNameValueFormat(InputStream in) throws IOException {
- // It would be nice if we could use RawParseUtils here, but GPG compares
- // names case-insensitively. We're only interested in the "Key:"
- // name-value pair.
- int[] nameLow = { 'k', 'e', 'y', ':' };
- int[] nameCap = { 'K', 'E', 'Y', ':' };
- int nameIdx = 0;
- for (;;) {
- int next = in.read();
- if (next < 0) {
- throw new EOFException();
- }
- if (next == '\n') {
- nameIdx = 0;
- } else if (nameIdx >= 0) {
- if (nameLow[nameIdx] == next || nameCap[nameIdx] == next) {
- nameIdx++;
- if (nameIdx == nameLow.length) {
- break;
- }
- } else {
- nameIdx = -1;
- }
- }
- }
- // We're after "Key:". Read the value as continuation lines.
- int last = ':';
- byte[] rawData;
- try (ByteArrayOutputStream out = new ByteArrayOutputStream(8192)) {
- for (;;) {
- int next = in.read();
- if (next < 0) {
- break;
- }
- if (last == '\n') {
- if (next == ' ' || next == '\t') {
- // Continuation line; skip this whitespace
- last = next;
- continue;
- }
- break; // Not a continuation line
- }
- out.write(next);
- last = next;
- }
- rawData = out.toByteArray();
- }
- // GPG trims off trailing whitespace, and a line having only whitespace
- // is a single LF.
- try (ByteArrayOutputStream out = new ByteArrayOutputStream(
- rawData.length)) {
- int lineStart = 0;
- boolean trimLeading = true;
- while (lineStart < rawData.length) {
- int nextLineStart = RawParseUtils.nextLF(rawData, lineStart);
- if (trimLeading) {
- while (lineStart < nextLineStart
- && isGpgSpace(rawData[lineStart])) {
- lineStart++;
- }
- }
- // Trim trailing
- int i = nextLineStart - 1;
- while (lineStart < i && isGpgSpace(rawData[i])) {
- i--;
- }
- if (i <= lineStart) {
- // Empty line signifies LF
- out.write('\n');
- trimLeading = true;
- } else {
- out.write(rawData, lineStart, i - lineStart + 1);
- trimLeading = false;
- }
- lineStart = nextLineStart;
- }
- return out.toByteArray();
- }
- }
-
- private static boolean isGpgSpace(int ch) {
- return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
- }
-
- private static boolean isTokenChar(int ch) {
- switch (ch) {
- case '-':
- case '.':
- case '/':
- case '_':
- case ':':
- case '*':
- case '+':
- case '=':
- return true;
+ return secret;
default:
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
- || (ch >= '0' && ch <= '9')) {
- return true;
- }
- return false;
+ // For other key types let Bouncy Castle do the check.
+ return data.getKeyData(publicKey, calculatorProvider, decryptor,
+ null, MAX_SEXPR_NESTING);
}
}
- private static boolean isHex(int ch) {
- return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
- || (ch >= 'a' && ch <= 'f');
+ private static boolean isProtected(OpenedPGPKeyData data) {
+ return SExprParser.ProtectionFormatTypeTags.PROTECTED_PRIVATE_KEY == SExprParser
+ .getProtectionType(data.getKeyExpression().getString(0));
}
- private static boolean isOctal(int ch) {
- return (ch >= '0' && ch <= '7');
- }
-
- private static int nibble(int ch) {
- if (ch >= '0' && ch <= '9') {
- return ch - '0';
- } else if (ch >= 'A' && ch <= 'F') {
- return ch - 'A' + 10;
- } else if (ch >= 'a' && ch <= 'f') {
- return ch - 'a' + 10;
- }
- return -1;
- }
}
diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF
index 18238b5..20d0e4d 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)"
-Export-Package: org.eclipse.jgit.transport.http.apache;version="7.0.2";
+ 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";
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 89b7db1..04bbe37 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.apache;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml
index ec29cbd..11d92fc 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 b0b1ae1..2a262d1 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.http.server;version="7.0.2",
- org.eclipse.jgit.http.server.glue;version="7.0.2";
+Export-Package: org.eclipse.jgit.http.server;version="7.1.2",
+ org.eclipse.jgit.http.server.glue;version="7.1.2";
uses:="jakarta.servlet,
jakarta.servlet.http",
- org.eclipse.jgit.http.server.resolver;version="7.0.2";
+ org.eclipse.jgit.http.server.resolver;version="7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.parser;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.resolver;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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)"
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 4239578..0d9322c 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.http.server;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml
index e13c39e..ecf80a3 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 b338ec4..ec2d986 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.http.server;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.http.server.glue;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.http.server.resolver;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.resolver;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.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 c69fe90..13c9acb 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.http.test</artifactId>
diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF
index e4c2174..d60dbed 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.http.server;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.resolver;version="[7.0.2,7.1.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.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.0.2";
+Export-Package: org.eclipse.jgit.junit.http;version="7.1.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 b290a02..32c5433 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.http;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml
index f853464..0ff0cd9 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 dfa1b7a..1f9dcf4 100644
--- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit.junit.ssh
Bundle-SymbolicName: org.eclipse.jgit.junit.ssh
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
@@ -33,16 +33,16 @@
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.0.2,7.1.0)",
- org.eclipse.jgit.api;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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)",
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.0.2"
+Export-Package: org.eclipse.jgit.junit.ssh;version="7.1.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 5f840d1..2fb060d 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit.ssh;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit.ssh/pom.xml b/org.eclipse.jgit.junit.ssh/pom.xml
index 067374b..6595b5b 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.junit.ssh</artifactId>
diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF
index c7ae7ce..396df4c 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.api;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.dircache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.merge;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="7.0.2",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.time;version="[7.0.2,7.1.0)",
+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)",
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.0.2";
+Export-Package: org.eclipse.jgit.junit;version="7.1.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.0.2";uses:="org.eclipse.jgit.util.time"
+ org.eclipse.jgit.junit.time;version="7.1.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 ef76a33..8724466 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.junit;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.junit;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml
index b63973f..aab23c8 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.junit</artifactId>
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
index 419fdb1..38f0d0b 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java
@@ -18,6 +18,8 @@
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -242,6 +244,11 @@ public TimeZone getTimeZone() {
}
@Override
+ public ZoneId getTimeZoneId() {
+ return ZoneOffset.ofHoursMinutes(-3, -30);
+ }
+
+ @Override
public Locale getLocale() {
return Locale.US;
}
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
index c8c56b2..2a482df 100644
--- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
+++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java
@@ -44,7 +44,7 @@ private static Class<?> loadNewClass(Class<?> klass)
try {
String pathSeparator = System.getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path")
- .split(pathSeparator);
+ .split(pathSeparator, -1);
URL[] urls = new URL[classPathEntries.length];
for (int i = 0; i < classPathEntries.length; i++) {
urls[i] = Paths.get(classPathEntries[i]).toUri().toURL();
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 3ce8c3e..53f87cb 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.server;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.test;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.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 a3a99b5..1568471 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 70d6029..925ada7 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs.server;version="7.0.2";
+Export-Package: org.eclipse.jgit.lfs.server;version="7.1.2";
uses:="jakarta.servlet.http,
org.eclipse.jgit.lfs.lib",
- org.eclipse.jgit.lfs.server.fs;version="7.0.2";
+ org.eclipse.jgit.lfs.server.fs;version="7.1.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.0.2";x-internal:=true,
- org.eclipse.jgit.lfs.server.s3;version="7.0.2";
+ org.eclipse.jgit.lfs.server.internal;version="7.1.2";x-internal:=true,
+ org.eclipse.jgit.lfs.server.s3;version="7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.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 2c07938..7f52aa9 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs.server;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs.server/pom.xml b/org.eclipse.jgit.lfs.server/pom.xml
index 679480b..819450b 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 2d1acdb..38bbaa2 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.api;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.attributes;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
+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)",
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.0.2";x-friends:="org.eclipse.jgit.lfs.server.test"
+Export-Package: org.eclipse.jgit.lfs.test;version="7.1.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 79601ff..813823e 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 bb87bab..ec18f81 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
-Export-Package: org.eclipse.jgit.lfs;version="7.0.2",
- org.eclipse.jgit.lfs.errors;version="7.0.2",
- org.eclipse.jgit.lfs.internal;version="7.0.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.0.2"
+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"
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.0.2,7.1.0)";resolution:=optional,
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.attributes;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.diff;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.dircache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.hooks;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.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)"
diff --git a/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/SOURCE-MANIFEST.MF
index a1dd5b8..fa88770 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.lfs;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.lfs/pom.xml b/org.eclipse.jgit.lfs/pom.xml
index 80f047a..c435419 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 98a3615..26a207f 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.0.2.qualifier"
+ version="7.1.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 bf22c74..6ea0915 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 987a925..47cc345 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import plugin="org.eclipse.jgit" version="7.1.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 7d2f4d9..87ec0ed 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 459e77c..3246780 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import plugin="org.eclipse.jgit" version="7.1.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 7c5b580..1fc7cbf 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 fe4d8d7..8a83e3c 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import plugin="org.eclipse.jgit" version="7.1.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 03c53de..3eeb973 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 e991f99..5eba1c5 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import feature="org.eclipse.jgit" version="7.1.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 57c62b2..3e90454 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 aa6ad24..5f0691e 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
- <import feature="org.eclipse.jgit.lfs" version="7.0.2" match="equivalent"/>
- <import feature="org.eclipse.jgit.ssh.apache" version="7.0.2" match="equivalent"/>
+ <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"/>
</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 bb595d6..949a9c8 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<groupId>org.eclipse.jgit.feature</groupId>
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 2c890e2..adf4e15 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 79d09f7..b6b5339 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import feature="org.eclipse.jgit" version="7.1.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 51bab0c..d67b629 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.0.2-SNAPSHOT</version>
+ <version>7.1.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.0.2-SNAPSHOT</version>
+ <version>7.1.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 c84de5d..6546e46 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import feature="org.eclipse.jgit" version="7.1.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 b135ca0..aa071c9 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 52d0f3d..f795e44 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.0.2.qualifier"
+ version="7.1.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.0.2" match="equivalent"/>
+ <import plugin="org.eclipse.jgit" version="7.1.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 fb42902..b93d527 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 c1964c9..efae5a1 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,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde?>
<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.32" sequenceNumber="1729714537">
+<target name="jgit-4.32" sequenceNumber="1731963692">
<locations>
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
@@ -79,7 +79,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>5.12.0</version>
+ <version>5.14.2</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -89,13 +89,13 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
- <version>5.14.0</version>
+ <version>5.15.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
- <version>5.14.0</version>
+ <version>5.15.0</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -105,49 +105,49 @@
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
- <version>12.0.10</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-session</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util-ajax</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
@@ -193,13 +193,13 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
- <version>1.15.0</version>
+ <version>1.15.10</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
- <version>1.15.0</version>
+ <version>1.15.10</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -209,25 +209,25 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -269,13 +269,13 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.16.0</version>
+ <version>3.17.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.16.1</version>
+ <version>2.17.0</version>
<type>jar</type>
</dependency>
<dependency>
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 4d9f630..1578e4c 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,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?pde?>
<!-- generated with https://github.com/eclipse-cbi/targetplatform-dsl -->
-<target name="jgit-4.33" sequenceNumber="1729714538">
+<target name="jgit-4.33" sequenceNumber="1731963694">
<locations>
<location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
<unit id="com.jcraft.jsch" version="0.1.55.v20230916-1400"/>
@@ -30,7 +30,7 @@
</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/2024-09/"/>
+ <repository location="https://download.eclipse.org/releases/2024-09/"/>
</location>
<location includeDependencyDepth="none" includeDependencyScopes="compile" includeSource="true" missingManifest="error" type="Maven" label="xz">
<dependencies>
@@ -79,7 +79,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>5.12.0</version>
+ <version>5.14.2</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -89,13 +89,13 @@
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
- <version>5.14.0</version>
+ <version>5.15.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
- <version>5.14.0</version>
+ <version>5.15.0</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -105,49 +105,49 @@
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
- <version>12.0.10</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-session</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util-ajax</artifactId>
- <version>12.0.12</version>
+ <version>12.0.15</version>
<type>jar</type>
</dependency>
<dependency>
@@ -193,13 +193,13 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
- <version>1.15.0</version>
+ <version>1.15.10</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
- <version>1.15.0</version>
+ <version>1.15.10</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -209,25 +209,25 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
- <version>1.78.1</version>
+ <version>1.79</version>
<type>jar</type>
</dependency>
</dependencies>
@@ -269,13 +269,13 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.16.0</version>
+ <version>3.17.0</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.16.1</version>
+ <version>2.17.0</version>
<type>jar</type>
</dependency>
<dependency>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd
index d01a8a9..74c6878 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.33.tpd
@@ -3,6 +3,6 @@
include "orbit/orbit-4.33.tpd"
include "maven/dependencies.tpd"
-location "https://download.eclipse.org/staging/2024-09/" {
+location "https://download.eclipse.org/releases/2024-09/" {
org.eclipse.osgi lazy
}
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
new file mode 100644
index 0000000..43678e7
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.target
@@ -0,0 +1,290 @@
+<?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">
+ <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"/>
+ <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/2024-12"/>
+ </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/2024-12/"/>
+ </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.14.0</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sshd</groupId>
+ <artifactId>sshd-sftp</artifactId>
+ <version>2.14.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.14.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.15.0</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.jna</groupId>
+ <artifactId>jna-platform</artifactId>
+ <version>5.15.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.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-io</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-session</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>12.0.15</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util-ajax</artifactId>
+ <version>12.0.15</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.11.0</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.15.10</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy-agent</artifactId>
+ <version>1.15.10</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.79</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk18on</artifactId>
+ <version>1.79</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk18on</artifactId>
+ <version>1.79</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcutil-jdk18on</artifactId>
+ <version>1.79</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.26.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.17.1</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.17.0</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.3.4</version>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+ </location>
+ </locations>
+</target>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.tpd
new file mode 100644
index 0000000..4c38371
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.34.tpd
@@ -0,0 +1,8 @@
+target "jgit-4.34" with source configurePhase
+
+include "orbit/orbit-4.34.tpd"
+include "maven/dependencies.tpd"
+
+location "https://download.eclipse.org/staging/2024-12/" {
+ 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 42712c0..7b9a397 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
@@ -20,12 +20,12 @@
dependency {
groupId = "org.apache.commons"
artifactId = "commons-lang3"
- version = "3.16.0"
+ version = "3.17.0"
}
dependency {
groupId = "commons-io"
artifactId = "commons-io"
- version = "2.16.1"
+ version = "2.17.0"
}
dependency {
groupId = "commons-logging"
@@ -69,22 +69,22 @@
dependency {
groupId = "org.bouncycastle"
artifactId = "bcpg-jdk18on"
- version = "1.78.1"
+ version = "1.79"
}
dependency {
groupId = "org.bouncycastle"
artifactId = "bcprov-jdk18on"
- version = "1.78.1"
+ version = "1.79"
}
dependency {
groupId = "org.bouncycastle"
artifactId = "bcpkix-jdk18on"
- version = "1.78.1"
+ version = "1.79"
}
dependency {
groupId = "org.bouncycastle"
artifactId = "bcutil-jdk18on"
- version = "1.78.1"
+ version = "1.79"
}
}
@@ -97,12 +97,12 @@
dependency {
groupId = "net.bytebuddy"
artifactId = "byte-buddy"
- version = "1.15.0"
+ version = "1.15.10"
}
dependency {
groupId = "net.bytebuddy"
artifactId = "byte-buddy-agent"
- version = "1.15.0"
+ version = "1.15.10"
}
}
@@ -154,42 +154,42 @@
dependency {
groupId = "org.eclipse.jetty.ee10"
artifactId = "jetty-ee10-servlet"
- version = "12.0.10"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-http"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-io"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-security"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-server"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-session"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-util"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "org.eclipse.jetty"
artifactId = "jetty-util-ajax"
- version = "12.0.12"
+ version = "12.0.15"
}
dependency {
groupId = "jakarta.servlet"
@@ -207,12 +207,12 @@
dependency {
groupId = "net.java.dev.jna"
artifactId = "jna"
- version = "5.14.0"
+ version = "5.15.0"
}
dependency {
groupId = "net.java.dev.jna"
artifactId = "jna-platform"
- version = "5.14.0"
+ version = "5.15.0"
}
}
@@ -225,7 +225,7 @@
dependency {
groupId = "org.mockito"
artifactId = "mockito-core"
- version = "5.12.0"
+ version = "5.14.2"
}
}
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
new file mode 100644
index 0000000..15931db
--- /dev/null
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/orbit-4.34.tpd
@@ -0,0 +1,27 @@
+target "orbit-4.34" with source configurePhase
+// see https://download.eclipse.org/tools/orbit/downloads/
+
+location "https://download.eclipse.org/tools/orbit/simrel/orbit-aggregation/2024-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.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]
+ 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.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 5d750fb..37fe6e9 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>JGit Tycho Parent</name>
diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF
index 339b969..7d3bc52 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.eclipse.jgit.api;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.diff;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.dircache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.merge;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.pgm;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.pgm.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.pgm.opt;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.0)",
+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)",
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 8009940..cca0d81 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.pgm.test</artifactId>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java
new file mode 100644
index 0000000..b4d4ea9
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/PackRefsTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PackRefsTest extends CLIRepositoryTestCase {
+ private Git git;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ git = new Git(db);
+ git.commit().setMessage("initial commit").call();
+ }
+
+ @Test
+ public void tagPacked() throws Exception {
+ git.tag().setName("test").call();
+ git.packRefs().call();
+ assertEquals(Ref.Storage.PACKED,
+ git.getRepository().exactRef("refs/tags/test").getStorage());
+ }
+
+ @Test
+ public void nonTagRefNotPackedWithoutAll() throws Exception {
+ git.branchCreate().setName("test").call();
+ git.packRefs().call();
+ assertEquals(Ref.Storage.LOOSE,
+ git.getRepository().exactRef("refs/heads/test").getStorage());
+ }
+
+ @Test
+ public void nonTagRefPackedWithAll() throws Exception {
+ git.branchCreate().setName("test").call();
+ git.packRefs().setAll(true).call();
+ assertEquals(Ref.Storage.PACKED,
+ git.getRepository().exactRef("refs/heads/test").getStorage());
+ }
+
+ @Test
+ public void refTableCompacted() throws Exception {
+ ((FileRepository) git.getRepository()).convertRefStorage(
+ ConfigConstants.CONFIG_REF_STORAGE_REFTABLE, false, false);
+
+ git.commit().setMessage("test commit").call();
+ File tableDir = new File(db.getDirectory(), Constants.REFTABLE);
+ File[] reftables = tableDir.listFiles();
+ assertNotNull(reftables);
+ assertTrue(reftables.length > 2);
+
+ git.packRefs().call();
+
+ reftables = tableDir.listFiles();
+ assertNotNull(reftables);
+ assertEquals(2, reftables.length);
+ }
+}
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 0d41cb8..b889686 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -14,49 +14,49 @@
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.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.archive;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.awtui;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.blame;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.diff;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.dircache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.gitrepo;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.server;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.server.fs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs.server.s3;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.merge;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.notes;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revplot;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http.apache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.resolver;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.sshd;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.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.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.0.2";
+Export-Package: org.eclipse.jgit.console;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util",
- org.eclipse.jgit.pgm;version="7.0.2";
+ org.eclipse.jgit.pgm;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util.io,
org.eclipse.jgit.awtui,
@@ -68,14 +68,14 @@
org.eclipse.jgit.treewalk,
org.eclipse.jgit.api,
javax.swing",
- org.eclipse.jgit.pgm.debug;version="7.0.2";
+ org.eclipse.jgit.pgm.debug;version="7.1.2";
uses:="org.eclipse.jgit.util.io,
org.eclipse.jgit.pgm,
org.eclipse.jetty.servlet",
- org.eclipse.jgit.pgm.internal;version="7.0.2";
+ org.eclipse.jgit.pgm.internal;version="7.1.2";
x-friends:="org.eclipse.jgit.pgm.test,
org.eclipse.jgit.test",
- org.eclipse.jgit.pgm.opt;version="7.0.2";
+ org.eclipse.jgit.pgm.opt;version="7.1.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 7b5009f..9acb609 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="7.1.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 08d3727..41b0091 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.PackRefs
org.eclipse.jgit.pgm.Push
org.eclipse.jgit.pgm.ReceivePack
org.eclipse.jgit.pgm.Reflog
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index a44e19c..b1baa16 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 50ee809..d24b639 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
@@ -257,6 +257,7 @@
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_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
usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR.
usage_extraArgument=Pass an extra argument to a merge driver. Currently supported are "-X ours" and "-X theirs".
@@ -300,6 +301,7 @@
usage_MergeBase=Find as good common ancestors as possible for a merge
usage_MergesTwoDevelopmentHistories=Merges two development histories
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
usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files
usage_ReadDirCache= Read the DirCache 100 times
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
index aacde2f..a29c4d9 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java
@@ -26,11 +26,6 @@ class MergeBase extends TextBuiltin {
private boolean all;
@Argument(index = 0, metaVar = "metaVar_commitish", required = true)
- void commit_0(final RevCommit c) {
- commits.add(c);
- }
-
- @Argument(index = 1, metaVar = "metaVar_commitish", required = true)
private List<RevCommit> commits = new ArrayList<>();
@Override
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java
new file mode 100644
index 0000000..ee05f5c
--- /dev/null
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/PackRefs.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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 org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.kohsuke.args4j.Option;
+
+@Command(common = true, usage = "usage_PackRefs")
+class PackRefs extends TextBuiltin {
+ @Option(name = "--all", usage = "usage_All")
+ private boolean all;
+
+ @Override
+ protected void run() {
+ Git git = Git.wrap(db);
+ try {
+ git.packRefs().setProgressMonitor(new TextProgressMonitor(errw))
+ .setAll(all).call();
+ } catch (GitAPIException e) {
+ throw die(e.getMessage(), e);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
index 190cbd7..80d3503 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowPackDelta.java
@@ -31,6 +31,7 @@
import org.eclipse.jgit.pgm.TextBuiltin;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.kohsuke.args4j.Argument;
@@ -68,7 +69,7 @@ protected void run() throws Exception {
ObjectReuseAsIs asis = (ObjectReuseAsIs) reader;
ObjectToPack target = asis.newObjectToPack(obj, obj.getType());
- PackWriter pw = new PackWriter(reader) {
+ PackWriter pw = new PackWriter(new PackConfig(), reader) {
@Override
public boolean select(ObjectToPack otp, StoredObjectRepresentation next) {
otp.select(next);
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 0cdec4d..46319c9 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: OSGI-INF/l10n/agent
Bundle-Vendor: %Bundle-Vendor
-Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[7.0.2,7.1.0)"
+Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[7.1.2,7.2.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.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)"
+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)"
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.0.2";x-internal:=true
+Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="7.1.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 d4fea05..773bd02 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache.agent/pom.xml b/org.eclipse.jgit.ssh.apache.agent/pom.xml
index 9a44834..85ac2a9 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ssh.apache.agent</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache.test/.classpath b/org.eclipse.jgit.ssh.apache.test/.classpath
index 6fdb99a..5be47af 100644
--- a/org.eclipse.jgit.ssh.apache.test/.classpath
+++ b/org.eclipse.jgit.ssh.apache.test/.classpath
@@ -11,5 +11,10 @@
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
+ <classpathentry kind="src" path="tst-rsrc">
+ <attributes>
+ <attribute name="test" value="true"/>
+ </attributes>
+ </classpathentry>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/org.eclipse.jgit.ssh.apache.test/.gitattributes b/org.eclipse.jgit.ssh.apache.test/.gitattributes
new file mode 100644
index 0000000..b5b9375
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/.gitattributes
@@ -0,0 +1,2 @@
+/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle binary
+/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl* binary
diff --git a/org.eclipse.jgit.ssh.apache.test/BUILD b/org.eclipse.jgit.ssh.apache.test/BUILD
index b384464..dfc059f 100644
--- a/org.eclipse.jgit.ssh.apache.test/BUILD
+++ b/org.eclipse.jgit.ssh.apache.test/BUILD
@@ -1,19 +1,49 @@
load(
+ "@com_googlesource_gerrit_bazlets//tools:genrule2.bzl",
+ "genrule2",
+)
+load(
"@com_googlesource_gerrit_bazlets//tools:junit.bzl",
"junit_tests",
)
+DEPS = [
+ "//lib:eddsa",
+ "//lib:junit",
+ "//lib:slf4j-api",
+ "//lib:sshd-osgi",
+ "//lib:sshd-sftp",
+ "//org.eclipse.jgit:jgit",
+ "//org.eclipse.jgit.junit:junit",
+ "//org.eclipse.jgit.junit.ssh:junit-ssh",
+ "//org.eclipse.jgit.ssh.apache:ssh-apache",
+]
+
+HELPERS = ["tst/org/eclipse/jgit/internal/signing/ssh/AbstractSshSignatureTest.java"]
+
junit_tests(
name = "sshd_apache",
- srcs = glob(["tst/**/*.java"]),
+ srcs = glob(
+ ["tst/**/*.java"],
+ exclude = HELPERS,
+ ),
tags = ["sshd"],
- deps = [
- "//lib:eddsa",
- "//lib:junit",
- "//lib:sshd-osgi",
- "//lib:sshd-sftp",
- "//org.eclipse.jgit:jgit",
- "//org.eclipse.jgit.junit.ssh:junit-ssh",
- "//org.eclipse.jgit.ssh.apache:ssh-apache",
+ runtime_deps = [":tst_rsrc"],
+ deps = DEPS + [
+ ":helpers",
],
)
+
+java_library(
+ name = "helpers",
+ testonly = 1,
+ srcs = HELPERS,
+ deps = DEPS,
+)
+
+genrule2(
+ name = "tst_rsrc",
+ srcs = glob(["tst-rsrc/**"]),
+ outs = ["tst_rsrc.jar"],
+ cmd = "tar cf - $(SRCS) | tar -C $$TMP --strip-components=2 -xf - && cd $$TMP && zip -qr $$ROOT/$@ .",
+)
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 cc382fb..21619de 100644
--- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit.ssh.apache.test
Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.test
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -17,22 +17,28 @@
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.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.sshd.proxy;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit.ssh;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.sshd;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.sshd.agent;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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)",
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)"
+ 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)"
diff --git a/org.eclipse.jgit.ssh.apache.test/pom.xml b/org.eclipse.jgit.ssh.apache.test/pom.xml
index 624104d..c53adb7 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ssh.apache.test</artifactId>
@@ -91,6 +91,12 @@
<sourceDirectory>src/</sourceDirectory>
<testSourceDirectory>tst/</testSourceDirectory>
+ <testResources>
+ <testResource>
+ <directory>tst-rsrc/</directory>
+ </testResource>
+ </testResources>
+
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers
new file mode 100644
index 0000000..ec74409
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/allowed_signers
@@ -0,0 +1,2 @@
+tester@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO
+*@example.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBV
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key
new file mode 100644
index 0000000..b8de8c3
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDHRJfojkxG39UQt7onBArYnfkmiDLAMO8K5VoTkE8gVQAAAJAhCMgzIQjI
+MwAAAAtzc2gtZWQyNTUxOQAAACDHRJfojkxG39UQt7onBArYnfkmiDLAMO8K5VoTkE8gVQ
+AAAEBmcXpast20+B4IzA0Xex2CKYiiWJj3NFJ5F0kil113vcdEl+iOTEbf1RC3uicECtid
++SaIMsAw7wrlWhOQTyBVAAAADVRIV09AU0VBR044MDA=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key.pub
new file mode 100644
index 0000000..842415b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBV
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2
new file mode 100644
index 0000000..a4af047
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA7S4ycIB6oTx4UN8l9N+u016UgMzkrbT7E+2XbG35jgwAAAJAa2jfBGto3
+wQAAAAtzc2gtZWQyNTUxOQAAACA7S4ycIB6oTx4UN8l9N+u016UgMzkrbT7E+2XbG35jgw
+AAAEBothGMqFaA5aTO8MLx9wm1oDRfzQCSsu7uJwrOiUFTTTtLjJwgHqhPHhQ3yX0367TX
+pSAzOSttPsT7ZdsbfmODAAAADVRIV09AU0VBR044MDA=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2.pub
new file mode 100644
index 0000000..e46c87e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/ca_key2.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDtLjJwgHqhPHhQ3yX0367TXpSAzOSttPsT7ZdsbfmOD
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/expired.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/expired.cert
new file mode 100644
index 0000000..9da63ec
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/expired.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAINFZ5NKywAWh1G1P6BiBKArmYKs1BDhJBOawJKlS29VXAAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAEAAAABAAAADGV4cGlyZWRfY2VydAAAAAAAAAAAZtOugAAAAABm1QAAAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgx0SX6I5MRt/VELe6JwQK2J35JogywDDvCuVaE5BPIFUAAABTAAAAC3NzaC1lZDI1NTE5AAAAQNf8i5dhRqWRe06epIRrZ5V+QZHq3ZrlJtlx98UJya9GAeCrJ5oHwBjr5O5TL5wNJS5Hz+T1qsJNFU9d1wdcuwI=
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/no_principals.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/no_principals.cert
new file mode 100644
index 0000000..101e374
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/no_principals.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILzuED1RSloB/enTghTEKSACVOuEARP0f8UVXSRwEXN6AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAIAAAABAAAADW5vX3ByaW5jaXBhbHMAAAAAAAAAAGbTroAAAAAAZyLIgAAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBVAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBwEQ2D0OHn4QDHnsINlgWUWpmhukseQCJu3Adulz28fFtewp1LLqkBy50wR6vJe1ifYbY4hzReXOSyoTmHSXEN
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other-ca.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other-ca.cert
new file mode 100644
index 0000000..752fee1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other-ca.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIHNW2bzSS61lvgHippv3Ymx4cVEAXBVCb8lFXHnVpsSyAAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAYAAAABAAAAB3Rlc3RlcjIAAAAWAAAAEnRlc3RlckBleGFtcGxlLmNvbQAAAABm066AAAAAAGciyIAAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACA7S4ycIB6oTx4UN8l9N+u016UgMzkrbT7E+2XbG35jgwAAAFMAAAALc3NoLWVkMjU1MTkAAABAuJ8zBazcaYTbUEr9QtoYox0MkVBg+8LANxJxc885M2vmg9yPHpTfV/emupqhBwuYcPJSskTxl7WX4TUNvhMsAA==
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other.cert
new file mode 100644
index 0000000..15825f6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/other.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGKXzyrvDzj9ObQ4SuzqytK6nomOV8DhgdzODfWuup1sAAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAQAAAABAAAABW90aGVyAAAAFQAAABFvdGhlckBleGFtcGxlLmNvbQAAAABm066AAAAAAGciyIAAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDHRJfojkxG39UQt7onBArYnfkmiDLAMO8K5VoTkE8gVQAAAFMAAAALc3NoLWVkMjU1MTkAAABA1ycFqWehyC6pIISEkXSTtHbatLWl9HHAoUFouQiDdubAnMDRSkyHipXR62rq+8yEAvtqm1mXBzO8nLalkF9xAA==
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/tester.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/tester.cert
new file mode 100644
index 0000000..a2b241c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/tester.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAICSl1xsyTWb23YlKo21musxOzj4L4eD2coTkHbBw2uOyAAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAMAAAABAAAABnRlc3RlcgAAABYAAAASdGVzdGVyQGV4YW1wbGUuY29tAAAAAGbTroAAAAAAZyLIgAAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBVAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDyjzq/0Egm1OxwrvqPZKUihE3w357Ji9Nd3j7VnUuvSYTXAdB9P0E+a2hyCcemmsil1MsvWTiCSSOsrHVB6FEO
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/two_principals.cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/two_principals.cert
new file mode 100644
index 0000000..5f7164a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/certs/two_principals.cert
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFmWKr9gNSQT0vna7k3uOyUF9CTcMGw2zxTFBf2Ev8TzAAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzOAAAAAAAAAAgAAAABAAAABnRlc3RlcgAAACkAAAAPZm9vQGV4YW1wbGUuY29tAAAAEnRlc3RlckBleGFtcGxlLmNvbQAAAABm066AAAAAAGciyIAAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDHRJfojkxG39UQt7onBArYnfkmiDLAMO8K5VoTkE8gVQAAAFMAAAALc3NoLWVkMjU1MTkAAABAqlSX2GzLz5U+hN/gF9UUyAkE6h5BgVFYhsyf1MR/B7Hoxa29wGLbJpUplrqEHMxoud2zfH2Nhj00unc3lr5bBA== ./signing_key.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl
new file mode 100644
index 0000000..9469340
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all
new file mode 100644
index 0000000..6f744c3
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-all
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca
new file mode 100644
index 0000000..84a8bc6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-ca
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert
new file mode 100644
index 0000000..26f29b2
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-cert
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty
new file mode 100644
index 0000000..78e5187
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-empty
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash
new file mode 100644
index 0000000..cdd1351
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-hash
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid
new file mode 100644
index 0000000..1a65243
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild
new file mode 100644
index 0000000..9ba549f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keyid-wild
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys
new file mode 100644
index 0000000..8dd496d
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-keys
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial
new file mode 100644
index 0000000..9965e2e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild
new file mode 100644
index 0000000..aefd2b1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-serial-wild
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1
new file mode 100644
index 0000000..3928543
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha1
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256
new file mode 100644
index 0000000..cdd1351
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-sha256
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text
new file mode 100644
index 0000000..77ddd5e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/krl-text
@@ -0,0 +1,11 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001
new file mode 100644
index 0000000..893fd5e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAtvUGe9h7cgnWjaOqdhh93yaVMp8JL7PAHMJ+uhu2E4QAAAIhUa4KiVGuC
+ogAAAAtzc2gtZWQyNTUxOQAAACAtvUGe9h7cgnWjaOqdhh93yaVMp8JL7PAHMJ+uhu2E4Q
+AAAECKgy+3FBgpdfxjOtNy9TamhadMWSyPlPiwu06mYVReyS29QZ72HtyCdaNo6p2GH3fJ
+pUynwkvs8Acwn66G7YThAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub
new file mode 100644
index 0000000..e2bcd25
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE1UUsQ+sncsuST6eGe3B5Se7purqhGcWrkyIwUnQM/jAAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YThAAAAAAAAAAEAAAABAAAACXJldm9rZWQgMQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQCjDATJVQs3odl9fsqaxyx/18qrodZEDyYZAsdqg0GMx8CvLYt4xHENyVm7kyBRxOeh3EKfII0WFoYCV4mGZ/wU= ./tst-keys/revoked-0001.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub
new file mode 100644
index 0000000..f561982
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0001.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004
new file mode 100644
index 0000000..e50a4fe
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACC9GQuH6oGwYETnl9bDhD6+LjFTXz3Setm1aP870jEUMQAAAIiQzZzikM2c
+4gAAAAtzc2gtZWQyNTUxOQAAACC9GQuH6oGwYETnl9bDhD6+LjFTXz3Setm1aP870jEUMQ
+AAAEBpn5dxbvHhqAsSVN3IqRwzbFFgOhdmpkOP+nvoKq+rSr0ZC4fqgbBgROeX1sOEPr4u
+MVNfPdJ62bVo/zvSMRQxAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub
new file mode 100644
index 0000000..8e92fa7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIC5jMLPDlEVbyPU/Icb04BF5jxN+OT8kpuO5c0CV6/AYAAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQxAAAAAAAAAAQAAAABAAAACXJldm9rZWQgNAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQOH4yNn7+zyvsCV8BCoop5xYv4uFk27VZRjmscuy3J66KNwLay9XkvkRNArDaWBwH47dmkcU7F6fLLpY4vN2jgM= ./tst-keys/revoked-0004.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub
new file mode 100644
index 0000000..1d7fe7f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0004.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010
new file mode 100644
index 0000000..fb457df
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBuSB/U+xWOO1SQ1KMjpQf4qgjeKTvHYDq8XJZijeUecAAAAIgvtSiML7Uo
+jAAAAAtzc2gtZWQyNTUxOQAAACBuSB/U+xWOO1SQ1KMjpQf4qgjeKTvHYDq8XJZijeUecA
+AAAECI2si7/SGjMM1UyhrFPXx4laQIfFUsb1+yfXKwQyeOXW5IH9T7FY47VJDUoyOlB/iq
+CN4pO8dgOrxclmKN5R5wAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub
new file mode 100644
index 0000000..9492f88
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIN2arXaBzVIdxAFfU+XU1Uc788HKlDH3tOLdDtcoORLmAAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5wAAAAAAAAAAoAAAABAAAACnJldm9rZWQgMTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDwhgQsYOG/eKf8EfH+fAmEW+88/ZJCmxAExEFPxkGL59waZcGiOJqquTKiqN5Kod8hpUrvZywrA0tjrRkYw8wH ./tst-keys/revoked-0010.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub
new file mode 100644
index 0000000..37a0d84
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0010.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050
new file mode 100644
index 0000000..b02e9df
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDY4RuPhJ0MvGBy7HxPPiEMDVmVZ9RgWF++acMQQmRpsgAAAIgCZLe5AmS3
+uQAAAAtzc2gtZWQyNTUxOQAAACDY4RuPhJ0MvGBy7HxPPiEMDVmVZ9RgWF++acMQQmRpsg
+AAAEB9Q6rpWK04mQDoeKSB2I7p/rb8pu00ClhR+vRATl4TYdjhG4+EnQy8YHLsfE8+IQwN
+WZVn1GBYX75pwxBCZGmyAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub
new file mode 100644
index 0000000..90bb86f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIecNj2Es6VfyCrhol4swP9lutvphd3seh+/b2LpD0EsAAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmyAAAAAAAAADIAAAABAAAACnJldm9rZWQgNTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA2q8tCXV8FXkB0QWnFNWfCL7zz5jCXL9ZQADM1DaGi8oUU/dxmlQtWgMxuu5vNuvOYQGPDcBLj+by8VqAdvZMP ./tst-keys/revoked-0050.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub
new file mode 100644
index 0000000..f3ad249
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0050.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090
new file mode 100644
index 0000000..efa3d5e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCV5vqoE7PrDvvc76GQcZEA9/Udq0KkAlXs8UKBoyrouQAAAIg3mgznN5oM
+5wAAAAtzc2gtZWQyNTUxOQAAACCV5vqoE7PrDvvc76GQcZEA9/Udq0KkAlXs8UKBoyrouQ
+AAAEAkRynGUH9n5hcp/S1WALvuIEDtbkMi2A7yNWze0o4gWpXm+qgTs+sO+9zvoZBxkQD3
+9R2rQqQCVezxQoGjKui5AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub
new file mode 100644
index 0000000..26e61e0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIOjIztpPiaKY0hztHWtWpX+4LEoyy8qYPPT277K3bykSAAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5AAAAAAAAAFoAAAABAAAACnJldm9rZWQgOTAAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBUaAWyv/jZrbrCO5zw2HuZcWYBig8R2jdvkKr5yzWMWEVRtn97gnAUsIGxkgUnUAs3B2En2FH2NaicC1F1n3sF ./tst-keys/revoked-0090.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub
new file mode 100644
index 0000000..e51b88c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0090.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500
new file mode 100644
index 0000000..900d444
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACC/PmkCo2vkm6sX1ketRQnLGwcNo2hfyh73KnT9hW6ekAAAAIhDam0PQ2pt
+DwAAAAtzc2gtZWQyNTUxOQAAACC/PmkCo2vkm6sX1ketRQnLGwcNo2hfyh73KnT9hW6ekA
+AAAED606GrYWlY7TOXcr8vAr3fjMtCtetdpwFHi2pzgf2Bbb8+aQKja+SbqxfWR61FCcsb
+Bw2jaF/KHvcqdP2Fbp6QAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub
new file mode 100644
index 0000000..0709618
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIIEblAg4b1eJ5KnT7KvYoOfe24La+nAKKLIYdsR6CdreAAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6QAAAAAAAAAfQAAAABAAAAC3Jldm9rZWQgNTAwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAc0WEuRfi9LG9uTfKY4Dh5MJCHUG7Dqp1J4S4Gs1iOzFX2YKgYXc0O+9j3jJ5/fB4z960Y1AxYR4TWEo1pNjzBQ== ./tst-keys/revoked-0500.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub
new file mode 100644
index 0000000..13d1aa4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0500.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510
new file mode 100644
index 0000000..a58675e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACADi+yFC2pU9iM6PU4EX9bdhzaWeVgmSR+aEQnfSytwTQAAAIgigF2AIoBd
+gAAAAAtzc2gtZWQyNTUxOQAAACADi+yFC2pU9iM6PU4EX9bdhzaWeVgmSR+aEQnfSytwTQ
+AAAEBWpyFpK0a+cdNPFMsvHTHtjBJpX4aMHxBAcEPN8hnpWAOL7IULalT2Izo9TgRf1t2H
+NpZ5WCZJH5oRCd9LK3BNAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub
new file mode 100644
index 0000000..1431af3
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAII8u8ho0YtDyXWYKv4WeOXSaRUxU8sUV0dQujB2J9VLaAAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BNAAAAAAAAAf4AAAABAAAAC3Jldm9rZWQgNTEwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABA3aijJnt8mJ8vLtr7H2PBVJHtNJpL6MQZNXHC6svzygIqZwEq3tDHGR00TPHaCYAqDEXQZysONciOQtQHzKXuBw== ./tst-keys/revoked-0510.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub
new file mode 100644
index 0000000..33ad644
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0510.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520
new file mode 100644
index 0000000..630316c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBlWknnYT5Jdh68dZzjik9hOdgA7AXm2sMLPOzTdfj+ngAAAIghEm1OIRJt
+TgAAAAtzc2gtZWQyNTUxOQAAACBlWknnYT5Jdh68dZzjik9hOdgA7AXm2sMLPOzTdfj+ng
+AAAEDfVYURudvfzK3ZFx6T2O1CWi0emOZ0MYPcDzUVlu1WmGVaSedhPkl2Hrx1nOOKT2E5
+2ADsBebawws87NN1+P6eAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub
new file mode 100644
index 0000000..b290943
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAID/r9T2Sv0NGmlcHl6Fw8rVPIupmsqwq3WAG1NvW7WRcAAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6eAAAAAAAAAggAAAABAAAAC3Jldm9rZWQgNTIwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAF8zkeAqwtlxF4iy4mDEHkzVaRqcS0sZ57gcZBWGn/peGFy3MpSxlFQM/IC2pNZ7GuCVSIPV6rRLJC65YMMOEDQ== ./tst-keys/revoked-0520.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub
new file mode 100644
index 0000000..fc13d37
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0520.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550
new file mode 100644
index 0000000..5e671b4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAscmsY+6UVQ3of4MSXpvQS4aFEChpylx3w6wfxrQtSgQAAAIj/9GKZ//Ri
+mQAAAAtzc2gtZWQyNTUxOQAAACAscmsY+6UVQ3of4MSXpvQS4aFEChpylx3w6wfxrQtSgQ
+AAAEDKC3eEgvCMy86rktq7VU1YQjjKY1iDFPVxWgKKcGJKkyxyaxj7pRVDeh/gxJem9BLh
+oUQKGnKXHfDrB/GtC1KBAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub
new file mode 100644
index 0000000..f529a91
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIF9q+Cg+9DSKt09eW1NXqVC4dZ3v80sZIYtc0/yqHRb+AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KBAAAAAAAAAiYAAAABAAAAC3Jldm9rZWQgNTUwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAovTuFOXLNCc4hQcI2hatXe2hbBQYbcnUo2BNdJ9EvIOsH/T0DzzEfRQajMQ+QD6oujIx7fb1Z2sRVPOAb3AcBg== ./tst-keys/revoked-0550.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub
new file mode 100644
index 0000000..e09316a
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0550.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799
new file mode 100644
index 0000000..8edd736
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA/ehirY+MDfjsL60LID9+30FVF04QjC+/eqqhTS1QwgAAAAIjdntzR3Z7c
+0QAAAAtzc2gtZWQyNTUxOQAAACA/ehirY+MDfjsL60LID9+30FVF04QjC+/eqqhTS1QwgA
+AAAEDQEb+IFCIz+yvkhmrOQ85GafOm9ra0oNRontpox62UTj96GKtj4wN+OwvrQsgP37fQ
+VUXThCML796qqFNLVDCAAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub
new file mode 100644
index 0000000..80312fb
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIC1z1LkrZhMz1mBWPU8sJIuH59v+ig4OK/B4/x8jLAtUAAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCAAAAAAAAAAx8AAAABAAAAC3Jldm9rZWQgNzk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABASNkJSbdRDARfgbqPOnuES0o6m6VZ7RC2XLPm3uwTqCvMqtHbFvq9etMddSUIR4XXah6ef+O7CJDk/Yjpkn+2CA== ./tst-keys/revoked-0799.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub
new file mode 100644
index 0000000..1f0556c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0799.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999
new file mode 100644
index 0000000..f05a1e4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBIdwRXE815oNRYuS71olA1jkbB81YVApvHpKxCRuvugAAAAIgzBpObMwaT
+mwAAAAtzc2gtZWQyNTUxOQAAACBIdwRXE815oNRYuS71olA1jkbB81YVApvHpKxCRuvugA
+AAAECxY5wx3XKIhMT+ajMZXPl51x8rkCPBq6gUgZV3Uqpu7Eh3BFcTzXmg1Fi5LvWiUDWO
+RsHzVhUCm8ekrEJG6+6AAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub
new file mode 100644
index 0000000..4aedb77
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGt3nV/XJmtz9sQGP2fiZiKOH7mkPhezN3S+8TnsVcQjAAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6AAAAAAAAAA+cAAAABAAAAC3Jldm9rZWQgOTk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAvLVCRCs7CV0JSXYL8ge4iRxL4y48bYuvu3YimKZDg7NdCXqw/jkaCsxJykRzb/xVnQDoNVCQQuzydt/I13FdBA== ./tst-keys/revoked-0999.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub
new file mode 100644
index 0000000..c837fe0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-0999.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca
new file mode 100644
index 0000000..47e01fb
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAIgok4I2KJOC
+NgAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzg
+AAAEAEN+knz2qOyj+jbY+SJSHYQhlJoB1u9jLqoQoiAerI3hbReZevLKczhayKUADRdAvZ
+5DXVzAJpQkcB4MPdQu/OAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub
new file mode 100644
index 0000000..2b92f89
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/O
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2
new file mode 100644
index 0000000..770ceee
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDsEBCX5jBwggkpt4XZXct1fOhDBuvgLL0KMpGoHRtj9wAAAIjMEwOtzBMD
+rQAAAAtzc2gtZWQyNTUxOQAAACDsEBCX5jBwggkpt4XZXct1fOhDBuvgLL0KMpGoHRtj9w
+AAAEAurE2/d7VhoEJeNFdDnVS7lpBRoMe/zAjA8dJRP1Z/I+wQEJfmMHCCCSm3hdldy3V8
+6EMG6+AsvQoykagdG2P3AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub
new file mode 100644
index 0000000..a177fd0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-ca2.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOwQEJfmMHCCCSm3hdldy3V86EMG6+AsvQoykagdG2P3
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash
new file mode 100644
index 0000000..c6f2361
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-hash
@@ -0,0 +1,11 @@
+hash: SHA256:RvNFBEc/N9jsm3toDkitgr/wnWu/6qWBHo4Xmh5ZUpM
+hash: SHA256:qu2IwCnItWWX+orXv0rjCeT4i++2O6ViTzLye6kyWzU
+hash: SHA256:qQTACAkAJxYk1zvSQ+Rx9wa2IuOFJKtaEy/XwxM89J0
+hash: SHA256:Fe4GdmipzulS9oMB/h3U69tSm5wil6bTUKSJCT+Jf3E
+hash: SHA256:esUK/whZ5oJeRFNeOrHK1bbx9dKC+nRITZ7up7HJaGA
+hash: SHA256:xkii+r6t9rEBFYkx1b3dGNXzEs69M5NUMfHP05ypSdI
+hash: SHA256:lZrSycKcBNvUafU9y4R0EEbDaQWqMFvIGM9M+VKt2zk
+hash: SHA256:/2bgZOiYEH2UVahUllNaQ5P0advEB7liCPkp+aNVKDk
+hash: SHA256:He3c0W5o/P1I0pK5/VusqD5V6duAMeZl6f+6Yy5P1z0
+hash: SHA256:5V5Xw2lgcAGR8dO9cbgRmCNlhcCsBBv/hmEstKsqKr4
+hash: SHA256:T7s26JPzzRP2WHOcw3OjLwWo8ZZTkfo2jBCrRfJ6BR4
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid
new file mode 100644
index 0000000..592ddb4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-keyid
@@ -0,0 +1,512 @@
+id: revoked 1
+id: revoked 2
+id: revoked 3
+id: revoked 4
+id: revoked 10
+id: revoked 15
+id: revoked 30
+id: revoked 50
+id: revoked 90
+id: revoked 300
+id: revoked 301
+id: revoked 302
+id: revoked 303
+id: revoked 304
+id: revoked 305
+id: revoked 306
+id: revoked 307
+id: revoked 308
+id: revoked 309
+id: revoked 310
+id: revoked 311
+id: revoked 312
+id: revoked 313
+id: revoked 314
+id: revoked 315
+id: revoked 316
+id: revoked 317
+id: revoked 318
+id: revoked 319
+id: revoked 320
+id: revoked 321
+id: revoked 322
+id: revoked 323
+id: revoked 324
+id: revoked 325
+id: revoked 326
+id: revoked 327
+id: revoked 328
+id: revoked 329
+id: revoked 330
+id: revoked 331
+id: revoked 332
+id: revoked 333
+id: revoked 334
+id: revoked 335
+id: revoked 336
+id: revoked 337
+id: revoked 338
+id: revoked 339
+id: revoked 340
+id: revoked 341
+id: revoked 342
+id: revoked 343
+id: revoked 344
+id: revoked 345
+id: revoked 346
+id: revoked 347
+id: revoked 348
+id: revoked 349
+id: revoked 350
+id: revoked 351
+id: revoked 352
+id: revoked 353
+id: revoked 354
+id: revoked 355
+id: revoked 356
+id: revoked 357
+id: revoked 358
+id: revoked 359
+id: revoked 360
+id: revoked 361
+id: revoked 362
+id: revoked 363
+id: revoked 364
+id: revoked 365
+id: revoked 366
+id: revoked 367
+id: revoked 368
+id: revoked 369
+id: revoked 370
+id: revoked 371
+id: revoked 372
+id: revoked 373
+id: revoked 374
+id: revoked 375
+id: revoked 376
+id: revoked 377
+id: revoked 378
+id: revoked 379
+id: revoked 380
+id: revoked 381
+id: revoked 382
+id: revoked 383
+id: revoked 384
+id: revoked 385
+id: revoked 386
+id: revoked 387
+id: revoked 388
+id: revoked 389
+id: revoked 390
+id: revoked 391
+id: revoked 392
+id: revoked 393
+id: revoked 394
+id: revoked 395
+id: revoked 396
+id: revoked 397
+id: revoked 398
+id: revoked 399
+id: revoked 400
+id: revoked 401
+id: revoked 402
+id: revoked 403
+id: revoked 404
+id: revoked 405
+id: revoked 406
+id: revoked 407
+id: revoked 408
+id: revoked 409
+id: revoked 410
+id: revoked 411
+id: revoked 412
+id: revoked 413
+id: revoked 414
+id: revoked 415
+id: revoked 416
+id: revoked 417
+id: revoked 418
+id: revoked 419
+id: revoked 420
+id: revoked 421
+id: revoked 422
+id: revoked 423
+id: revoked 424
+id: revoked 425
+id: revoked 426
+id: revoked 427
+id: revoked 428
+id: revoked 429
+id: revoked 430
+id: revoked 431
+id: revoked 432
+id: revoked 433
+id: revoked 434
+id: revoked 435
+id: revoked 436
+id: revoked 437
+id: revoked 438
+id: revoked 439
+id: revoked 440
+id: revoked 441
+id: revoked 442
+id: revoked 443
+id: revoked 444
+id: revoked 445
+id: revoked 446
+id: revoked 447
+id: revoked 448
+id: revoked 449
+id: revoked 450
+id: revoked 451
+id: revoked 452
+id: revoked 453
+id: revoked 454
+id: revoked 455
+id: revoked 456
+id: revoked 457
+id: revoked 458
+id: revoked 459
+id: revoked 460
+id: revoked 461
+id: revoked 462
+id: revoked 463
+id: revoked 464
+id: revoked 465
+id: revoked 466
+id: revoked 467
+id: revoked 468
+id: revoked 469
+id: revoked 470
+id: revoked 471
+id: revoked 472
+id: revoked 473
+id: revoked 474
+id: revoked 475
+id: revoked 476
+id: revoked 477
+id: revoked 478
+id: revoked 479
+id: revoked 480
+id: revoked 481
+id: revoked 482
+id: revoked 483
+id: revoked 484
+id: revoked 485
+id: revoked 486
+id: revoked 487
+id: revoked 488
+id: revoked 489
+id: revoked 490
+id: revoked 491
+id: revoked 492
+id: revoked 493
+id: revoked 494
+id: revoked 495
+id: revoked 496
+id: revoked 497
+id: revoked 498
+id: revoked 500
+id: revoked 501
+id: revoked 502
+id: revoked 503
+id: revoked 504
+id: revoked 505
+id: revoked 506
+id: revoked 507
+id: revoked 508
+id: revoked 509
+id: revoked 510
+id: revoked 511
+id: revoked 512
+id: revoked 513
+id: revoked 514
+id: revoked 515
+id: revoked 516
+id: revoked 517
+id: revoked 518
+id: revoked 519
+id: revoked 520
+id: revoked 521
+id: revoked 522
+id: revoked 523
+id: revoked 524
+id: revoked 525
+id: revoked 526
+id: revoked 527
+id: revoked 528
+id: revoked 529
+id: revoked 530
+id: revoked 531
+id: revoked 532
+id: revoked 533
+id: revoked 534
+id: revoked 535
+id: revoked 536
+id: revoked 537
+id: revoked 538
+id: revoked 539
+id: revoked 540
+id: revoked 541
+id: revoked 542
+id: revoked 543
+id: revoked 544
+id: revoked 545
+id: revoked 546
+id: revoked 547
+id: revoked 548
+id: revoked 549
+id: revoked 550
+id: revoked 551
+id: revoked 552
+id: revoked 553
+id: revoked 554
+id: revoked 555
+id: revoked 556
+id: revoked 557
+id: revoked 558
+id: revoked 559
+id: revoked 560
+id: revoked 561
+id: revoked 562
+id: revoked 563
+id: revoked 564
+id: revoked 565
+id: revoked 566
+id: revoked 567
+id: revoked 568
+id: revoked 569
+id: revoked 570
+id: revoked 571
+id: revoked 572
+id: revoked 573
+id: revoked 574
+id: revoked 575
+id: revoked 576
+id: revoked 577
+id: revoked 578
+id: revoked 579
+id: revoked 580
+id: revoked 581
+id: revoked 582
+id: revoked 583
+id: revoked 584
+id: revoked 585
+id: revoked 586
+id: revoked 587
+id: revoked 588
+id: revoked 589
+id: revoked 590
+id: revoked 591
+id: revoked 592
+id: revoked 593
+id: revoked 594
+id: revoked 595
+id: revoked 596
+id: revoked 597
+id: revoked 598
+id: revoked 599
+id: revoked 600
+id: revoked 601
+id: revoked 602
+id: revoked 603
+id: revoked 604
+id: revoked 605
+id: revoked 606
+id: revoked 607
+id: revoked 608
+id: revoked 609
+id: revoked 610
+id: revoked 611
+id: revoked 612
+id: revoked 613
+id: revoked 614
+id: revoked 615
+id: revoked 616
+id: revoked 617
+id: revoked 618
+id: revoked 619
+id: revoked 620
+id: revoked 621
+id: revoked 622
+id: revoked 623
+id: revoked 624
+id: revoked 625
+id: revoked 626
+id: revoked 627
+id: revoked 628
+id: revoked 629
+id: revoked 630
+id: revoked 631
+id: revoked 632
+id: revoked 633
+id: revoked 634
+id: revoked 635
+id: revoked 636
+id: revoked 637
+id: revoked 638
+id: revoked 639
+id: revoked 640
+id: revoked 641
+id: revoked 642
+id: revoked 643
+id: revoked 644
+id: revoked 645
+id: revoked 646
+id: revoked 647
+id: revoked 648
+id: revoked 649
+id: revoked 650
+id: revoked 651
+id: revoked 652
+id: revoked 653
+id: revoked 654
+id: revoked 655
+id: revoked 656
+id: revoked 657
+id: revoked 658
+id: revoked 659
+id: revoked 660
+id: revoked 661
+id: revoked 662
+id: revoked 663
+id: revoked 664
+id: revoked 665
+id: revoked 666
+id: revoked 667
+id: revoked 668
+id: revoked 669
+id: revoked 670
+id: revoked 671
+id: revoked 672
+id: revoked 673
+id: revoked 674
+id: revoked 675
+id: revoked 676
+id: revoked 677
+id: revoked 678
+id: revoked 679
+id: revoked 680
+id: revoked 681
+id: revoked 682
+id: revoked 683
+id: revoked 684
+id: revoked 685
+id: revoked 686
+id: revoked 687
+id: revoked 688
+id: revoked 689
+id: revoked 690
+id: revoked 691
+id: revoked 692
+id: revoked 693
+id: revoked 694
+id: revoked 695
+id: revoked 696
+id: revoked 697
+id: revoked 698
+id: revoked 699
+id: revoked 700
+id: revoked 701
+id: revoked 702
+id: revoked 703
+id: revoked 704
+id: revoked 705
+id: revoked 706
+id: revoked 707
+id: revoked 708
+id: revoked 709
+id: revoked 710
+id: revoked 711
+id: revoked 712
+id: revoked 713
+id: revoked 714
+id: revoked 715
+id: revoked 716
+id: revoked 717
+id: revoked 718
+id: revoked 719
+id: revoked 720
+id: revoked 721
+id: revoked 722
+id: revoked 723
+id: revoked 724
+id: revoked 725
+id: revoked 726
+id: revoked 727
+id: revoked 728
+id: revoked 729
+id: revoked 730
+id: revoked 731
+id: revoked 732
+id: revoked 733
+id: revoked 734
+id: revoked 735
+id: revoked 736
+id: revoked 737
+id: revoked 738
+id: revoked 739
+id: revoked 740
+id: revoked 741
+id: revoked 742
+id: revoked 743
+id: revoked 744
+id: revoked 745
+id: revoked 746
+id: revoked 747
+id: revoked 748
+id: revoked 749
+id: revoked 750
+id: revoked 751
+id: revoked 752
+id: revoked 753
+id: revoked 754
+id: revoked 755
+id: revoked 756
+id: revoked 757
+id: revoked 758
+id: revoked 759
+id: revoked 760
+id: revoked 761
+id: revoked 762
+id: revoked 763
+id: revoked 764
+id: revoked 765
+id: revoked 766
+id: revoked 767
+id: revoked 768
+id: revoked 769
+id: revoked 770
+id: revoked 771
+id: revoked 772
+id: revoked 773
+id: revoked 774
+id: revoked 775
+id: revoked 776
+id: revoked 777
+id: revoked 778
+id: revoked 779
+id: revoked 780
+id: revoked 781
+id: revoked 782
+id: revoked 783
+id: revoked 784
+id: revoked 785
+id: revoked 786
+id: revoked 787
+id: revoked 788
+id: revoked 789
+id: revoked 790
+id: revoked 791
+id: revoked 792
+id: revoked 793
+id: revoked 794
+id: revoked 795
+id: revoked 796
+id: revoked 797
+id: revoked 798
+id: revoked 799
+id: revoked 999
+id: revoked 1000
+id: revoked 1001
+id: revoked 1002
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials
new file mode 100644
index 0000000..b20fec2
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-serials
@@ -0,0 +1,19 @@
+serial: 1-4
+serial: 10
+serial: 15
+serial: 30
+serial: 50
+serial: 90
+serial: 999
+# The following sum to 500-799
+serial: 500
+serial: 501
+serial: 502
+serial: 503-600
+serial: 700-797
+serial: 798
+serial: 799
+serial: 599-701
+# Some multiple consecutive serial number ranges
+serial: 10000-20000
+serial: 30000-40000
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1
new file mode 100644
index 0000000..475e90c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha1
@@ -0,0 +1,11 @@
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA
+sha1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256
new file mode 100644
index 0000000..13109e9
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/revoked-sha256
@@ -0,0 +1,11 @@
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC29QZ72HtyCdaNo6p2GH3fJpUynwkvs8Acwn66G7YTh
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0ZC4fqgbBgROeX1sOEPr4uMVNfPdJ62bVo/zvSMRQx
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG5IH9T7FY47VJDUoyOlB/iqCN4pO8dgOrxclmKN5R5w
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjhG4+EnQy8YHLsfE8+IQwNWZVn1GBYX75pwxBCZGmy
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXm+qgTs+sO+9zvoZBxkQD39R2rQqQCVezxQoGjKui5
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL8+aQKja+SbqxfWR61FCcsbBw2jaF/KHvcqdP2Fbp6Q
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAOL7IULalT2Izo9TgRf1t2HNpZ5WCZJH5oRCd9LK3BN
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGVaSedhPkl2Hrx1nOOKT2E52ADsBebawws87NN1+P6e
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxyaxj7pRVDeh/gxJem9BLhoUQKGnKXHfDrB/GtC1KB
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID96GKtj4wN+OwvrQsgP37fQVUXThCML796qqFNLVDCA
+sha256: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEh3BFcTzXmg1Fi5LvWiUDWORsHzVhUCm8ekrEJG6+6A
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005
new file mode 100644
index 0000000..d82a0b5
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDqONQediveIXoseoT+MWp9yEdMO7hP7F4fAno6gunyoAAAAIig1MZroNTG
+awAAAAtzc2gtZWQyNTUxOQAAACDqONQediveIXoseoT+MWp9yEdMO7hP7F4fAno6gunyoA
+AAAEBSEPLoX4NVkAchYZEGi7hjd5NoVBWuoxqluCGt/fWrYeo41B52K94heix6hP4xan3I
+R0w7uE/sXh8CejqC6fKgAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub
new file mode 100644
index 0000000..59ea422
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGnzDhP/hp83ipkW8T7f0CIXJuPK7ldbJFKDUrkvn6J1AAAAIOo41B52K94heix6hP4xan3IR0w7uE/sXh8CejqC6fKgAAAAAAAAAAUAAAABAAAACXJldm9rZWQgNQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQO9W58IrK+I0o2us9Hs/QBkrEe1YIgl6PzCMsu/Zu/tdZxGDK5Pxoz7tKzXezS9LPGQfZ3fVdl58PZC1DtxQ5gU= ./tst-keys/unrevoked-0005.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub
new file mode 100644
index 0000000..081ac6c
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0005.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOo41B52K94heix6hP4xan3IR0w7uE/sXh8CejqC6fKg
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009
new file mode 100644
index 0000000..9479498
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDXQqTeALQCMo64B4EX5abjRvrjVu69Mnxgg2q0SB5/oQAAAIgIqeXLCKnl
+ywAAAAtzc2gtZWQyNTUxOQAAACDXQqTeALQCMo64B4EX5abjRvrjVu69Mnxgg2q0SB5/oQ
+AAAECubGChJGu90ZNiP/zF+tTtr0+l7y8BrTDMQ0m0+cU0qtdCpN4AtAIyjrgHgRflpuNG
++uNW7r0yfGCDarRIHn+hAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub
new file mode 100644
index 0000000..9ee8890
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIERRY0M1bHm2Qjyo105OCHWp0UCRHLP0xkMuHnkMDP5eAAAAINdCpN4AtAIyjrgHgRflpuNG+uNW7r0yfGCDarRIHn+hAAAAAAAAAAkAAAABAAAACXJldm9rZWQgOQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQFsA4xJHRCXSyq6GHkKdemfbg+jvUZxHlu/UBoZf4esEHAtx0mXiajbUwkWzkh1vCtxZNZhiLIhxqDcNMu+O+wo= ./tst-keys/unrevoked-0009.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub
new file mode 100644
index 0000000..74a797b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0009.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINdCpN4AtAIyjrgHgRflpuNG+uNW7r0yfGCDarRIHn+h
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014
new file mode 100644
index 0000000..6fa4fd9
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDvTTMHyjozzZabuUzy61XOKBm4klUjUGSWYtX6T4XtEwAAAIhyFdxYchXc
+WAAAAAtzc2gtZWQyNTUxOQAAACDvTTMHyjozzZabuUzy61XOKBm4klUjUGSWYtX6T4XtEw
+AAAEBtC+f4bz1/qtq5K2Rf+0bPeY3P0OWdD3rvrlGPh8wN5u9NMwfKOjPNlpu5TPLrVc4o
+GbiSVSNQZJZi1fpPhe0TAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub
new file mode 100644
index 0000000..bb954f9
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPes2n/Xk4mm4OpuvHDqx9+76vm+SmFgc9d7ATGT1+C8AAAAIO9NMwfKOjPNlpu5TPLrVc4oGbiSVSNQZJZi1fpPhe0TAAAAAAAAAA4AAAABAAAACnJldm9rZWQgMTQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDGVORypw3DoMuWBu0V4cH/OgRBstD5cY37CfLrVZpmGv9jDRXVNQee7vYowk0r3XvQPoUecQBIMZGAQtEiw18E ./tst-keys/unrevoked-0014.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub
new file mode 100644
index 0000000..4a866e4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0014.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO9NMwfKOjPNlpu5TPLrVc4oGbiSVSNQZJZi1fpPhe0T
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016
new file mode 100644
index 0000000..62d5027
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBWKMDlSwSGo4dcBAZmL+Xxk64Wp/ZfFSu2vkp82JXQCQAAAIjUcNt51HDb
+eQAAAAtzc2gtZWQyNTUxOQAAACBWKMDlSwSGo4dcBAZmL+Xxk64Wp/ZfFSu2vkp82JXQCQ
+AAAEC1V7PD5tJSOUZtpfqVfWyiSIMJkCDFZzTmFs7GBpJE71YowOVLBIajh1wEBmYv5fGT
+rhan9l8VK7a+SnzYldAJAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub
new file mode 100644
index 0000000..367e4ab
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAICGqa0xwr0etbKquuBy5/hYQ/rbMrKfEE6XShgb4YWpUAAAAIFYowOVLBIajh1wEBmYv5fGTrhan9l8VK7a+SnzYldAJAAAAAAAAABAAAAABAAAACnJldm9rZWQgMTYAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBKVetE3dsch2wjMIHGoiH8zp6gFMn1KgGKn01EPc1A08a/JKNvaSDYhlARLjiBzjIUGlykhHTTr4EcHTPWl58P ./tst-keys/unrevoked-0016.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub
new file mode 100644
index 0000000..47cac1e
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0016.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFYowOVLBIajh1wEBmYv5fGTrhan9l8VK7a+SnzYldAJ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029
new file mode 100644
index 0000000..589daa6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA3B1NQ9RFEkJUGcIUcCL22yMVEeob8/PUsk9lYH43vPwAAAIjxPrzV8T68
+1QAAAAtzc2gtZWQyNTUxOQAAACA3B1NQ9RFEkJUGcIUcCL22yMVEeob8/PUsk9lYH43vPw
+AAAED89ht9KdlYRfsKwh+pzh6BOvPf/U58QBkw1d3LfKnn+jcHU1D1EUSQlQZwhRwIvbbI
+xUR6hvz89SyT2Vgfje8/AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub
new file mode 100644
index 0000000..1bf3883
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIEVLRuchC4z7/EqITmyqCxOyhC7/enmFWsalP8FFFYiXAAAAIDcHU1D1EUSQlQZwhRwIvbbIxUR6hvz89SyT2Vgfje8/AAAAAAAAAB0AAAABAAAACnJldm9rZWQgMjkAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEChRFz/Zb6b3znoIWJjd8OTmCIEH7YE/fKWtyWHoGjz02G4VnCfwuHp23yD+k1XsoOGC7xcSnQeqZ19160HDNgC ./tst-keys/unrevoked-0029.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub
new file mode 100644
index 0000000..4072d92
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0029.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDcHU1D1EUSQlQZwhRwIvbbIxUR6hvz89SyT2Vgfje8/
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049
new file mode 100644
index 0000000..b5788a0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACD2mB5GBuavtb/bX7W54OmUCCJzUWBwG7cQ4q/jon1MBQAAAIjRkEU40ZBF
+OAAAAAtzc2gtZWQyNTUxOQAAACD2mB5GBuavtb/bX7W54OmUCCJzUWBwG7cQ4q/jon1MBQ
+AAAECuUtJb+T0um2mGvjD/ZZpbtjIhWc3jGVbzuDnEovOjnPaYHkYG5q+1v9tftbng6ZQI
+InNRYHAbtxDir+OifUwFAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub
new file mode 100644
index 0000000..587cf62
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILZPLEL5xQ8HDLa8pJhchJ3EEhZcjMqACCAEeL+U6c/QAAAAIPaYHkYG5q+1v9tftbng6ZQIInNRYHAbtxDir+OifUwFAAAAAAAAADEAAAABAAAACnJldm9rZWQgNDkAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB2GglzoC1VgsYNAVd5BDsLbeR5M5hHcVVvNsGnK1QCXMj56cgfkbXLj6W6tjJEEFY4G+KPJh1F/SGJi02P5lkJ ./tst-keys/unrevoked-0049.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub
new file mode 100644
index 0000000..07d5369
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0049.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPaYHkYG5q+1v9tftbng6ZQIInNRYHAbtxDir+OifUwF
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051
new file mode 100644
index 0000000..52d3283
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACD39ygfAlHPhZWU8inWu1hypIlQTChQxSKKB6iaV6Q0lQAAAIgMawsqDGsL
+KgAAAAtzc2gtZWQyNTUxOQAAACD39ygfAlHPhZWU8inWu1hypIlQTChQxSKKB6iaV6Q0lQ
+AAAEB4Ng9MekhsMKYDaBcOUWdxmi1rjgCsPOOfpABTxiCef/f3KB8CUc+FlZTyKda7WHKk
+iVBMKFDFIooHqJpXpDSVAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub
new file mode 100644
index 0000000..5b4bd11
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGTNYRrlJ1vExK7dume319Krn4YW6wyZc4PzZLjZoB8zAAAAIPf3KB8CUc+FlZTyKda7WHKkiVBMKFDFIooHqJpXpDSVAAAAAAAAADMAAAABAAAACnJldm9rZWQgNTEAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBbReZevLKczhayKUADRdAvZ5DXVzAJpQkcB4MPdQu/OAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAgUiwWKerMo8nuejTER/EmM6ZUpmXjgFwPCpb1LAxBJH71iOnyF9S0gp+CSmjqiTS2yuQajSMen64wOdJCX7wF ./tst-keys/unrevoked-0051.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub
new file mode 100644
index 0000000..88867e5
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0051.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPf3KB8CUc+FlZTyKda7WHKkiVBMKFDFIooHqJpXpDSV
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499
new file mode 100644
index 0000000..8f59be9
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCpwI1aCbAOVvA7NJhLtBNpR4tiGGtTQ019wjKL6zJ/uQAAAIhllrzrZZa8
+6wAAAAtzc2gtZWQyNTUxOQAAACCpwI1aCbAOVvA7NJhLtBNpR4tiGGtTQ019wjKL6zJ/uQ
+AAAECQ6o+3J9W3wXFWEcrPJl5qJZudUPmPdKF7SYxcMTrVP6nAjVoJsA5W8Ds0mEu0E2lH
+i2IYa1NDTX3CMovrMn+5AAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub
new file mode 100644
index 0000000..a6e76f1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJvt1IxZsGIIS9DDCCKiD13Dbs5Af5ouews+YwZ9FoydAAAAIKnAjVoJsA5W8Ds0mEu0E2lHi2IYa1NDTX3CMovrMn+5AAAAAAAAAfMAAAABAAAAC3Jldm9rZWQgNDk5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABAMaA4UjND4LX9kdHjhgWJjGzzs/xUBwxQQcAmNgwmmQzmkwj8ctWBBA1+TkBMcZbSNUWBdclT4UcnDPEYqG1NBg== ./tst-keys/unrevoked-0499.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub
new file mode 100644
index 0000000..5a3acbb
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0499.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKnAjVoJsA5W8Ds0mEu0E2lHi2IYa1NDTX3CMovrMn+5
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800
new file mode 100644
index 0000000..9684d72
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAn5h8A2vYJ1+IWVtdLMulUQKCqlVLHpcHEFqYC5gtGlwAAAIh2lf7UdpX+
+1AAAAAtzc2gtZWQyNTUxOQAAACAn5h8A2vYJ1+IWVtdLMulUQKCqlVLHpcHEFqYC5gtGlw
+AAAEAEXGgMPKs3HwkQmNdVkbO3PcaBVCBEv1l8yy/ly30jPSfmHwDa9gnX4hZW10sy6VRA
+oKqVUselwcQWpgLmC0aXAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub
new file mode 100644
index 0000000..ab47a2b
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPAKFTJ25v9CsCppsQ/FwXAZgntAIdQHUXo0KQ3FrlTzAAAAICfmHwDa9gnX4hZW10sy6VRAoKqVUselwcQWpgLmC0aXAAAAAAAAAyAAAAABAAAAC3Jldm9rZWQgODAwAAAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACAW0XmXryynM4WsilAA0XQL2eQ11cwCaUJHAeDD3ULvzgAAAFMAAAALc3NoLWVkMjU1MTkAAABA16aKfsgD0iZ+qc2b1AxBHZ/nyczN2Xjbhg4eJm/6cPSkBHs8uan5e8yPBIQJq2LztC3If6Z6PARoWUnIKb43CQ== ./tst-keys/unrevoked-0800.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub
new file mode 100644
index 0000000..3a41f29
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-0800.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICfmHwDa9gnX4hZW10sy6VRAoKqVUselwcQWpgLmC0aX
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010
new file mode 100644
index 0000000..89df717
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAg0jawQzRMO/ESfFm6yDc66J5kjasOqTb7rmQSU6Nk3QAAAIhczXMoXM1z
+KAAAAAtzc2gtZWQyNTUxOQAAACAg0jawQzRMO/ESfFm6yDc66J5kjasOqTb7rmQSU6Nk3Q
+AAAEAdeQiqpyZqBaffmgy+UrvFVpygD0n8isn3zjumVNtKxiDSNrBDNEw78RJ8WbrINzro
+nmSNqw6pNvuuZBJTo2TdAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub
new file mode 100644
index 0000000..2d0fe53
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIITg9nSjjofIKXTKf2byvYL3Ce43PP9Dtrbj/+AlfgEtAAAAICDSNrBDNEw78RJ8WbrINzronmSNqw6pNvuuZBJTo2TdAAAAAAAAA/IAAAABAAAADHJldm9rZWQgMTAxMAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQIndHhKILtU0+FkKKw1KmhaHQS3p1KiQdld/2P5jpcEgb292iY+ICU+aHXKvS8qGM2aMImv8835NEyWy/MB74QM= ./tst-keys/unrevoked-1010.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub
new file mode 100644
index 0000000..05c5eac
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1010.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICDSNrBDNEw78RJ8WbrINzronmSNqw6pNvuuZBJTo2Td
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011 b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011
new file mode 100644
index 0000000..38b8232
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCd4IBQx9BhO9FzYMOKu3cKgBcwUwb7XzS3uI26RgmEYgAAAIjHvhtux74b
+bgAAAAtzc2gtZWQyNTUxOQAAACCd4IBQx9BhO9FzYMOKu3cKgBcwUwb7XzS3uI26RgmEYg
+AAAEBsteyDUYUNwgY3SMkMs0guy8MJfek2kuvH35zEpVf6Hp3ggFDH0GE70XNgw4q7dwqA
+FzBTBvtfNLe4jbpGCYRiAAAAAAECAwQF
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub
new file mode 100644
index 0000000..4671638
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMjD2+xjmUC1VviOH+peT9C81Y4xjyTue/F69nFKmQBMAAAAIJ3ggFDH0GE70XNgw4q7dwqAFzBTBvtfNLe4jbpGCYRiAAAAAAAAA/MAAAABAAAADHJldm9rZWQgMTAxMQAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgFtF5l68spzOFrIpQANF0C9nkNdXMAmlCRwHgw91C784AAABTAAAAC3NzaC1lZDI1NTE5AAAAQNENdVFCE02X6z+wFJtm2DQcgdc4oov9DyFKLPqLrogo+pVao5QwOkeJ2J/tmp40H2+uP/jrDlQuCvOcoQGHqwY= ./tst-keys/unrevoked-1011.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub
new file mode 100644
index 0000000..0809077
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/krl/unrevoked-1011.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ3ggFDH0GE70XNgw4q7dwqAFzBTBvtfNLe4jbpGCYRi
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key
new file mode 100644
index 0000000..ee3f922
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACA3ivU7wf37jE1ITC5KQjVeVlyFTkgWJxub8t380ovjiwAAAJDdMhQO3TIU
+DgAAAAtzc2gtZWQyNTUxOQAAACA3ivU7wf37jE1ITC5KQjVeVlyFTkgWJxub8t380ovjiw
+AAAEA4NlTFs3h2zqt5pSZ5S3dJb42GE7EjG16coKj70eELNDeK9TvB/fuMTUhMLkpCNV5W
+XIVOSBYnG5vy3fzSi+OLAAAADVRIV09AU0VBR044MDA=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key-cert.pub
new file mode 100644
index 0000000..2be08be
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIGXo4+L/NyBl1VQDP39PxJP3LSzaqopqZGVP3cG0WoFAAAAAIDeK9TvB/fuMTUhMLkpCNV5WXIVOSBYnG5vy3fzSi+OLAAAAAAAAAAUAAAABAAAABnRlc3RlcgAAABYAAAASdGVzdGVyQGV4YW1wbGUuY29tAAAAAGbTroAAAAAAZyLIgAAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMdEl+iOTEbf1RC3uicECtid+SaIMsAw7wrlWhOQTyBVAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA/HwKB8J/kvkEsdxDou+UebnR9u30xPH6FEnbHLlfKbKMIXwLFIHnf9F6bTL36WhFDEDcSBGS19VBWBDRosM8L
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key.pub
new file mode 100644
index 0000000..0255005
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/other_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDeK9TvB/fuMTUhMLkpCNV5WXIVOSBYnG5vy3fzSi+OL
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle
new file mode 100644
index 0000000..c402f54
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/repo.bundle
Binary files differ
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key
new file mode 100644
index 0000000..3dd37be
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBgEzmfD3DinWPe/H8yLLZ2dPhbnnyFiqe8EWcp0C3czgAAAJDhSMqA4UjK
+gAAAAAtzc2gtZWQyNTUxOQAAACBgEzmfD3DinWPe/H8yLLZ2dPhbnnyFiqe8EWcp0C3czg
+AAAEB1yC00NMYEAVzhDj9odGVL0EonaIkf5jdUZ/czJ0+SPWATOZ8PcOKdY978fzIstnZ0
++FuefIWKp7wRZynQLdzOAAAADVRIV09AU0VBR044MDA=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key-cert.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key-cert.pub
new file mode 100644
index 0000000..de191d1
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key-cert.pub
@@ -0,0 +1 @@
+ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIFEmoWkYraMju0JI0b/0RQtR6RYo/OVp53EVf48L/Pu/AAAAIBmHlkHFlA7HkoTZcau80PH5zduQu41m8BqnH/1v2BwVAAAAAAAAAAEAAAABAAAACGFfa2V5X2lkAAAAFgAAABJ0ZXN0ZXJAZXhhbXBsZS5jb20AAAAAZtOugAAAAABm1QAAAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg2ifM9NMuXwQf7H/H5LCMhMjVqugyyN+jmcMoJUL2YLAAAABTAAAAC3NzaC1lZDI1NTE5AAAAQG1kXUido46YOnmwvkJuIAKyp6Q9Gr+lbdOQvU0St/Hc9HTTIxgDGyLpv0alIJpHOuSYUUUxDufvGKtLJK1duwg= ./signing_key.pub
diff --git a/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key.pub b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key.pub
new file mode 100644
index 0000000..e1210e7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst-rsrc/org/eclipse/jgit/internal/signing/ssh/signing_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AbstractSshSignatureTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AbstractSshSignatureTest.java
new file mode 100644
index 0000000..fdfffce
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AbstractSshSignatureTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.time.Instant;
+import java.time.ZoneOffset;
+
+import org.eclipse.jgit.api.CommitCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Common setup for SSH signature tests.
+ */
+public abstract class AbstractSshSignatureTest extends RepositoryTestCase {
+
+ @Rule
+ public TemporaryFolder keys = new TemporaryFolder();
+
+ protected File certs;
+
+ protected Instant commitTime;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ copyResource("allowed_signers", keys.getRoot());
+ copyResource("other_key", keys.getRoot());
+ copyResource("other_key.pub", keys.getRoot());
+ copyResource("other_key-cert.pub", keys.getRoot());
+ copyResource("signing_key", keys.getRoot());
+ copyResource("signing_key.pub", keys.getRoot());
+ certs = keys.newFolder("certs");
+ copyResource("certs/expired.cert", certs);
+ copyResource("certs/no_principals.cert", certs);
+ copyResource("certs/other.cert", certs);
+ copyResource("certs/other-ca.cert", certs);
+ copyResource("certs/tester.cert", certs);
+ copyResource("certs/two_principals.cert", certs);
+ Repository repo = db;
+ StoredConfig config = repo.getConfig();
+ config.setString("gpg", null, "format", "ssh");
+ config.setString("gpg", "ssh", "allowedSignersFile",
+ keys.getRoot().toPath().resolve("allowed_signers").toString()
+ .replace('\\', '/'));
+ config.save();
+ // Run all tests with commit times on 2024-10-02T12:00:00Z. The test
+ // certificates are valid from 2024-09-01 to 2024-10-31, except the
+ // "expired" certificate which is valid only on 2024-09-01.
+ commitTime = Instant.parse("2024-10-02T12:00:00.00Z");
+ }
+
+ private void copyResource(String name, File directory) throws IOException {
+ try (InputStream in = this.getClass().getResourceAsStream(name)) {
+ int i = name.lastIndexOf('/');
+ String fileName = i < 0 ? name : name.substring(i + 1);
+ Files.copy(in, directory.toPath().resolve(fileName));
+ }
+ }
+
+ protected RevCommit createSignedCommit(String certificate,
+ String signingKey) throws Exception {
+ Repository repo = db;
+ Path key = keys.getRoot().toPath().resolve(signingKey);
+ if (certificate != null) {
+ Files.copy(certs.toPath().resolve(certificate),
+ keys.getRoot().toPath().resolve(signingKey),
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+ PersonIdent commitAuthor = new PersonIdent("tester",
+ "tester@example.com", commitTime, ZoneOffset.UTC);
+ try (Git git = Git.wrap(repo)) {
+ writeTrashFile("foo.txt", "foo");
+ git.add().addFilepattern("foo.txt").call();
+ CommitCommand commit = git.commit();
+ commit.setAuthor(commitAuthor);
+ commit.setCommitter(commitAuthor);
+ commit.setMessage("Message");
+ commit.setSign(Boolean.TRUE);
+ commit.setSigningKey(key.toAbsolutePath().toString());
+ return commit.call();
+ }
+ }
+
+ protected RevCommit checkSshSignature(RevCommit c) {
+ byte[] sig = c.getRawGpgSignature();
+ assertNotNull(sig);
+ String signature = new String(sig, StandardCharsets.US_ASCII);
+ assertTrue("Not an SSH signature:\n" + signature,
+ signature.startsWith(Constants.SSH_SIGNATURE_PREFIX));
+ return c;
+ }
+}
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
new file mode 100644
index 0000000..90fde3f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/AllowedSignersParseTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import java.io.StreamCorruptedException;
+import java.time.Instant;
+
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.eclipse.jgit.util.SystemReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the line parsing in {@link AllowedSigners}.
+ */
+public class AllowedSignersParseTest {
+
+ @Before
+ public void setup() {
+ // Uses GMT-03:30 as time zone.
+ SystemReader.setInstance(new MockSystemReader());
+ }
+
+ @After
+ public void tearDown() {
+ SystemReader.setInstance(null);
+ }
+
+ @Test
+ public void testValidDate() {
+ assertEquals(Instant.parse("2024-09-01T00:00:00.00Z"),
+ AllowedSigners.parseDate("20240901Z"));
+ assertEquals(Instant.parse("2024-09-01T01:02:00.00Z"),
+ AllowedSigners.parseDate("202409010102Z"));
+ assertEquals(Instant.parse("2024-09-01T01:02:03.00Z"),
+ AllowedSigners.parseDate("20240901010203Z"));
+ assertEquals(Instant.parse("2024-09-01T03:30:00.00Z"),
+ AllowedSigners.parseDate("20240901"));
+ assertEquals(Instant.parse("2024-09-01T04:32:00.00Z"),
+ AllowedSigners.parseDate("202409010102"));
+ assertEquals(Instant.parse("2024-09-01T04:32:03.00Z"),
+ AllowedSigners.parseDate("20240901010203"));
+ }
+
+ @Test
+ public void testInvalidDate() {
+ assertThrows(Exception.class, () -> AllowedSigners.parseDate("1234"));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parseDate("09/01/2024"));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parseDate("2024-09-01"));
+ }
+
+ private void checkValidKey(String expected, String input, int from)
+ throws StreamCorruptedException {
+ assertEquals(expected, AllowedSigners.parsePublicKey(input, from));
+ }
+ @Test
+ public void testValidPublicKey() throws StreamCorruptedException {
+ checkValidKey(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ 0);
+ checkValidKey(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ "xyzssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ 3);
+ checkValidKey(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ "xyz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO abc",
+ 3);
+ checkValidKey(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO",
+ "xyz\tssh-ed25519 \tAAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO abc",
+ 3);
+ }
+
+ @Test
+ public void testInvalidPublicKey() {
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey(null, 0));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("", 0));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("foo", 0));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", -1));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 12));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 13));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.parsePublicKey("ssh-ed25519 bar", 16));
+ }
+
+ @Test
+ public void testValidDequote() {
+ assertEquals(new AllowedSigners.Dequoted("a\\bc", 4),
+ AllowedSigners.dequote("a\\bc", 0));
+ assertEquals(new AllowedSigners.Dequoted("a\\bc\"", 5),
+ AllowedSigners.dequote("a\\bc\"", 0));
+ assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 5),
+ AllowedSigners.dequote("a\\b\"c", 0));
+ assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 8),
+ AllowedSigners.dequote("\"a\\b\\\"c\"", 0));
+ assertEquals(new AllowedSigners.Dequoted("a\\b\"c", 11),
+ AllowedSigners.dequote("xyz\"a\\b\\\"c\"", 3));
+ assertEquals(new AllowedSigners.Dequoted("abc", 6),
+ AllowedSigners.dequote(" abc def", 3));
+ }
+
+ @Test
+ public void testInvalidDequote() {
+ assertThrows(Exception.class, () -> AllowedSigners.dequote("\"abc", 0));
+ assertThrows(Exception.class,
+ () -> AllowedSigners.dequote("\"abc\\\"", 0));
+ }
+
+ @Test
+ public void testValidLine() throws Exception {
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "*@a.com" },
+ true, null, null, null,
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+ AllowedSigners.parseLine(
+ "*@a.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "*@a.com", "*@b.a.com" },
+ true, null, null, null,
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+ AllowedSigners.parseLine(
+ "*@a.com,*@b.a.com cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "foo@a.com" },
+ false, null, null, null,
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+ AllowedSigners.parseLine(
+ "foo@a.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "foo@a.com" },
+ false, new String[] { "foo", "bar" }, null, null,
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+ AllowedSigners.parseLine(
+ "foo@a.com namespaces=\"foo,bar\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "foo@a.com" },
+ false, null, Instant.parse("2024-09-01T03:30:00.00Z"), null,
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"),
+ AllowedSigners.parseLine(
+ "foo@a.com valid-After=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertEquals(new AllowedSigners.AllowedEntry(
+ new String[] { "*@a.com", "*@b.a.com" },
+ true, new String[] { "git" },
+ Instant.parse("2024-09-01T03:30:00.00Z"),
+ Instant.parse("2024-09-01T12:00:00.00Z"),
+ "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"));
+ }
+
+ @Test
+ public void testInvalidLine() {
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "cert-authority ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "namespaces=\"git\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "valid-after=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "valid-before=\"20240901\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "a@a.com namespaces=\"\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "a@a.com namespaces=\",,,\" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ assertThrows(Exception.class, () -> AllowedSigners.parseLine(
+ "a@a.com,,b@a.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGATOZ8PcOKdY978fzIstnZ0+FuefIWKp7wRZynQLdzO"));
+ }
+
+ @Test
+ public void testSkippedLine() throws Exception {
+ assertNull(AllowedSigners.parseLine(null));
+ assertNull(AllowedSigners.parseLine(""));
+ assertNull(AllowedSigners.parseLine("# Comment"));
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java
new file mode 100644
index 0000000..9f9c3ca
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrlLoadTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+
+import org.junit.Test;
+
+/**
+ * Tests loading an {@link OpenSshBinaryKrl}.
+ */
+public class OpenSshBinaryKrlLoadTest {
+
+ @Test
+ public void testLoad() throws Exception {
+ try (InputStream in = new BufferedInputStream(
+ this.getClass().getResourceAsStream("krl/krl"))) {
+ OpenSshBinaryKrl krl = OpenSshBinaryKrl.load(in, false);
+ assertNotNull(krl);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java
new file mode 100644
index 0000000..2fd7756
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/OpenSshKrlTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for {@link OpenSshKrl} using binary KRLs.
+ */
+@RunWith(Parameterized.class)
+public class OpenSshKrlTest {
+
+ // The test data was generated using the public domain OpenSSH test script
+ // with some minor modifications (une ed25519 always, generate a "includes
+ // everything KRl, name the unrekoked keys "unrevoked*", generate a plain
+ // text KRL, and don't run the ssh-keygen tests). The original script is
+ // available at
+ // https://github.com/openssh/openssh-portable/blob/67a115e/regress/krl.sh
+
+ private static final String[] KRLS = {
+ "krl-empty", "krl-keys", "krl-all",
+ "krl-sha1", "krl-sha256", "krl-hash",
+ "krl-serial", "krl-keyid", "krl-cert", "krl-ca",
+ "krl-serial-wild", "krl-keyid-wild", "krl-text" };
+
+ private static final int[] REVOKED = { 1, 4, 10, 50, 90, 500, 510, 520, 550,
+ 799, 999 };
+
+ private static final int[] UNREVOKED = { 5, 9, 14, 16, 29, 49, 51, 499, 800,
+ 1010, 1011 };
+
+ private static class TestData {
+
+ String key;
+
+ String krl;
+
+ Boolean expected;
+
+ TestData(String key, String krl, boolean expected) {
+ this.key = key;
+ this.krl = krl;
+ this.expected = Boolean.valueOf(expected);
+ }
+
+ @Override
+ public String toString() {
+ return key + '-' + krl;
+ }
+ }
+
+ @Parameters(name = "{0}")
+ public static List<TestData> initTestData() {
+ List<TestData> tests = new ArrayList<>();
+ for (int i = 0; i < REVOKED.length; i++) {
+ String key = String.format("revoked-%04d",
+ Integer.valueOf(REVOKED[i]));
+ for (String krl : KRLS) {
+ boolean expected = !krl.endsWith("-empty");
+ tests.add(new TestData(key + "-cert.pub", krl, expected));
+ expected = krl.endsWith("-keys") || krl.endsWith("-all")
+ || krl.endsWith("-hash") || krl.endsWith("-sha1")
+ || krl.endsWith("-sha256") || krl.endsWith("-text");
+ tests.add(new TestData(key + ".pub", krl, expected));
+ }
+ }
+ for (int i = 0; i < UNREVOKED.length; i++) {
+ String key = String.format("unrevoked-%04d",
+ Integer.valueOf(UNREVOKED[i]));
+ for (String krl : KRLS) {
+ boolean expected = false;
+ tests.add(new TestData(key + ".pub", krl, expected));
+ expected = krl.endsWith("-ca");
+ tests.add(new TestData(key + "-cert.pub", krl, expected));
+ }
+ }
+ return tests;
+ }
+
+ private static Path tmp;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ tmp = Files.createTempDirectory("krls");
+ for (String krl : KRLS) {
+ copyResource("krl/" + krl, tmp);
+ }
+ }
+
+ private static void copyResource(String name, Path directory)
+ throws IOException {
+ try (InputStream in = OpenSshKrlTest.class
+ .getResourceAsStream(name)) {
+ int i = name.lastIndexOf('/');
+ String fileName = i < 0 ? name : name.substring(i + 1);
+ Files.copy(in, directory.resolve(fileName));
+ }
+ }
+
+ @AfterClass
+ public static void cleanUp() throws Exception {
+ FileUtils.delete(tmp.toFile(), FileUtils.RECURSIVE);
+ }
+
+ // Injected by JUnit
+ @Parameter
+ public TestData data;
+
+ @Test
+ public void testIsRevoked() throws Exception {
+ OpenSshKrl krl = new OpenSshKrl(tmp.resolve(data.krl));
+ try (InputStream in = this.getClass()
+ .getResourceAsStream("krl/" + data.key)) {
+ PublicKey key = AuthorizedKeyEntry.readAuthorizedKeys(in, true)
+ .get(0)
+ .resolvePublicKey(null, PublicKeyEntryResolver.FAILING);
+ assertEquals(data.expected, Boolean.valueOf(krl.isRevoked(key)));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java
new file mode 100644
index 0000000..e6709ad
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SerialRangeSetTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Tests for the set of serial number ranges.
+ */
+public class SerialRangeSetTest {
+
+ private SerialRangeSet ranges = new SerialRangeSet();
+
+ @Test
+ public void testInsertSimple() {
+ ranges.add(1);
+ ranges.add(3);
+ ranges.add(5);
+ assertEquals(3, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertFalse(ranges.contains(2));
+ assertTrue(ranges.contains(3));
+ assertFalse(ranges.contains(4));
+ assertTrue(ranges.contains(5));
+ assertFalse(ranges.contains(6));
+ }
+
+ @Test
+ public void testInsertSimpleRanges() {
+ ranges.add(1, 2);
+ ranges.add(4, 5);
+ ranges.add(7, 8);
+ assertEquals(3, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertTrue(ranges.contains(2));
+ assertFalse(ranges.contains(3));
+ assertTrue(ranges.contains(4));
+ assertTrue(ranges.contains(5));
+ assertFalse(ranges.contains(6));
+ assertTrue(ranges.contains(7));
+ assertTrue(ranges.contains(8));
+ assertFalse(ranges.contains(9));
+ }
+
+ @Test
+ public void testInsertCoalesce() {
+ ranges.add(5);
+ ranges.add(1);
+ ranges.add(2);
+ ranges.add(4);
+ ranges.add(7);
+ ranges.add(3);
+ assertEquals(2, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertTrue(ranges.contains(2));
+ assertTrue(ranges.contains(3));
+ assertTrue(ranges.contains(4));
+ assertTrue(ranges.contains(5));
+ assertFalse(ranges.contains(6));
+ assertTrue(ranges.contains(7));
+ assertFalse(ranges.contains(8));
+ }
+
+ @Test
+ public void testInsertOverlap() {
+ ranges.add(1, 3);
+ ranges.add(6);
+ ranges.add(2, 5);
+ assertEquals(1, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertTrue(ranges.contains(2));
+ assertTrue(ranges.contains(3));
+ assertTrue(ranges.contains(4));
+ assertTrue(ranges.contains(5));
+ assertTrue(ranges.contains(6));
+ assertFalse(ranges.contains(7));
+ }
+
+ @Test
+ public void testInsertOverlapMultiple() {
+ ranges.add(1, 3);
+ ranges.add(5, 6);
+ ranges.add(8);
+ ranges.add(2, 5);
+ assertEquals(2, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertTrue(ranges.contains(2));
+ assertTrue(ranges.contains(3));
+ assertTrue(ranges.contains(4));
+ assertTrue(ranges.contains(5));
+ assertTrue(ranges.contains(6));
+ assertFalse(ranges.contains(7));
+ assertTrue(ranges.contains(8));
+ assertFalse(ranges.contains(9));
+ }
+
+ @Test
+ public void testInsertOverlapTotal() {
+ ranges.add(1, 3);
+ ranges.add(2, 3);
+ assertEquals(1, ranges.size());
+ assertFalse(ranges.contains(0));
+ assertTrue(ranges.contains(1));
+ assertTrue(ranges.contains(2));
+ assertTrue(ranges.contains(3));
+ assertFalse(ranges.contains(4));
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtilsTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtilsTest.java
new file mode 100644
index 0000000..79ca21f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtilsTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.List;
+
+import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link SshCertificateUtils}. They use a certificate valid from
+ * 2024-09-01 00:00:00 to 2024-09-02 00:00:00 UTC.
+ */
+public class SshCertificateUtilsTest {
+
+ private OpenSshCertificate certificate;
+
+ @Before
+ public void loadCertificate() throws Exception {
+ try (InputStream in = this.getClass().getResourceAsStream(
+ "certs/expired.cert")) {
+ List<AuthorizedKeyEntry> keys = AuthorizedKeyEntry
+ .readAuthorizedKeys(in, true);
+ if (keys.isEmpty()) {
+ certificate = null;
+ }
+ PublicKey key = keys.get(0).resolvePublicKey(null,
+ PublicKeyEntryResolver.FAILING);
+ assertTrue(
+ "Expected an OpenSshKeyCertificate but got a "
+ + key.getClass().getName(),
+ key instanceof OpenSshCertificate);
+ certificate = (OpenSshCertificate) key;
+ }
+ }
+
+ @Test
+ public void testValidUserCertificate() {
+ assertNull(SshCertificateUtils.verify(certificate, null));
+ Instant validTime = Instant.parse("2024-09-01T00:00:00.00Z");
+ assertNull(SshCertificateUtils.verify(certificate, validTime));
+ assertNull(SshCertificateUtils.checkExpiration(certificate, validTime));
+ }
+
+ @Test
+ public void testCheckTooEarly() {
+ Instant invalidTime = Instant.parse("2024-08-31T23:59:59.00Z");
+ assertNotNull(
+ SshCertificateUtils.checkExpiration(certificate, invalidTime));
+ assertNotNull(SshCertificateUtils.verify(certificate, invalidTime));
+ }
+
+ @Test
+ public void testCheckExpired() {
+ Instant invalidTime = Instant.parse("2024-09-02T00:00:01.00Z");
+ assertNotNull(
+ SshCertificateUtils.checkExpiration(certificate, invalidTime));
+ assertNotNull(SshCertificateUtils.verify(certificate, invalidTime));
+ }
+
+ @Test
+ public void testInvalidSignature() throws Exception {
+ // Modify the serialized certificate, then re-load it again. To check that
+ // serialization per se works fine, also check an unmodified version.
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putPublicKey(certificate);
+ int pos = buffer.rpos();
+ PublicKey unchanged = buffer.getPublicKey();
+ assertTrue(
+ "Expected an OpenSshCertificate but got a "
+ + unchanged.getClass().getName(),
+ unchanged instanceof OpenSshCertificate);
+ assertNull(SshCertificateUtils.verify((OpenSshCertificate) unchanged,
+ null));
+ buffer.rpos(pos);
+ // Change a byte. The test certificate has the key ID at offset 128.
+ // Changing a byte in the key ID should still result in a successful
+ // deserialization, but then fail the signature check.
+ buffer.array()[pos + 128]++;
+ PublicKey changed = buffer.getPublicKey();
+ assertTrue(
+ "Expected an OpenSshCertificate but got a "
+ + changed.getClass().getName(),
+ changed instanceof OpenSshCertificate);
+ assertNotNull(
+ SshCertificateUtils.verify((OpenSshCertificate) changed, null));
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java
new file mode 100644
index 0000000..e5dfe49
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifierTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.VerificationResult;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.StringUtils;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link SshSignatureVerifier}.
+ */
+public class SshSignatureVerifierTest extends AbstractSshSignatureTest {
+
+ @Test
+ public void testPlainSignature() throws Exception {
+ RevCommit c = checkSshSignature(
+ createSignedCommit(null, "signing_key.pub"));
+ try (Git git = new Git(db)) {
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addName(c.getName()).call();
+ assertEquals(1, results.size());
+ VerificationResult verified = results.get(c.getName());
+ assertNotNull(verified);
+ assertNull(verified.getException());
+ SignatureVerifier.SignatureVerification v = verified
+ .getVerification();
+ assertTrue(v.verified());
+ assertFalse(v.expired());
+ assertTrue(StringUtils.isEmptyOrNull(v.message()));
+ assertEquals("tester@example.com", v.keyUser());
+ assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+ v.keyFingerprint());
+ assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+ assertEquals(commitTime, v.creationDate().toInstant());
+ }
+ }
+
+ @Test
+ public void testCertificateSignature() throws Exception {
+ RevCommit c = checkSshSignature(
+ createSignedCommit("tester.cert", "signing_key-cert.pub"));
+ try (Git git = new Git(db)) {
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addName(c.getName()).call();
+ assertEquals(1, results.size());
+ VerificationResult verified = results.get(c.getName());
+ assertNotNull(verified);
+ assertNull(verified.getException());
+ SignatureVerifier.SignatureVerification v = verified
+ .getVerification();
+ assertTrue(v.verified());
+ assertFalse(v.expired());
+ assertTrue(StringUtils.isEmptyOrNull(v.message()));
+ assertEquals("tester@example.com", v.keyUser());
+ assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+ v.keyFingerprint());
+ assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+ assertEquals(commitTime, v.creationDate().toInstant());
+ }
+ }
+
+ @Test
+ public void testNoPrincipalsSignature() throws Exception {
+ RevCommit c = checkSshSignature(createSignedCommit("no_principals.cert",
+ "signing_key-cert.pub"));
+ try (Git git = new Git(db)) {
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addName(c.getName()).call();
+ assertEquals(1, results.size());
+ VerificationResult verified = results.get(c.getName());
+ assertNotNull(verified);
+ assertNull(verified.getException());
+ SignatureVerifier.SignatureVerification v = verified
+ .getVerification();
+ assertFalse(v.verified());
+ assertFalse(v.expired());
+ assertNull(v.keyUser());
+ assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+ v.keyFingerprint());
+ assertEquals(SignatureVerifier.TrustLevel.NEVER, v.trustLevel());
+ assertTrue(v.message().contains("*@example.com"));
+ assertEquals(commitTime, v.creationDate().toInstant());
+ }
+ }
+
+ @Test
+ public void testOtherCertificateSignature() throws Exception {
+ RevCommit c = checkSshSignature(
+ createSignedCommit("other.cert", "signing_key-cert.pub"));
+ try (Git git = new Git(db)) {
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addName(c.getName()).call();
+ assertEquals(1, results.size());
+ VerificationResult verified = results.get(c.getName());
+ assertNotNull(verified);
+ assertNull(verified.getException());
+ SignatureVerifier.SignatureVerification v = verified
+ .getVerification();
+ assertTrue(v.verified());
+ assertFalse(v.expired());
+ assertTrue(StringUtils.isEmptyOrNull(v.message()));
+ assertEquals("other@example.com", v.keyUser());
+ assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+ v.keyFingerprint());
+ assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+ assertEquals(commitTime, v.creationDate().toInstant());
+ }
+ }
+
+ @Test
+ public void testTwoPrincipalsCertificateSignature() throws Exception {
+ RevCommit c = checkSshSignature(createSignedCommit(
+ "two_principals.cert", "signing_key-cert.pub"));
+ try (Git git = new Git(db)) {
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addName(c.getName()).call();
+ assertEquals(1, results.size());
+ VerificationResult verified = results.get(c.getName());
+ assertNotNull(verified);
+ assertNull(verified.getException());
+ SignatureVerifier.SignatureVerification v = verified
+ .getVerification();
+ assertTrue(v.verified());
+ assertFalse(v.expired());
+ assertTrue(StringUtils.isEmptyOrNull(v.message()));
+ assertEquals("foo@example.com,tester@example.com", v.keyUser());
+ assertEquals("SHA256:GKW0xy+XKnJGs0CJqP6j5bd4FdiwWNaUbwvUbHvhQKo",
+ v.keyFingerprint());
+ assertEquals(SignatureVerifier.TrustLevel.FULL, v.trustLevel());
+ assertEquals(commitTime, v.creationDate().toInstant());
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignerTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignerTest.java
new file mode 100644
index 0000000..b3a4482
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/SshSignerTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+/**
+ * Tests for the {@link SshSigner}.
+ */
+public class SshSignerTest extends AbstractSshSignatureTest {
+
+ @Test
+ public void testPlainSignature() throws Exception {
+ checkSshSignature(createSignedCommit(null, "signing_key.pub"));
+ }
+
+ @Test
+ public void testExpiredSignature() throws Exception {
+ Throwable t = assertThrows(Throwable.class,
+ () -> createSignedCommit("expired.cert",
+ "signing_key-cert.pub"));
+ // The exception or one of its causes should mention "[Ee]xpired" and
+ // "[Cc]ertificate" in the message
+ while (t != null) {
+ String message = t.getMessage();
+ if (message.contains("xpired") && message.contains("ertificate")) {
+ return;
+ }
+ t = t.getCause();
+ }
+ fail("Expected exception message not found");
+ }
+
+ @Test
+ public void testCertificateSignature() throws Exception {
+ checkSshSignature(createSignedCommit("tester.cert", "signing_key.pub"));
+ }
+
+ @Test
+ public void testNoPrincipalsSignature() throws Exception {
+ // Certificate has no principals; should still work
+ checkSshSignature(
+ createSignedCommit("no_principals.cert", "signing_key.pub"));
+ }
+
+ @Test
+ public void testOtherSignature() throws Exception {
+ // Certificate has a principal different that tester@example.com; should
+ // still work
+ checkSshSignature(createSignedCommit("other.cert", "signing_key.pub"));
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java
new file mode 100644
index 0000000..30ddee5
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/internal/signing/ssh/VerifyGitSignaturesTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.signing.ssh;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.VerificationResult;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.GitDateFormatter;
+import org.eclipse.jgit.util.SignatureUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Verifies signatures made with C git and OpenSSH 9.0 to ensure we arrive at
+ * the same good/bad decisions, and that we can verify signatures not created by
+ * ourselves.
+ * <p>
+ * Clones a JGit repo from a git bundle file created with C git, then checks all
+ * the commits and their signatures. (All commits in that bundle have SSH
+ * signatures.)
+ * </p>
+ */
+public class VerifyGitSignaturesTest extends LocalDiskRepositoryTestCase {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(VerifyGitSignaturesTest.class);
+
+ @Rule
+ public TemporaryFolder bundleDir = new TemporaryFolder();
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ try (InputStream in = this.getClass()
+ .getResourceAsStream("repo.bundle")) {
+ Files.copy(in, bundleDir.getRoot().toPath().resolve("repo.bundle"));
+ }
+ try (InputStream in = this.getClass()
+ .getResourceAsStream("allowed_signers")) {
+ Files.copy(in,
+ bundleDir.getRoot().toPath().resolve("allowed_signers"));
+ }
+ }
+
+ /**
+ * Tests signatures created by C git using OpenSSH 9.0.
+ */
+ @Test
+ public void testGitSignatures() throws Exception {
+ File gitDir = new File(getTemporaryDirectory(), "repo.git");
+ try (Git git = Git.cloneRepository().setBare(true)
+ .setGitDir(gitDir)
+ .setURI(new File(bundleDir.getRoot(), "repo.bundle").toURI()
+ .toString())
+ .setBranch("master")
+ .call()) {
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString("gpg", "ssh", "allowedSignersFile",
+ bundleDir.getRoot().toPath().resolve("allowed_signers")
+ .toAbsolutePath().toString().replace('\\', '/'));
+ config.save();
+ List<String> commits = new ArrayList<>();
+ Map<String, PersonIdent> committers = new HashMap<>();
+ git.log().all().call().forEach(c -> {
+ commits.add(c.getName());
+ committers.put(c.getName(), c.getCommitterIdent());
+ });
+ Map<String, Boolean> expected = new HashMap<>();
+ // These two commits do have multiple principals. GIT just reports
+ // the first one; we report both.
+ expected.put("9f79a7b661a22ab1ddf8af880d23678ae7696b71",
+ Boolean.TRUE);
+ expected.put("435108d157440e77d61a914b6a5736bc831c874d",
+ Boolean.TRUE);
+ // This commit has a wrong commit message; the certificate used
+ // did _not_ have two principals, but only a single principal
+ // foo@example.org.
+ expected.put("779dac7de40ebc3886af87d5e6680a09f8b13a3e",
+ Boolean.TRUE);
+ // Signed with other_key-cert.pub: we still don't know the key,
+ // but we do know the certificate's CA key, and trust it, so it's
+ // accepted as a signature from the principal(s) listed in the
+ // certificate.
+ expected.put("951f06d5b5598b721b98d98b04e491f234c1926a",
+ Boolean.TRUE);
+ // Signature with other_key.pub not listed in allowed_signers
+ expected.put("984e629c6d543a7f77eb49a8c9316f2ae4416375",
+ Boolean.FALSE);
+ // Signed with other-ca.cert (CA key not in allowed_signers), but
+ // the certified key _is_ listed in allowed_signers.
+ expected.put("1d7ac6d91747a9c9a777df238fbdaeffa7731a6c",
+ Boolean.FALSE);
+ expected.put("a297bcfbf5c4a850f9770655fef7315328a4b3fb",
+ Boolean.TRUE);
+ expected.put("852729d54676cb83826ed821dc7734013e97950d",
+ Boolean.TRUE);
+ // Signature with a certificate without principals.
+ expected.put("e39a049f75fe127eb74b30aba4b64e171d4281dd",
+ Boolean.FALSE);
+ // Signature made with expired.cert (expired at the commit time).
+ // git/OpenSSH 9.0 allows to create such signatures, but reports
+ // them as FALSE. Our SshSigner doesn't allow creating such
+ // signatures.
+ expected.put("303ea5e61feacdad4cb012b4cb6b0cea3fbcef9f",
+ Boolean.FALSE);
+ expected.put("1ae4b120a869b72a7a2d4ad4d7a8c9d454384333",
+ Boolean.TRUE);
+ Map<String, VerificationResult> results = git.verifySignature()
+ .addNames(commits).call();
+ GitDateFormatter dateFormat = new GitDateFormatter(
+ GitDateFormatter.Format.ISO);
+ for (String oid : commits) {
+ VerificationResult v = results.get(oid);
+ assertNotNull(v);
+ assertNull(v.getException());
+ SignatureVerifier.SignatureVerification sv = v
+ .getVerification();
+ assertNotNull(sv);
+ LOG.info("Commit {}\n{}", oid, SignatureUtils.toString(sv,
+ committers.get(oid), dateFormat));
+ Boolean wanted = expected.get(oid);
+ assertNotNull(wanted);
+ assertEquals(wanted, Boolean.valueOf(sv.verified()));
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
index b7ea87f..d12eed0 100644
--- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF
@@ -6,9 +6,10 @@
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-ActivationPolicy: lazy
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.internal.transport.sshd;version="7.0.2";x-internal:=true;
+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;
uses:="org.apache.sshd.client,
org.apache.sshd.client.auth,
org.apache.sshd.client.auth.keyboard,
@@ -23,18 +24,19 @@
org.apache.sshd.common.signature,
org.apache.sshd.common.util.buffer,
org.eclipse.jgit.transport",
- org.eclipse.jgit.internal.transport.sshd.agent;version="7.0.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.auth;version="7.0.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.pkcs11;version="7.0.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.sshd.proxy;version="7.0.2";x-friends:="org.eclipse.jgit.ssh.apache.test",
- org.eclipse.jgit.transport.sshd;version="7.0.2";
+ 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";
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.0.2",
+ 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)",
@@ -89,12 +91,14 @@
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.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.fnmatch;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.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 c328382..1a63852 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.apache/pom.xml b/org.eclipse.jgit.ssh.apache/pom.xml
index c189776..857f8d6 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ssh.apache</artifactId>
diff --git a/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory b/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory
new file mode 100644
index 0000000..4a0f553
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignatureVerifierFactory
@@ -0,0 +1 @@
+org.eclipse.jgit.signing.ssh.SshSignatureVerifierFactory
\ No newline at end of file
diff --git a/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory b/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory
new file mode 100644
index 0000000..80f22c0
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/resources/META-INF/services/org.eclipse.jgit.lib.SignerFactory
@@ -0,0 +1 @@
+org.eclipse.jgit.signing.ssh.SshSignerFactory
\ No newline at end of file
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 7da7181..6048239 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
@@ -125,4 +125,69 @@
sshCommandTimeout={0} timed out after {1} seconds while opening the channel
sshProcessStillRunning={0} is not yet completed, cannot get exit code
sshProxySessionCloseFailed=Error while closing proxy session {0}
+signAllowedSignersCertAuthorityError=Garbage after cert-authority
+signAllowedSignersEmptyIdentity=Identities contains an empty identity; check for spurious extra commas: {0}
+signAllowedSignersEmptyNamespaces=Empty namespaces= is not allowed; to allow a key for any namespace, omit the namespaces option
+signAllowedSignersFormatError=Cannot parse allowed signers file {0}, problem at line {1}: {2}
+signAllowedSignersInvalidDate=Cannot parse valid-before or valid-after date {0}
+signAllowedSignersLineFormat=Invalid line format
+signAllowedSignersMultiple={0} is allowed only once
+signAllowedSignersNoIdentities=Line has no identity patterns
+signAllowedSignersPublicKeyParsing=Cannot parse public key {0}
+signAllowedSignersUnterminatedQuote=Unterminated double quote
+signCertAlgorithmMismatch=Certificate of type {0} with CA key {1} uses an incompatible signature algorithm {2}
+signCertAlgorithmUnknown=Certificate with CA key {0} is signed with an unknown algorithm {1}
+signCertificateExpired=Expired certificate with CA key {0}
+signCertificateInvalid=Certificate signature does not match on certificate with CA key {0}
+signCertificateNotForName=Certificate with CA key {0} does not apply for name ''{1}''
+signCertificateRevoked=Certificate with CA key {0} was revoked
+signCertificateTooEarly=Certificate with CA key {0} was not valid yet
+signCertificateWithoutPrincipals=Certificate with CA key {0} has no principals; identities from gpg.ssh.allowedSignersFile: {1}
+signDefaultKeyEmpty=git.ssh.defaultKeyCommand {0} returned no key
+signDefaultKeyFailed=git.ssh.defaultKeyCommand {0} failed with exit code {1}\n{2}
+signDefaultKeyInterrupted=git.ssh.defaultKeyCommand {0} was interrupted
+signGarbageAtEnd=SSH signature has extra bytes at the end
+signInvalidAlgorithm=SSH signature has invalid signature algorithm {0}
+signInvalidKeyDSA=SSH signatures with DSA keys or certificates are not supported; use a different signing key.
+signInvalidMagic=SSH signature does not start with "SSHSIG"
+signInvalidNamespace=Namespace of SSH signature should be ''git'' but is ''{0}''
+signInvalidSignature=SSH signature is invalid: {0}
+signInvalidVersion=Cannot verify signature with version {0}
+signKeyExpired=Expired key used for SSH signature
+signKeyRevoked=Key used for the SSH signature was revoked
+signKeyTooEarly=Key used for the SSH signature was not valid yet
+signKrlBlobLeftover=gpg.ssh.revocationFile has invalid blob section {0} with {1} leftover bytes
+signKrlBlobLengthInvalid=gpg.ssh.revocationFile has invalid blob length {1} in section {0}
+signKrlBlobLengthInvalidExpected=gpg.ssh.revocationFile has invalid blob length {1} (expected {2}) in section {0}
+signKrlCaKeyLengthInvalid=gpg.ssh.revocationFile has invalid CA key length {0} in certificates section
+signKrlCertificateLeftover=gpg.ssh.revocationFile has invalid certificates section with {0} leftover bytes
+signKrlCertificateSubsectionLeftover=gpg.ssh.revocationFile has invalid certificates subsection with {0} leftover bytes
+signKrlCertificateSubsectionLength=gpg.ssh.revocationFile has invalid certificates subsection length {0}
+signKrlEmptyRange=gpg.ssh.revocationFile has an empty range of certificate serial numbers
+signKrlInvalidBitSetLength=gpg.ssh.revocationFile has invalid certificate serial number bit set length {0}
+signKrlInvalidKeyIdLength=gpg.ssh.revocationFile has invalid certificate key ID length {0}
+signKrlInvalidMagic=gpg.ssh.revocationFile is not a binary OpenSSH key revocation list
+signKrlInvalidReservedLength=gpg.ssh.revocationFile has an invalid reserved string length {0}
+signKrlInvalidVersion=gpg.ssh.revocationFile: cannot read KRLs with FORMAT_VERSION {0}
+signKrlNoCertificateSubsection=gpg.ssh.revocationFile has certificate section without subsections
+signKrlSerialZero=gpg.ssh.revocationFile: certificate serial number zero cannot be revoked
+signKrlShortRange=gpg.ssh.revocationFile: short certificate serial number range, need at least 8 more bytes, got only {0}
+signKrlUnknownSection=gpg.ssh.revocationFile has an unknown section type {0}
+signKrlUnknownSubsection=gpg.ssh.revocationFile has an unknown certificates subsection type {0}
+signLogFailure=SSH signature verification failed
+signMismatchedSignatureAlgorithm=SSH signature made with an ''{0}'' key has incompatible signature algorithm ''{1}''
+signNoAgent=No connector for ssh-agent found; maybe include org.eclipse.jgit.ssh.apache.agent in the application.
+signNoPrincipalMatched=No principal matched in gpg.ssh.allowedSignersFile
+signNoPublicKey=No public key found with signing key {0}
+signNoSigningKey=Git config user.signingKey or gpg.ssh.defaultKeyCommand must be set for SSH signing.
+signNotUserCertificate=Certificate with CA key {0} used for the SSH signature is not a user certificate.
+signPublicKeyError=Cannot read public key {0}
+signSeeLog=SSH signature verification failed; see the log for details
+signSignatureError=Could not create the signature
+signStderr=Cannot read stderr
+signTooManyPrivateKeys=Private key file {0} must contain exactly one private key
+signTooManyPublicKeys=Public key file {0} must contain exactly one public key
+signUnknownHashAlgorithm=SSH Signature has an unknown hash algorithm {0}
+signUnknownSignatureAlgorithm=SSH Signature has an unknown signature algorithm {0}
+signWrongNamespace=Key may not be used in namespace "{0}".
unknownProxyProtocol=Ignoring unknown proxy protocol {0}
\ No newline at end of file
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
new file mode 100644
index 0000000..cfbe7a7
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/AllowedSigners.java
@@ -0,0 +1,536 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+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;
+
+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.util.io.ModifiableFileWatcher;
+import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Encapsulates the allowed signers handling.
+ */
+final class AllowedSigners extends ModifiableFileWatcher {
+
+ private static final String CERT_AUTHORITY = "cert-authority"; //$NON-NLS-1$
+
+ private static final String NAMESPACES = "namespaces="; //$NON-NLS-1$
+
+ private static final String VALID_AFTER = "valid-after="; //$NON-NLS-1$
+
+ 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)
+ .appendValue(ChronoField.DAY_OF_MONTH, 2)
+ .optionalStart()
+ .appendValue(ChronoField.HOUR_OF_DAY, 2)
+ .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+ .optionalStart()
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+ .toFormatter(Locale.ROOT);
+
+ private static final Predicate<AllowedEntry> CERTIFICATES = AllowedEntry::isCA;
+
+ private static final Predicate<AllowedEntry> PLAIN_KEYS = Predicate
+ .not(CERTIFICATES);
+
+ @SuppressWarnings("ArrayRecordComponent")
+ static record AllowedEntry(String[] identities, boolean isCA,
+ String[] namespaces, Instant validAfter, Instant validBefore,
+ String key) {
+ // Empty
+
+ @Override
+ public final boolean equals(Object any) {
+ if (this == any) {
+ return true;
+ }
+ if (any == null || !(any instanceof AllowedEntry)) {
+ return false;
+ }
+ AllowedEntry other = (AllowedEntry) any;
+ return isCA == other.isCA
+ && Arrays.equals(identities, other.identities)
+ && Arrays.equals(namespaces, other.namespaces)
+ && Objects.equals(validAfter, other.validAfter)
+ && Objects.equals(validBefore, other.validBefore)
+ && Objects.equals(key, other.key);
+ }
+
+ @Override
+ public final int hashCode() {
+ int hash = Boolean.hashCode(isCA);
+ hash = hash * 31 + Arrays.hashCode(identities);
+ hash = hash * 31 + Arrays.hashCode(namespaces);
+ return hash * 31 + Objects.hash(validAfter, validBefore, key);
+ }
+ }
+
+ private static record State(Map<String, List<AllowedEntry>> entries) {
+ // Empty
+ }
+
+ private State state;
+
+ public AllowedSigners(Path path) {
+ super(path);
+ state = new State(new HashMap<>());
+ }
+
+ public String isAllowed(PublicKey key, String namespace, String name,
+ Instant time) throws IOException, VerificationException {
+ State currentState = refresh();
+ PublicKey keyToCheck = key;
+ if (key instanceof OpenSshCertificate certificate) {
+ AllowedEntry entry = find(currentState, certificate.getCaPubKey(),
+ namespace, name, time, CERTIFICATES);
+ if (entry != null) {
+ Collection<String> principals = certificate.getPrincipals();
+ if (principals.isEmpty()) {
+ // According to the OpenSSH documentation, a certificate
+ // without principals is valid for anyone.
+ //
+ // See https://man.openbsd.org/ssh-keygen.1#CERTIFICATES .
+ //
+ // However, the same documentation also says that a name
+ // must match both the entry's patterns and be listed in the
+ // certificate's principals.
+ //
+ // See https://man.openbsd.org/ssh-keygen.1#ALLOWED_SIGNERS
+ //
+ // git/OpenSSH considers signatures made by such
+ // certificates untrustworthy.
+ String identities;
+ if (!StringUtils.isEmptyOrNull(name)) {
+ // The name must have matched entry.identities.
+ identities = name;
+ } else {
+ identities = Arrays.stream(entry.identities())
+ .collect(Collectors.joining(",")); //$NON-NLS-1$
+ }
+ throw new VerificationException(false, MessageFormat.format(
+ SshdText.get().signCertificateWithoutPrincipals,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()),
+ identities));
+ }
+ if (!StringUtils.isEmptyOrNull(name)) {
+ if (!principals.contains(name)) {
+ throw new VerificationException(false,
+ MessageFormat.format(SshdText
+ .get().signCertificateNotForName,
+ KeyUtils.getFingerPrint(
+ certificate.getCaPubKey()),
+ name));
+ }
+ return name;
+ }
+ // Filter the principals listed in the certificate by
+ // the patterns defined in the file.
+ Set<String> filtered = new LinkedHashSet<>();
+ List<String> patterns = Arrays.asList(entry.identities());
+ for (String principal : principals) {
+ if (OpenSshConfigFile.patternMatch(patterns, principal)) {
+ filtered.add(principal);
+ }
+ }
+ return filtered.stream().collect(Collectors.joining(",")); //$NON-NLS-1$
+ }
+ // Certificate not found. git/OpenSSH considers this untrustworthy,
+ // even if the certified key itself might be listed.
+ return null;
+ // Alternative: go check for the certified key itself:
+ // keyToCheck = certificate.getCertPubKey();
+ }
+ AllowedEntry entry = find(currentState, keyToCheck, namespace, name,
+ time, PLAIN_KEYS);
+ if (entry != null) {
+ if (!StringUtils.isEmptyOrNull(name)) {
+ // The name must have matched entry.identities.
+ return name;
+ }
+ // No name given, but we consider the key valid: report the
+ // identities.
+ return Arrays.stream(entry.identities())
+ .collect(Collectors.joining(",")); //$NON-NLS-1$
+ }
+ return null;
+ }
+
+ private AllowedEntry find(State current, PublicKey key,
+ String namespace, String name, Instant time,
+ Predicate<AllowedEntry> filter)
+ throws VerificationException {
+ String k = PublicKeyEntry.toString(key);
+ VerificationException v = null;
+ List<AllowedEntry> candidates = current.entries().get(k);
+ if (candidates == null) {
+ return null;
+ }
+ for (AllowedEntry entry : candidates) {
+ if (!filter.test(entry)) {
+ continue;
+ }
+ if (name != null && !OpenSshConfigFile
+ .patternMatch(Arrays.asList(entry.identities()), name)) {
+ continue;
+ }
+ if (entry.namespaces() != null) {
+ if (!OpenSshConfigFile.patternMatch(
+ Arrays.asList(entry.namespaces()),
+ namespace)) {
+ if (v == null) {
+ v = new VerificationException(false,
+ MessageFormat.format(
+ SshdText.get().signWrongNamespace,
+ KeyUtils.getFingerPrint(key),
+ namespace));
+ }
+ continue;
+ }
+ }
+ if (time != null) {
+ if (entry.validAfter() != null
+ && time.isBefore(entry.validAfter())) {
+ if (v == null) {
+ v = new VerificationException(true,
+ MessageFormat.format(
+ SshdText.get().signKeyTooEarly,
+ KeyUtils.getFingerPrint(key)));
+ }
+ continue;
+ } else if (entry.validBefore() != null
+ && time.isAfter(entry.validBefore())) {
+ if (v == null) {
+ v = new VerificationException(true,
+ MessageFormat.format(
+ SshdText.get().signKeyTooEarly,
+ KeyUtils.getFingerPrint(key)));
+ }
+ continue;
+ }
+ }
+ return entry;
+ }
+ if (v != null) {
+ throw v;
+ }
+ return null;
+ }
+
+ private synchronized State refresh() throws IOException {
+ if (checkReloadRequired()) {
+ updateReloadAttributes();
+ try {
+ state = reload(getPath());
+ } catch (NoSuchFileException e) {
+ // File disappeared
+ resetReloadAttributes();
+ state = new State(new HashMap<>());
+ }
+ }
+ return state;
+ }
+
+ private static State reload(Path path) throws IOException {
+ Map<String, List<AllowedEntry>> entries = new HashMap<>();
+ try (BufferedReader r = Files.newBufferedReader(path,
+ StandardCharsets.UTF_8)) {
+ String line;
+ for (int lineNumber = 1;; lineNumber++) {
+ line = r.readLine();
+ if (line == null) {
+ break;
+ }
+ line = line.strip();
+ try {
+ AllowedEntry entry = parseLine(line);
+ if (entry != null) {
+ entries.computeIfAbsent(entry.key(),
+ k -> new ArrayList<>()).add(entry);
+ }
+ } catch (IOException | RuntimeException e) {
+ throw new IOException(MessageFormat.format(
+ SshdText.get().signAllowedSignersFormatError, path,
+ Integer.toString(lineNumber), line), e);
+ }
+ }
+ }
+ return new State(entries);
+ }
+
+ private static boolean matches(String src, String other, int offset) {
+ return src.regionMatches(true, offset, other, 0, other.length());
+ }
+
+ // Things below have package visibility for testing.
+
+ static AllowedEntry parseLine(String line)
+ throws IOException {
+ if (StringUtils.isEmptyOrNull(line) || line.charAt(0) == '#') {
+ return null;
+ }
+ int length = line.length();
+ if ((matches(line, CERT_AUTHORITY, 0)
+ && CERT_AUTHORITY.length() < length
+ && 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)) {
+ throw new StreamCorruptedException(
+ SshdText.get().signAllowedSignersNoIdentities);
+ }
+ int i = 0;
+ while (i < length && !Character.isWhitespace(line.charAt(i))) {
+ i++;
+ }
+ if (i >= length) {
+ throw new StreamCorruptedException(SshdText.get().signAllowedSignersLineFormat);
+ }
+ String[] identities = line.substring(0, i).split(","); //$NON-NLS-1$
+ if (Arrays.stream(identities).anyMatch(String::isEmpty)) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersEmptyIdentity,
+ line.substring(0, i)));
+ }
+ // Parse the options
+ i++;
+ boolean isCA = false;
+ List<String> namespaces = null;
+ Instant validAfter = null;
+ Instant validBefore = null;
+ while (i < length) {
+ // Skip whitespace
+ if (Character.isSpaceChar(line.charAt(i))) {
+ i++;
+ continue;
+ }
+ if (matches(line, CERT_AUTHORITY, i)) {
+ i += CERT_AUTHORITY.length();
+ isCA = true;
+ if (!Character.isWhitespace(line.charAt(i))) {
+ throw new StreamCorruptedException(SshdText.get().signAllowedSignersCertAuthorityError);
+ }
+ i++;
+ } else if (matches(line, NAMESPACES, i)) {
+ if (namespaces != null) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersMultiple,
+ NAMESPACES));
+ }
+ i += NAMESPACES.length();
+ Dequoted parsed = dequote(line, i);
+ i = parsed.after();
+ String ns = parsed.value();
+ String[] items = ns.split(","); //$NON-NLS-1$
+ namespaces = new ArrayList<>(items.length);
+ for (int j = 0; j < items.length; j++) {
+ String n = items[j].strip();
+ if (!n.isEmpty()) {
+ namespaces.add(n);
+ }
+ }
+ if (namespaces.isEmpty()) {
+ throw new StreamCorruptedException(
+ SshdText.get().signAllowedSignersEmptyNamespaces);
+ }
+ } else if (matches(line, VALID_AFTER, i)) {
+ if (validAfter != null) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersMultiple,
+ VALID_AFTER));
+ }
+ i += VALID_AFTER.length();
+ Dequoted parsed = dequote(line, i);
+ i = parsed.after();
+ validAfter = parseDate(parsed.value());
+ } else if (matches(line, VALID_BEFORE, i)) {
+ if (validBefore != null) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersMultiple,
+ VALID_BEFORE));
+ }
+ i += VALID_BEFORE.length();
+ Dequoted parsed = dequote(line, i);
+ i = parsed.after();
+ validBefore = parseDate(parsed.value());
+ } else {
+ break;
+ }
+ }
+ // Now we should be at the key
+ String key = parsePublicKey(line, i);
+ return new AllowedEntry(identities, isCA,
+ namespaces == null ? null : namespaces.toArray(new String[0]),
+ validAfter, validBefore, key);
+ }
+
+ static String parsePublicKey(String s, int from)
+ throws StreamCorruptedException {
+ int i = from;
+ int length = s.length();
+ while (i < length && Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ if (i >= length) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersPublicKeyParsing,
+ s.substring(from)));
+ }
+ int start = i;
+ while (i < length && !Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ if (i >= length) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersPublicKeyParsing,
+ s.substring(start)));
+ }
+ int endOfKeyType = i;
+ i = endOfKeyType + 1;
+ while (i < length && Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ int startOfKey = i;
+ while (i < length && !Character.isWhitespace(s.charAt(i))) {
+ i++;
+ }
+ if (i == startOfKey) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersPublicKeyParsing,
+ s.substring(start)));
+ }
+ String keyType = s.substring(start, endOfKeyType);
+ if (!keyType.startsWith(SSH_KEY_PREFIX)) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signAllowedSignersPublicKeyParsing,
+ s.substring(start)));
+ }
+ return keyType + ' ' + s.substring(startOfKey, i);
+ }
+
+ static Instant parseDate(String input) {
+ // Allowed formats are YYYYMMDD[Z] or YYYYMMDDHHMM[SS][Z]. If 'Z', it's
+ // UTC, otherwise local time.
+ String timeSpec = input;
+ int length = input.length();
+ if (length < 8) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ SshdText.get().signAllowedSignersInvalidDate, input));
+ }
+ boolean isUTC = false;
+ if (timeSpec.charAt(length - 1) == 'Z') {
+ isUTC = true;
+ timeSpec = timeSpec.substring(0, length - 1);
+ }
+ LocalDateTime time;
+ TemporalAccessor temporalAccessor = SSH_DATE_FORMAT.parseBest(timeSpec,
+ LocalDateTime::from, LocalDate::from);
+ if (temporalAccessor instanceof LocalDateTime) {
+ time = (LocalDateTime) temporalAccessor;
+ } else {
+ time = ((LocalDate) temporalAccessor).atStartOfDay();
+ }
+ 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();
+ }
+
+ // OpenSSH uses the backslash *only* to quote the double-quote.
+ static Dequoted dequote(String line, int from) {
+ int length = line.length();
+ int i = from;
+ if (line.charAt(i) == '"') {
+ boolean quoted = false;
+ i++;
+ StringBuilder b = new StringBuilder();
+ while (i < length) {
+ char ch = line.charAt(i);
+ if (ch == '"') {
+ if (quoted) {
+ b.append(ch);
+ quoted = false;
+ } else {
+ break;
+ }
+ } else if (ch == '\\') {
+ quoted = true;
+ } else {
+ if (quoted) {
+ b.append('\\');
+ }
+ b.append(ch);
+ quoted = false;
+ }
+ i++;
+ }
+ if (i >= length) {
+ throw new IllegalArgumentException(
+ SshdText.get().signAllowedSignersUnterminatedQuote);
+ }
+ return new Dequoted(b.toString(), i + 1);
+ }
+ while (i < length && !Character.isWhitespace(line.charAt(i))) {
+ i++;
+ }
+ return new Dequoted(line.substring(from, i), i);
+ }
+
+ static record Dequoted(String value, int after) {
+ // Empty
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java
new file mode 100644
index 0000000..6b19eb32
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshBinaryKrl.java
@@ -0,0 +1,491 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * An implementation of OpenSSH binary format key revocation lists (KRLs).
+ *
+ * @see <a href=
+ * "https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.krl">PROTOCOL.krl</a>
+ */
+class OpenSshBinaryKrl {
+
+ /**
+ * The "magic" bytes at the start of an OpenSSH binary KRL.
+ */
+ static final byte[] MAGIC = { 'S', 'S', 'H', 'K', 'R', 'L', '\n', 0 };
+
+ private static final int FORMAT_VERSION = 1;
+
+ private static final int SECTION_CERTIFICATES = 1;
+
+ private static final int SECTION_KEY = 2;
+
+ private static final int SECTION_SHA1 = 3;
+
+ private static final int SECTION_SIGNATURE = 4; // Skipped
+
+ private static final int SECTION_SHA256 = 5;
+
+ private static final int SECTION_EXTENSION = 255; // Skipped
+
+ // Certificates
+
+ private static final int CERT_SERIAL_LIST = 0x20;
+
+ private static final int CERT_SERIAL_RANGES = 0x21;
+
+ private static final int CERT_SERIAL_BITS = 0x22;
+
+ private static final int CERT_KEY_IDS = 0x23;
+
+ private static final int CERT_EXTENSIONS = 0x39; // Skipped
+
+ private final Map<Blob, CertificateRevocation> certificates = new HashMap<>();
+
+ private static class CertificateRevocation {
+
+ final SerialRangeSet ranges = new SerialRangeSet();
+
+ final Set<String> keyIds = new HashSet<>();
+ }
+
+ // Plain keys
+
+ /**
+ * A byte array that can be used as a key in a {@link Map} or {@link Set}.
+ * {@link #equals(Object)} and {@link #hashCode()} are based on the content.
+ *
+ * @param blob
+ * the array to wrap
+ */
+ @SuppressWarnings("ArrayRecordComponent")
+ private static record Blob(byte[] blob) {
+
+ @Override
+ public final boolean equals(Object any) {
+ if (this == any) {
+ return true;
+ }
+ if (any == null || !(any instanceof Blob)) {
+ return false;
+ }
+ Blob other = (Blob) any;
+ return Arrays.equals(blob, other.blob);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Arrays.hashCode(blob);
+ }
+ }
+
+ private final Set<Blob> blobs = new HashSet<>();
+
+ private final Set<Blob> sha1 = new HashSet<>();
+
+ private final Set<Blob> sha256 = new HashSet<>();
+
+ private OpenSshBinaryKrl() {
+ // No public instantiation, use load(InputStream, boolean) instead.
+ }
+
+ /**
+ * Tells whether the given key has been revoked.
+ *
+ * @param key
+ * {@link PublicKey} to check
+ * @return {@code true} if the key was revoked, {@code false} otherwise
+ */
+ boolean isRevoked(PublicKey key) {
+ if (key instanceof OpenSshCertificate certificate) {
+ if (certificates.isEmpty()) {
+ return false;
+ }
+ // These apply to all certificates
+ if (isRevoked(certificate, certificates.get(null))) {
+ return true;
+ }
+ if (isRevoked(certificate,
+ certificates.get(blob(certificate.getCaPubKey())))) {
+ return true;
+ }
+ // Keys themselves are checked in OpenSshKrl.
+ return false;
+ }
+ if (!blobs.isEmpty() && blobs.contains(blob(key))) {
+ return true;
+ }
+ if (!sha256.isEmpty() && sha256.contains(hash("SHA256", key))) { //$NON-NLS-1$
+ return true;
+ }
+ if (!sha1.isEmpty() && sha1.contains(hash("SHA1", key))) { //$NON-NLS-1$
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isRevoked(OpenSshCertificate certificate,
+ CertificateRevocation revocations) {
+ if (revocations == null) {
+ return false;
+ }
+ String id = certificate.getId();
+ if (!StringUtils.isEmptyOrNull(id) && revocations.keyIds.contains(id)) {
+ return true;
+ }
+ long serial = certificate.getSerial();
+ if (serial != 0 && revocations.ranges.contains(serial)) {
+ return true;
+ }
+ return false;
+ }
+
+ private Blob blob(PublicKey key) {
+ ByteArrayBuffer buf = new ByteArrayBuffer();
+ buf.putRawPublicKey(key);
+ return new Blob(buf.getCompactData());
+ }
+
+ private Blob hash(String algorithm, PublicKey key) {
+ ByteArrayBuffer buf = new ByteArrayBuffer();
+ buf.putRawPublicKey(key);
+ try {
+ return new Blob(MessageDigest.getInstance(algorithm)
+ .digest(buf.getCompactData()));
+ } catch (NoSuchAlgorithmException e) {
+ throw new JGitInternalException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Loads a binary KRL from the given stream.
+ *
+ * @param in
+ * {@link InputStream} to read from
+ * @param magicSkipped
+ * whether the {@link #MAGIC} bytes at the beginning have already
+ * been skipped
+ * @return a new {@link OpenSshBinaryKrl}.
+ * @throws IOException
+ * if the stream cannot be read as an OpenSSH binary KRL
+ */
+ @NonNull
+ static OpenSshBinaryKrl load(InputStream in, boolean magicSkipped)
+ throws IOException {
+ if (!magicSkipped) {
+ byte[] magic = new byte[MAGIC.length];
+ IO.readFully(in, magic);
+ if (!Arrays.equals(magic, MAGIC)) {
+ throw new StreamCorruptedException(
+ SshdText.get().signKrlInvalidMagic);
+ }
+ }
+ skipHeader(in);
+ return load(in);
+ }
+
+ private static long getUInt(InputStream in) throws IOException {
+ byte[] buf = new byte[Integer.BYTES];
+ IO.readFully(in, buf);
+ return BufferUtils.getUInt(buf);
+ }
+
+ private static long getLong(InputStream in) throws IOException {
+ byte[] buf = new byte[Long.BYTES];
+ IO.readFully(in, buf);
+ return BufferUtils.getLong(buf, 0, Long.BYTES);
+ }
+
+ private static void skipHeader(InputStream in) throws IOException {
+ long version = getUInt(in);
+ if (version != FORMAT_VERSION) {
+ throw new StreamCorruptedException(
+ MessageFormat.format(SshdText.get().signKrlInvalidVersion,
+ Long.valueOf(version)));
+ }
+ // krl_version, generated_date, flags (none defined in version 1)
+ in.skip(24);
+ in.skip(getUInt(in)); // reserved
+ in.skip(getUInt(in)); // comment
+ }
+
+ private static OpenSshBinaryKrl load(InputStream in) throws IOException {
+ OpenSshBinaryKrl krl = new OpenSshBinaryKrl();
+ for (;;) {
+ int sectionType = in.read();
+ if (sectionType < 0) {
+ break; // EOF
+ }
+ switch (sectionType) {
+ case SECTION_CERTIFICATES:
+ readCertificates(krl.certificates, in, getUInt(in));
+ break;
+ case SECTION_KEY:
+ readBlobs("explicit_keys", krl.blobs, in, getUInt(in), 0); //$NON-NLS-1$
+ break;
+ case SECTION_SHA1:
+ readBlobs("fingerprint_sha1", krl.sha1, in, getUInt(in), 20); //$NON-NLS-1$
+ break;
+ case SECTION_SIGNATURE:
+ // Unsupported as of OpenSSH 9.4. It even refuses to load such
+ // KRLs. Just skip it.
+ in.skip(getUInt(in));
+ break;
+ case SECTION_SHA256:
+ readBlobs("fingerprint_sha256", krl.sha256, in, getUInt(in), //$NON-NLS-1$
+ 32);
+ break;
+ case SECTION_EXTENSION:
+ // No extensions are defined for version 1 KRLs.
+ in.skip(getUInt(in));
+ break;
+ default:
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlUnknownSection,
+ Integer.valueOf(sectionType)));
+ }
+ }
+ return krl;
+ }
+
+ private static void readBlobs(String sectionName, Set<Blob> blobs,
+ InputStream in, long sectionLength, long expectedBlobLength)
+ throws IOException {
+ while (sectionLength >= Integer.BYTES) {
+ // Read blobs.
+ long blobLength = getUInt(in);
+ sectionLength -= Integer.BYTES;
+ if (blobLength > sectionLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlBlobLengthInvalid, sectionName,
+ Long.valueOf(blobLength)));
+ }
+ if (expectedBlobLength != 0 && blobLength != expectedBlobLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlBlobLengthInvalidExpected,
+ sectionName, Long.valueOf(blobLength),
+ Long.valueOf(expectedBlobLength)));
+ }
+ byte[] blob = new byte[(int) blobLength];
+ IO.readFully(in, blob);
+ sectionLength -= blobLength;
+ blobs.add(new Blob(blob));
+ }
+ if (sectionLength != 0) {
+ throw new StreamCorruptedException(
+ MessageFormat.format(SshdText.get().signKrlBlobLeftover,
+ sectionName, Long.valueOf(sectionLength)));
+ }
+ }
+
+ private static void readCertificates(Map<Blob, CertificateRevocation> certs,
+ InputStream in, long sectionLength) throws IOException {
+ long keyLength = getUInt(in);
+ sectionLength -= Integer.BYTES;
+ if (keyLength > sectionLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCaKeyLengthInvalid,
+ Long.valueOf(keyLength)));
+ }
+ Blob key = null;
+ if (keyLength > 0) {
+ byte[] blob = new byte[(int) keyLength];
+ IO.readFully(in, blob);
+ key = new Blob(blob);
+ sectionLength -= keyLength;
+ }
+ CertificateRevocation rev = certs.computeIfAbsent(key,
+ k -> new CertificateRevocation());
+ long reservedLength = getUInt(in);
+ sectionLength -= Integer.BYTES;
+ if (reservedLength > sectionLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCaKeyLengthInvalid,
+ Long.valueOf(reservedLength)));
+ }
+ in.skip(reservedLength);
+ sectionLength -= reservedLength;
+ if (sectionLength == 0) {
+ throw new StreamCorruptedException(
+ SshdText.get().signKrlNoCertificateSubsection);
+ }
+ while (sectionLength > 0) {
+ int subSection = in.read();
+ if (subSection < 0) {
+ throw new EOFException();
+ }
+ sectionLength--;
+ if (sectionLength < Integer.BYTES) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateLeftover,
+ Long.valueOf(sectionLength)));
+ }
+ long subLength = getUInt(in);
+ sectionLength -= Integer.BYTES;
+ if (subLength > sectionLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateSubsectionLength,
+ Long.valueOf(subLength)));
+ }
+ if (subLength > 0) {
+ switch (subSection) {
+ case CERT_SERIAL_LIST:
+ readSerials(rev.ranges, in, subLength, false);
+ break;
+ case CERT_SERIAL_RANGES:
+ readSerials(rev.ranges, in, subLength, true);
+ break;
+ case CERT_SERIAL_BITS:
+ readSerialBitSet(rev.ranges, in, subLength);
+ break;
+ case CERT_KEY_IDS:
+ readIds(rev.keyIds, in, subLength);
+ break;
+ case CERT_EXTENSIONS:
+ in.skip(subLength);
+ break;
+ default:
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlUnknownSubsection,
+ Long.valueOf(subSection)));
+ }
+ }
+ sectionLength -= subLength;
+ }
+ }
+
+ private static void readSerials(SerialRangeSet set, InputStream in,
+ long length, boolean ranges) throws IOException {
+ while (length >= Long.BYTES) {
+ long a = getLong(in);
+ length -= Long.BYTES;
+ if (a == 0) {
+ throw new StreamCorruptedException(
+ SshdText.get().signKrlSerialZero);
+ }
+ if (!ranges) {
+ set.add(a);
+ continue;
+ }
+ if (length < Long.BYTES) {
+ throw new StreamCorruptedException(
+ MessageFormat.format(SshdText.get().signKrlShortRange,
+ Long.valueOf(length)));
+ }
+ long b = getLong(in);
+ length -= Long.BYTES;
+ if (Long.compareUnsigned(a, b) > 0) {
+ throw new StreamCorruptedException(
+ SshdText.get().signKrlEmptyRange);
+ }
+ set.add(a, b);
+ }
+ if (length != 0) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateSubsectionLeftover,
+ Long.valueOf(length)));
+ }
+ }
+
+ private static void readSerialBitSet(SerialRangeSet set, InputStream in,
+ long subLength) throws IOException {
+ while (subLength > 0) {
+ if (subLength < Long.BYTES) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateSubsectionLeftover,
+ Long.valueOf(subLength)));
+ }
+ long base = getLong(in);
+ subLength -= Long.BYTES;
+ if (subLength < Integer.BYTES) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateSubsectionLeftover,
+ Long.valueOf(subLength)));
+ }
+ long setLength = getUInt(in);
+ subLength -= Integer.BYTES;
+ if (setLength == 0 || setLength > subLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlInvalidBitSetLength,
+ Long.valueOf(setLength)));
+ }
+ // Now process the bits. Note that the mpint is stored MSB first.
+ //
+ // We set individual serial numbers (one for each set bit) and let
+ // the SerialRangeSet take care of coalescing for successive runs
+ // of set bits.
+ int n = (int) setLength;
+ for (int i = n - 1; i >= 0; i--) {
+ int b = in.read();
+ if (b < 0) {
+ throw new EOFException();
+ } else if (b == 0) {
+ // Stored as an mpint: may have leading zero bytes (actually
+ // at most one; if the high bit of the first byte is set).
+ continue;
+ }
+ for (int bit = 0,
+ mask = 1; bit < Byte.SIZE; bit++, mask <<= 1) {
+ if ((b & mask) != 0) {
+ set.add(base + (i * Byte.SIZE) + bit);
+ }
+ }
+ }
+ subLength -= setLength;
+ }
+ }
+
+ private static void readIds(Set<String> ids, InputStream in, long subLength)
+ throws IOException {
+ while (subLength >= Integer.BYTES) {
+ long length = getUInt(in);
+ subLength -= Integer.BYTES;
+ if (length > subLength) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlInvalidKeyIdLength,
+ Long.valueOf(length)));
+ }
+ byte[] bytes = new byte[(int) length];
+ IO.readFully(in, bytes);
+ ids.add(new String(bytes, StandardCharsets.UTF_8));
+ subLength -= length;
+ }
+ if (subLength != 0) {
+ throw new StreamCorruptedException(MessageFormat.format(
+ SshdText.get().signKrlCertificateSubsectionLeftover,
+ Long.valueOf(subLength)));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java
new file mode 100644
index 0000000..7993def
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshKrl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.util.io.ModifiableFileWatcher;
+import org.eclipse.jgit.util.IO;
+
+/**
+ * An implementation of an OpenSSH key revocation list (KRL), either a binary
+ * KRL or a simple list of public keys.
+ */
+class OpenSshKrl extends ModifiableFileWatcher {
+
+ private static record State(Set<String> keys, OpenSshBinaryKrl krl) {
+ // Empty
+ }
+
+ private State state;
+
+ public OpenSshKrl(Path path) {
+ super(path);
+ state = new State(Set.of(), null);
+ }
+
+ public boolean isRevoked(PublicKey key) throws IOException {
+ State current = refresh();
+ return isRevoked(current, key);
+ }
+
+ private boolean isRevoked(State current, PublicKey key) {
+ if (key instanceof OpenSshCertificate cert) {
+ OpenSshBinaryKrl krl = current.krl();
+ if (krl != null && krl.isRevoked(cert)) {
+ return true;
+ }
+ if (isRevoked(current, cert.getCaPubKey())
+ || isRevoked(current, cert.getCertPubKey())) {
+ return true;
+ }
+ return false;
+ }
+ OpenSshBinaryKrl krl = current.krl();
+ if (krl != null) {
+ return krl.isRevoked(key);
+ }
+ return current.keys().contains(PublicKeyEntry.toString(key));
+ }
+
+ private synchronized State refresh() throws IOException {
+ if (checkReloadRequired()) {
+ updateReloadAttributes();
+ try {
+ state = reload(getPath());
+ } catch (NoSuchFileException e) {
+ // File disappeared
+ resetReloadAttributes();
+ state = new State(Set.of(), null);
+ }
+ }
+ return state;
+ }
+
+ private static State reload(Path path) throws IOException {
+ try (BufferedInputStream in = new BufferedInputStream(
+ Files.newInputStream(path))) {
+ byte[] magic = new byte[OpenSshBinaryKrl.MAGIC.length];
+ in.mark(magic.length);
+ IO.readFully(in, magic);
+ if (Arrays.equals(magic, OpenSshBinaryKrl.MAGIC)) {
+ return new State(null, OpenSshBinaryKrl.load(in, true));
+ }
+ // Otherwise try reading it textually
+ in.reset();
+ return loadTextKrl(in);
+ }
+ }
+
+ private static State loadTextKrl(InputStream in) throws IOException {
+ Set<String> keys = new HashSet<>();
+ try (BufferedReader r = new BufferedReader(
+ new InputStreamReader(in, StandardCharsets.UTF_8))) {
+ String line;
+ for (;;) {
+ line = r.readLine();
+ if (line == null) {
+ break;
+ }
+ line = line.strip();
+ if (line.isEmpty() || line.charAt(0) == '#') {
+ continue;
+ }
+ keys.add(AllowedSigners.parsePublicKey(line, 0));
+ }
+ }
+ return new State(keys, null);
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java
new file mode 100644
index 0000000..aa26886
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/OpenSshSigningKeyDatabase.java
@@ -0,0 +1,161 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * A {@link CachingSigningKeyDatabase} using the OpenSSH allowed signers file
+ * and the OpenSSH key revocation list.
+ */
+public class OpenSshSigningKeyDatabase implements CachingSigningKeyDatabase {
+
+ // Keep caches of allowed signers and KRLs. Cache by canonical path.
+
+ private static final int DEFAULT_CACHE_SIZE = 5;
+
+ private AtomicInteger cacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
+
+ private class LRU<K, V> extends LinkedHashMap<K, V> {
+
+ private static final long serialVersionUID = 1L;
+
+ LRU() {
+ super(DEFAULT_CACHE_SIZE, 0.75f, true);
+ }
+
+ @Override
+ protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+ return size() > cacheSize.get();
+ }
+ }
+
+ private final HashMap<Path, AllowedSigners> allowedSigners = new LRU<>();
+
+ private final HashMap<Path, OpenSshKrl> revocations = new LRU<>();
+
+ @Override
+ public boolean isRevoked(Repository repository, GpgConfig config,
+ PublicKey key) throws IOException {
+ String fileName = config.getSshRevocationFile();
+ if (StringUtils.isEmptyOrNull(fileName)) {
+ return false;
+ }
+ File file = getFile(repository, fileName);
+ OpenSshKrl revocationList;
+ synchronized (revocations) {
+ revocationList = revocations.computeIfAbsent(file.toPath(),
+ OpenSshKrl::new);
+ }
+ return revocationList.isRevoked(key);
+ }
+
+ @Override
+ public String isAllowed(Repository repository, GpgConfig config,
+ PublicKey key, String namespace, PersonIdent ident)
+ throws IOException, VerificationException {
+ String fileName = config.getSshAllowedSignersFile();
+ if (StringUtils.isEmptyOrNull(fileName)) {
+ // No file configured. Git would error out.
+ return null;
+ }
+ File file = getFile(repository, fileName);
+ AllowedSigners allowed;
+ synchronized (allowedSigners) {
+ allowed = allowedSigners.computeIfAbsent(file.toPath(),
+ AllowedSigners::new);
+ }
+ Instant gitTime = null;
+ if (ident != null) {
+ gitTime = ident.getWhenAsInstant();
+ }
+ return allowed.isAllowed(key, namespace, null, gitTime);
+ }
+
+ private File getFile(@NonNull Repository repository, String fileName)
+ throws IOException {
+ File file;
+ if (fileName.startsWith("~/") //$NON-NLS-1$
+ || fileName.startsWith('~' + File.separator)) {
+ file = FS.DETECTED.resolve(FS.DETECTED.userHome(),
+ fileName.substring(2));
+ } else {
+ file = new File(fileName);
+ if (!file.isAbsolute()) {
+ file = new File(repository.getWorkTree(), fileName);
+ }
+ }
+ return file.getCanonicalFile();
+ }
+
+ @Override
+ public int getCacheSize() {
+ return cacheSize.get();
+ }
+
+ @Override
+ public void setCacheSize(int size) {
+ if (size > 0) {
+ cacheSize.set(size);
+ pruneCache(size);
+ }
+ }
+
+ private void pruneCache(int size) {
+ prune(allowedSigners, size);
+ prune(revocations, size);
+ }
+
+ private void prune(HashMap<?, ?> map, int size) {
+ synchronized (map) {
+ if (map.size() <= size) {
+ return;
+ }
+ Iterator<?> iter = map.entrySet().iterator();
+ int i = 0;
+ while (iter.hasNext() && i < size) {
+ iter.next();
+ i++;
+ }
+ while (iter.hasNext()) {
+ iter.next();
+ iter.remove();
+ }
+ }
+ }
+
+ @Override
+ public void clearCache() {
+ synchronized (allowedSigners) {
+ allowedSigners.clear();
+ }
+ synchronized (revocations) {
+ revocations.clear();
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java
new file mode 100644
index 0000000..f4eb884
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SerialRangeSet.java
@@ -0,0 +1,131 @@
+/*
+ * 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.signing.ssh;
+
+import java.util.TreeMap;
+
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+
+/**
+ * Encapsulates the storage for revoked certificate serial numbers.
+ */
+class SerialRangeSet {
+
+ /**
+ * A range of certificate serial numbers [from..to], i.e., with both range
+ * limits included.
+ */
+ private interface SerialRange {
+
+ long from();
+
+ long to();
+ }
+
+ private static record Singleton(long from) implements SerialRange {
+
+ @Override
+ public long to() {
+ return from;
+ }
+ }
+
+ private static record Range(long from, long to) implements SerialRange {
+
+ public Range(long from, long to) {
+ if (Long.compareUnsigned(from, to) > 0) {
+ throw new IllegalArgumentException(
+ SshdText.get().signKrlEmptyRange);
+ }
+ this.from = from;
+ this.to = to;
+ }
+ }
+
+ // We use the same data structure as OpenSSH; basically a TreeSet of mutable
+ // SerialRanges. To get "mutability", the set is implemented as a TreeMap
+ // with the same elements as keys and values.
+ //
+ // get(x) will return null if none of the serial numbers in the range x is
+ // in the set, and some range (partially) overlapping with x otherwise.
+ //
+ // containsKey(x) will return true if there is any (partially) overlapping
+ // range in the TreeMap.
+ private final TreeMap<SerialRange, SerialRange> ranges = new TreeMap<>(
+ SerialRangeSet::compare);
+
+ private static int compare(SerialRange a, SerialRange b) {
+ // Return == if they overlap
+ if (Long.compareUnsigned(a.to(), b.from()) >= 0
+ && Long.compareUnsigned(a.from(), b.to()) <= 0) {
+ return 0;
+ }
+ return Long.compareUnsigned(a.from(), b.from());
+ }
+
+ void add(long serial) {
+ add(ranges, new Singleton(serial));
+ }
+
+ void add(long from, long to) {
+ add(ranges, new Range(from, to));
+ }
+
+ boolean contains(long serial) {
+ return ranges.containsKey(new Singleton(serial));
+ }
+
+ int size() {
+ return ranges.size();
+ }
+
+ boolean isEmpty() {
+ return ranges.isEmpty();
+ }
+
+ private static void add(TreeMap<SerialRange, SerialRange> ranges,
+ SerialRange newRange) {
+ for (;;) {
+ SerialRange existing = ranges.get(newRange);
+ if (existing == null) {
+ break;
+ }
+ if (Long.compareUnsigned(existing.from(), newRange.from()) <= 0
+ && Long.compareUnsigned(existing.to(),
+ newRange.to()) >= 0) {
+ // newRange completely contained in existing
+ return;
+ }
+ ranges.remove(existing);
+ long newFrom = newRange.from();
+ if (Long.compareUnsigned(existing.from(), newFrom) < 0) {
+ newFrom = existing.from();
+ }
+ long newTo = newRange.to();
+ if (Long.compareUnsigned(existing.to(), newTo) > 0) {
+ newTo = existing.to();
+ }
+ newRange = new Range(newFrom, newTo);
+ }
+ // No overlapping range exists: check for coalescing with the
+ // previous/next range
+ SerialRange prev = ranges.floorKey(newRange);
+ if (prev != null && newRange.from() - prev.to() == 1) {
+ ranges.remove(prev);
+ newRange = new Range(prev.from(), newRange.to());
+ }
+ SerialRange next = ranges.ceilingKey(newRange);
+ if (next != null && next.from() - newRange.to() == 1) {
+ ranges.remove(next);
+ newRange = new Range(newRange.from(), next.to());
+ }
+ ranges.put(newRange, newRange);
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java
new file mode 100644
index 0000000..e2e1a36
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SigningDatabase.java
@@ -0,0 +1,59 @@
+/*
+ * 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.signing.ssh;
+
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.SigningKeyDatabase;
+
+/**
+ * A global {@link SigningKeyDatabase} instance.
+ */
+public final class SigningDatabase {
+
+ private static SigningKeyDatabase INSTANCE = new OpenSshSigningKeyDatabase();
+
+ private SigningDatabase() {
+ // No instantiation
+ }
+
+ /**
+ * Obtains the current instance.
+ *
+ * @return the global {@link SigningKeyDatabase}
+ */
+ public static synchronized SigningKeyDatabase getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Sets the global {@link SigningKeyDatabase}.
+ *
+ * @param database
+ * to set; if {@code null} a default database using the OpenSSH
+ * allowed signers file and the OpenSSH revocation list mechanism
+ * is used.
+ * @return the previously set {@link SigningKeyDatabase}
+ */
+ public static synchronized SigningKeyDatabase setInstance(
+ SigningKeyDatabase database) {
+ SigningKeyDatabase previous = INSTANCE;
+ if (database != INSTANCE) {
+ if (INSTANCE instanceof CachingSigningKeyDatabase caching) {
+ caching.clearCache();
+ }
+ if (database == null) {
+ INSTANCE = new OpenSshSigningKeyDatabase();
+ } else {
+ INSTANCE = database;
+ }
+ }
+ return previous;
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtils.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtils.java
new file mode 100644
index 0000000..040c6d4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshCertificateUtils.java
@@ -0,0 +1,175 @@
+/*
+ * 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.signing.ssh;
+
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.time.Instant;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods for working with OpenSSH certificates.
+ */
+final class SshCertificateUtils {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(SshCertificateUtils.class);
+
+ /**
+ * Verifies a certificate: checks that it is a user certificate and has a
+ * valid signature, and if a time is given, that the certificate is valid at
+ * that time.
+ *
+ * @param certificate
+ * {@link OpenSshCertificate} to verify
+ * @param signatureTime
+ * {@link Instant} to check whether the certificate is valid at
+ * that time; maybe {@code null}, in which case the valid-time
+ * check is skipped.
+ * @return {@code null} if the certificate is valid; otherwise a descriptive
+ * message
+ */
+ static String verify(OpenSshCertificate certificate,
+ Instant signatureTime) {
+ if (!OpenSshCertificate.Type.USER.equals(certificate.getType())) {
+ return MessageFormat.format(SshdText.get().signNotUserCertificate,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()));
+ }
+ String message = verifySignature(certificate);
+ if (message == null && signatureTime != null) {
+ message = checkExpiration(certificate, signatureTime);
+ }
+ return message;
+ }
+
+ /**
+ * Verifies the signature on a certificate.
+ *
+ * @param certificate
+ * {@link OpenSshCertificate} to verify
+ * @return {@code null} if the signature is valid; otherwise a descriptive
+ * message
+ */
+ static String verifySignature(OpenSshCertificate certificate) {
+ // Verify the signature on the certificate.
+ //
+ // Note that OpenSSH certificates do not support chaining.
+ //
+ // ssh-keygen refuses to create a certificate for a certificate, so the
+ // certified key cannot be another OpenSshCertificate. Additionally,
+ // when creating a certificate ssh-keygen loads the CA private key to
+ // make the signature and reconstructs the public key that it stores in
+ // the certificate from that, so the CA public key also cannot be an
+ // OpenSshCertificate.
+ PublicKey caKey = certificate.getCaPubKey();
+ PublicKey certifiedKey = certificate.getCertPubKey();
+ if (caKey == null
+ || caKey instanceof OpenSshCertificate
+ || certifiedKey == null
+ || certifiedKey instanceof OpenSshCertificate) {
+ return SshdText.get().signCertificateInvalid;
+ }
+ // Verify that key type and algorithm match
+ String keyType = KeyUtils.getKeyType(caKey);
+ String certAlgorithm = certificate.getSignatureAlgorithm();
+ if (!KeyUtils.getCanonicalKeyType(keyType)
+ .equals(KeyUtils.getCanonicalKeyType(certAlgorithm))) {
+ return MessageFormat.format(
+ SshdText.get().signCertAlgorithmMismatch, keyType,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()),
+ certAlgorithm);
+ }
+ BuiltinSignatures factory = BuiltinSignatures
+ .fromFactoryName(certAlgorithm);
+ if (factory == null || !factory.isSupported()) {
+ return MessageFormat.format(SshdText.get().signCertAlgorithmUnknown,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()),
+ certAlgorithm);
+ }
+ Signature signer = factory.create();
+ try {
+ signer.initVerifier(null, caKey);
+ signer.update(null, getBlob(certificate));
+ if (signer.verify(null, certificate.getRawSignature())) {
+ return null;
+ }
+ } catch (Exception e) {
+ LOG.warn("{}", SshdText.get().signLogFailure, e); //$NON-NLS-1$
+ return SshdText.get().signSeeLog;
+ }
+ return MessageFormat.format(SshdText.get().signCertificateInvalid,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()));
+ }
+
+ private static byte[] getBlob(OpenSshCertificate certificate) {
+ // Theoretically, this should be just certificate.getMessage(). But
+ // Apache MINA sshd has a bug and may return additional bytes if the
+ // certificate is not the first thing in the buffer it was read from.
+ // As a work-around, re-create the signed blob from scratch.
+ //
+ // This may be replaced by return certificate.getMessage() once the
+ // upstream bug is fixed.
+ //
+ // See https://github.com/apache/mina-sshd/issues/618
+ Buffer tmp = new ByteArrayBuffer();
+ tmp.putString(certificate.getKeyType());
+ tmp.putBytes(certificate.getNonce());
+ tmp.putRawPublicKeyBytes(certificate.getCertPubKey());
+ tmp.putLong(certificate.getSerial());
+ tmp.putInt(certificate.getType().getCode());
+ tmp.putString(certificate.getId());
+ Buffer list = new ByteArrayBuffer();
+ list.putStringList(certificate.getPrincipals(), false);
+ tmp.putBytes(list.getCompactData());
+ tmp.putLong(certificate.getValidAfter());
+ tmp.putLong(certificate.getValidBefore());
+ tmp.putCertificateOptions(certificate.getCriticalOptions());
+ tmp.putCertificateOptions(certificate.getExtensions());
+ tmp.putString(certificate.getReserved());
+ Buffer inner = new ByteArrayBuffer();
+ inner.putRawPublicKey(certificate.getCaPubKey());
+ tmp.putBytes(inner.getCompactData());
+ return tmp.getCompactData();
+ }
+
+ /**
+ * Checks whether a certificate is valid at a given time.
+ *
+ * @param certificate
+ * {@link OpenSshCertificate} to check
+ * @param signatureTime
+ * {@link Instant} to check
+ * @return {@code null} if the certificate is valid at the given instant;
+ * otherwise a descriptive message
+ */
+ static String checkExpiration(OpenSshCertificate certificate,
+ @NonNull Instant signatureTime) {
+ long instant = signatureTime.getEpochSecond();
+ if (Long.compareUnsigned(instant, certificate.getValidAfter()) < 0) {
+ return MessageFormat.format(SshdText.get().signCertificateTooEarly,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()));
+ } else if (Long.compareUnsigned(instant,
+ certificate.getValidBefore()) > 0) {
+ return MessageFormat.format(SshdText.get().signCertificateExpired,
+ KeyUtils.getFingerPrint(certificate.getCaPubKey()));
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureConstants.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureConstants.java
new file mode 100644
index 0000000..bc72196
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureConstants.java
@@ -0,0 +1,38 @@
+/*
+ * 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.signing.ssh;
+
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jgit.lib.Constants;
+
+/**
+ * Defines common constants for SSH signatures.
+ */
+final class SshSignatureConstants {
+
+ private static final String SIGNATURE_END = "-----END SSH SIGNATURE-----"; //$NON-NLS-1$
+
+ static final byte[] MAGIC = { 'S', 'S', 'H', 'S', 'I', 'G' };
+
+ static final int VERSION = 1;
+
+ static final String NAMESPACE = "git"; //$NON-NLS-1$
+
+ static final byte[] ARMOR_HEAD = Constants.SSH_SIGNATURE_PREFIX
+ .getBytes(StandardCharsets.US_ASCII);
+
+ static final byte[] ARMOR_END = SIGNATURE_END
+ .getBytes(StandardCharsets.US_ASCII);
+
+ private SshSignatureConstants() {
+ // No instantiation
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java
new file mode 100644
index 0000000..76be340
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSignatureVerifier.java
@@ -0,0 +1,319 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.signing.ssh.CachingSigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.SigningKeyDatabase;
+import org.eclipse.jgit.signing.ssh.VerificationException;
+import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link SignatureVerifier} for SSH signatures.
+ */
+public class SshSignatureVerifier implements SignatureVerifier {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(SshSignatureVerifier.class);
+
+ private static final byte[] OBJECT = { 'o', 'b', 'j', 'e', 'c', 't', ' ' };
+
+ private static final byte[] TREE = { 't', 'r', 'e', 'e', ' ' };
+
+ private static final byte[] TYPE = { 't', 'y', 'p', 'e', ' ' };
+
+ @Override
+ public String getName() {
+ return "ssh"; //$NON-NLS-1$
+ }
+
+ @Override
+ public SignatureVerification verify(Repository repository, GpgConfig config,
+ byte[] data, byte[] signatureData) throws IOException {
+ // This is a bit stupid. SSH signatures do not store a signer, nor a
+ // time the signature was created. So we must use the committer's or
+ // tagger's PersonIdent, but here we have neither. But... if we see
+ // that the data is a commit or tag, then we can parse the PersonIdent
+ // from the data.
+ //
+ // Note: we cannot assume that absent a principal recorded in the
+ // allowedSignersFile or on a certificate that the key used to sign the
+ // commit belonged to the committer.
+ PersonIdent gitIdentity = getGitIdentity(data);
+ Date signatureDate = null;
+ Instant signatureInstant = null;
+ if (gitIdentity != null) {
+ signatureDate = gitIdentity.getWhen();
+ signatureInstant = gitIdentity.getWhenAsInstant();
+ }
+
+ TrustLevel trust = TrustLevel.NEVER;
+ byte[] decodedSignature;
+ try {
+ decodedSignature = dearmor(signatureData);
+ } catch (IllegalArgumentException e) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ null, null, false, false, trust,
+ MessageFormat.format(SshdText.get().signInvalidSignature,
+ e.getLocalizedMessage()));
+ }
+ int start = RawParseUtils.match(decodedSignature, 0,
+ SshSignatureConstants.MAGIC);
+ if (start < 0) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ null, null, false, false, trust,
+ SshdText.get().signInvalidMagic);
+ }
+ ByteArrayBuffer signature = new ByteArrayBuffer(decodedSignature, start,
+ decodedSignature.length - start);
+
+ long version = signature.getUInt();
+ if (version != SshSignatureConstants.VERSION) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ null, null, false, false, trust,
+ MessageFormat.format(SshdText.get().signInvalidVersion,
+ Long.toString(version)));
+ }
+
+ PublicKey key = signature.getPublicKey();
+ String fingerprint;
+ if (key instanceof OpenSshCertificate cert) {
+ fingerprint = KeyUtils.getFingerPrint(cert.getCertPubKey());
+ String message = SshCertificateUtils.verify(cert, signatureInstant);
+ if (message != null) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust, message);
+ }
+ } else {
+ fingerprint = KeyUtils.getFingerPrint(key);
+ }
+
+ String namespace = signature.getString();
+ if (!SshSignatureConstants.NAMESPACE.equals(namespace)) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ MessageFormat.format(SshdText.get().signInvalidNamespace,
+ namespace));
+ }
+
+ signature.getString(); // Skip the reserved field
+ String hashAlgorithm = signature.getString();
+ byte[] hash;
+ try {
+ hash = MessageDigest
+ .getInstance(hashAlgorithm.toUpperCase(Locale.ROOT))
+ .digest(data);
+ } catch (NoSuchAlgorithmException e) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ MessageFormat.format(
+ SshdText.get().signUnknownHashAlgorithm,
+ hashAlgorithm));
+ }
+ ByteArrayBuffer rawSignature = new ByteArrayBuffer(
+ signature.getBytes());
+ if (signature.available() > 0) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ SshdText.get().signGarbageAtEnd);
+ }
+
+ String signatureAlgorithm = rawSignature.getString();
+ switch (signatureAlgorithm) {
+ case KeyPairProvider.SSH_DSS:
+ case KeyPairProvider.SSH_DSS_CERT:
+ case KeyPairProvider.SSH_RSA:
+ case KeyPairProvider.SSH_RSA_CERT:
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ MessageFormat.format(SshdText.get().signInvalidAlgorithm,
+ signatureAlgorithm));
+ }
+
+ String keyType = KeyUtils
+ .getSignatureAlgorithm(KeyUtils.getKeyType(key), key);
+ if (!KeyUtils.getCanonicalKeyType(keyType)
+ .equals(KeyUtils.getCanonicalKeyType(signatureAlgorithm))) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ MessageFormat.format(
+ SshdText.get().signMismatchedSignatureAlgorithm,
+ keyType, signatureAlgorithm));
+ }
+
+ BuiltinSignatures factory = BuiltinSignatures
+ .fromFactoryName(signatureAlgorithm);
+ if (factory == null || !factory.isSupported()) {
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, null, false, false, trust,
+ MessageFormat.format(
+ SshdText.get().signUnknownSignatureAlgorithm,
+ signatureAlgorithm));
+ }
+
+ boolean valid;
+ String message = null;
+ try {
+ Signature verifier = factory.create();
+ verifier.initVerifier(null,
+ key instanceof OpenSshCertificate cert
+ ? cert.getCertPubKey()
+ : key);
+ // Feed it the data
+ Buffer toSign = new ByteArrayBuffer();
+ toSign.putRawBytes(SshSignatureConstants.MAGIC);
+ toSign.putString(SshSignatureConstants.NAMESPACE);
+ toSign.putUInt(0); // reserved: zero-length string
+ toSign.putString(hashAlgorithm);
+ toSign.putBytes(hash);
+ verifier.update(null, toSign.getCompactData());
+ valid = verifier.verify(null, rawSignature.getBytes());
+ } catch (Exception e) {
+ LOG.warn("{}", SshdText.get().signLogFailure, e); //$NON-NLS-1$
+ valid = false;
+ message = SshdText.get().signSeeLog;
+ }
+ boolean expired = false;
+ String principal = null;
+ if (valid) {
+ if (rawSignature.available() > 0) {
+ valid = false;
+ message = SshdText.get().signGarbageAtEnd;
+ } else {
+ SigningKeyDatabase database = SigningKeyDatabase.getInstance();
+ if (database.isRevoked(repository, config, key)) {
+ valid = false;
+ if (key instanceof OpenSshCertificate certificate) {
+ message = MessageFormat.format(
+ SshdText.get().signCertificateRevoked,
+ KeyUtils.getFingerPrint(
+ certificate.getCaPubKey()));
+ } else {
+ message = SshdText.get().signKeyRevoked;
+ }
+ } else {
+ // This may turn a positive verification into a failed one.
+ try {
+ principal = database.isAllowed(repository, config, key,
+ SshSignatureConstants.NAMESPACE, gitIdentity);
+ if (!StringUtils.isEmptyOrNull(principal)) {
+ trust = TrustLevel.FULL;
+ } else {
+ valid = false;
+ message = SshdText.get().signNoPrincipalMatched;
+ trust = TrustLevel.UNKNOWN;
+ }
+ } catch (VerificationException e) {
+ valid = false;
+ message = e.getMessage();
+ expired = e.isExpired();
+ } catch (IOException e) {
+ LOG.warn("{}", SshdText.get().signLogFailure, e); //$NON-NLS-1$
+ valid = false;
+ message = SshdText.get().signSeeLog;
+ }
+ }
+ }
+ }
+ return new SignatureVerification(getName(), signatureDate, null,
+ fingerprint, principal, valid, expired, trust, message);
+ }
+
+ private static PersonIdent getGitIdentity(byte[] rawObject) {
+ // Data from a commit will start with "tree ID\n".
+ int i = RawParseUtils.match(rawObject, 0, TREE);
+ if (i > 0) {
+ i = RawParseUtils.committer(rawObject, 0);
+ if (i < 0) {
+ return null;
+ }
+ return RawParseUtils.parsePersonIdent(rawObject, i);
+ }
+ // Data from a tag will start with "object ID\ntype ".
+ i = RawParseUtils.match(rawObject, 0, OBJECT);
+ if (i > 0) {
+ i = RawParseUtils.nextLF(rawObject, i);
+ i = RawParseUtils.match(rawObject, i, TYPE);
+ if (i > 0) {
+ i = RawParseUtils.tagger(rawObject, 0);
+ if (i < 0) {
+ return null;
+ }
+ return RawParseUtils.parsePersonIdent(rawObject, i);
+ }
+ }
+ return null;
+ }
+
+ private static byte[] dearmor(byte[] data) {
+ int start = RawParseUtils.match(data, 0,
+ SshSignatureConstants.ARMOR_HEAD);
+ if (start > 0) {
+ if (data[start] == '\r') {
+ start++;
+ }
+ if (data[start] == '\n') {
+ start++;
+ }
+ }
+ int end = data.length;
+ if (end > start + 1 && data[end - 1] == '\n') {
+ end--;
+ if (end > start + 1 && data[end - 1] == '\r') {
+ end--;
+ }
+ }
+ end = end - SshSignatureConstants.ARMOR_END.length;
+ if (end >= 0 && end >= start
+ && RawParseUtils.match(data, end,
+ SshSignatureConstants.ARMOR_END) >= 0) {
+ // end is fine: on the first the character of the end marker
+ } else {
+ // No end marker.
+ end = data.length;
+ }
+ if (start < 0) {
+ start = 0;
+ }
+ return Base64.decode(data, start, end - start);
+ }
+
+ @Override
+ public void clear() {
+ SigningKeyDatabase database = SigningKeyDatabase.getInstance();
+ if (database instanceof CachingSigningKeyDatabase caching) {
+ caching.clearCache();
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSigner.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSigner.java
new file mode 100644
index 0000000..8cfe5f4
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/signing/ssh/SshSigner.java
@@ -0,0 +1,485 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StreamCorruptedException;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.text.MessageFormat;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity;
+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.PublicKeyEntryResolver;
+import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.signature.BuiltinSignatures;
+import org.apache.sshd.common.signature.Signature;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.errors.CanceledException;
+import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
+import org.eclipse.jgit.internal.transport.sshd.AuthenticationCanceledException;
+import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper;
+import org.eclipse.jgit.internal.transport.sshd.SshdText;
+import org.eclipse.jgit.internal.transport.sshd.agent.SshAgentClient;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.GpgSignature;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Signer;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.sshd.KeyPasswordProviderFactory;
+import org.eclipse.jgit.transport.sshd.agent.Connector;
+import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
+import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link Signer} to create SSH signatures.
+ *
+ * @see <a href=
+ * "https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig">PROTOCOL.sshsig</a>
+ */
+public class SshSigner implements Signer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SshSigner.class);
+
+ private static final String GIT_KEY_PREFIX = "key::"; //$NON-NLS-1$
+
+ // Base64 encoded lines should not be longer than 75 characters, plus the
+ // newline.
+ private static final int LINE_LENGTH = 75;
+
+ @Override
+ public GpgSignature sign(Repository repository, GpgConfig config,
+ byte[] data, PersonIdent committer, String signingKey,
+ CredentialsProvider credentialsProvider) throws CanceledException,
+ IOException, UnsupportedSigningFormatException {
+ byte[] hash;
+ try {
+ hash = MessageDigest.getInstance("SHA512").digest(data); //$NON-NLS-1$
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedSigningFormatException(
+ MessageFormat.format(
+ SshdText.get().signUnknownHashAlgorithm, "SHA512"), //$NON-NLS-1$
+ e);
+ }
+ Buffer toSign = new ByteArrayBuffer();
+ toSign.putRawBytes(SshSignatureConstants.MAGIC);
+ toSign.putString(SshSignatureConstants.NAMESPACE);
+ toSign.putUInt(0); // reserved: zero-length string
+ toSign.putString("sha512"); //$NON-NLS-1$
+ toSign.putBytes(hash);
+ String key = signingKey;
+ if (StringUtils.isEmptyOrNull(key)) {
+ key = config.getSigningKey();
+ }
+ if (StringUtils.isEmptyOrNull(key)) {
+ key = defaultKeyCommand(repository, config);
+ // According to documentation, this is supposed to return a
+ // valid SSH public key prefixed with "key::". We don't enforce
+ // this: there might be older command implementations (like just
+ // calling "ssh-add -L") that return keys without prefix.
+ }
+ PublicKeyIdentity identity;
+ try {
+ identity = getIdentity(key, committer, credentialsProvider);
+ } catch (GeneralSecurityException e) {
+ throw new UnsupportedSigningFormatException(MessageFormat
+ .format(SshdText.get().signPublicKeyError, key), e);
+ }
+ String algorithm = KeyUtils
+ .getKeyType(identity.getKeyIdentity().getPublic());
+ switch (algorithm) {
+ case KeyPairProvider.SSH_DSS:
+ case KeyPairProvider.SSH_DSS_CERT:
+ throw new UnsupportedSigningFormatException(
+ SshdText.get().signInvalidKeyDSA);
+ case KeyPairProvider.SSH_RSA:
+ algorithm = KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS;
+ break;
+ case KeyPairProvider.SSH_RSA_CERT:
+ algorithm = KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS;
+ break;
+ default:
+ break;
+ }
+
+ Map.Entry<String, byte[]> rawSignature;
+ try {
+ rawSignature = identity.sign(null, algorithm,
+ toSign.getCompactData());
+ } catch (Exception e) {
+ throw new UnsupportedSigningFormatException(
+ SshdText.get().signSignatureError, e);
+ }
+ algorithm = rawSignature.getKey();
+ Buffer signature = new ByteArrayBuffer();
+ signature.putRawBytes(SshSignatureConstants.MAGIC);
+ signature.putUInt(SshSignatureConstants.VERSION);
+ signature.putPublicKey(identity.getKeyIdentity().getPublic());
+ signature.putString(SshSignatureConstants.NAMESPACE);
+ signature.putUInt(0); // reserved: zero-length string
+ signature.putString("sha512"); //$NON-NLS-1$
+ Buffer sig = new ByteArrayBuffer();
+ sig.putString(KeyUtils.getSignatureAlgorithm(algorithm,
+ identity.getKeyIdentity().getPublic()));
+ sig.putBytes(rawSignature.getValue());
+ signature.putBytes(sig.getCompactData());
+ return armor(signature.getCompactData());
+ }
+
+ private static String defaultKeyCommand(@NonNull Repository repository,
+ @NonNull GpgConfig config) throws IOException {
+ String command = config.getSshDefaultKeyCommand();
+ if (StringUtils.isEmptyOrNull(command)) {
+ return null;
+ }
+ FS fileSystem = repository.getFS();
+ if (fileSystem == null) {
+ fileSystem = FS.DETECTED;
+ }
+ ProcessBuilder builder = fileSystem.runInShell(command,
+ new String[] {});
+ ExecutionResult result = null;
+ try {
+ result = fileSystem.execute(builder, null);
+ int exitCode = result.getRc();
+ if (exitCode == 0) {
+ // The command is supposed to return a public key in its first
+ // line on stdout.
+ try (BufferedReader r = new BufferedReader(
+ new InputStreamReader(
+ result.getStdout().openInputStream(),
+ SystemReader.getInstance()
+ .getDefaultCharset()))) {
+ String line = r.readLine();
+ if (line != null) {
+ line = line.strip();
+ }
+ if (StringUtils.isEmptyOrNull(line)) {
+ throw new IOException(MessageFormat.format(
+ SshdText.get().signDefaultKeyEmpty, command));
+ }
+ return line;
+ }
+ }
+ TemporaryBuffer stderr = result.getStderr();
+ throw new IOException(MessageFormat.format(
+ SshdText.get().signDefaultKeyFailed, command,
+ Integer.toString(exitCode), toString(stderr)));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException(
+ MessageFormat.format(
+ SshdText.get().signDefaultKeyInterrupted, command),
+ e);
+ } finally {
+ if (result != null) {
+ if (result.getStderr() != null) {
+ result.getStderr().destroy();
+ }
+ if (result.getStdout() != null) {
+ result.getStdout().destroy();
+ }
+ }
+ }
+ }
+
+ private static String toString(TemporaryBuffer b) {
+ if (b != null) {
+ try {
+ return new String(b.toByteArray(4000),
+ SystemReader.getInstance().getDefaultCharset());
+ } catch (IOException e) {
+ LOG.warn("{}", SshdText.get().signStderr, e); //$NON-NLS-1$
+ }
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ private static PublicKeyIdentity getIdentity(String signingKey,
+ PersonIdent committer, CredentialsProvider credentials)
+ throws CanceledException, GeneralSecurityException, IOException {
+ if (StringUtils.isEmptyOrNull(signingKey)) {
+ throw new IllegalArgumentException(SshdText.get().signNoSigningKey);
+ }
+ PublicKey publicKey = null;
+ PrivateKey privateKey = null;
+ File keyFile = null;
+ if (signingKey.startsWith(GIT_KEY_PREFIX)) {
+ try (StringReader r = new StringReader(
+ signingKey.substring(GIT_KEY_PREFIX.length()))) {
+ publicKey = fromEntry(
+ AuthorizedKeyEntry.readAuthorizedKeys(r, true));
+ }
+ } else if (signingKey.startsWith("~/") //$NON-NLS-1$
+ || signingKey.startsWith('~' + File.separator)) {
+ keyFile = new File(FS.DETECTED.userHome(), signingKey.substring(2));
+ } else {
+ try (StringReader r = new StringReader(signingKey)) {
+ publicKey = fromEntry(
+ AuthorizedKeyEntry.readAuthorizedKeys(r, true));
+ } catch (IOException e) {
+ // Ignore and try to read as a file
+ keyFile = new File(signingKey);
+ }
+ }
+ if (keyFile != null && keyFile.isFile()) {
+ try {
+ publicKey = fromEntry(AuthorizedKeyEntry
+ .readAuthorizedKeys(keyFile.toPath()));
+ if (publicKey == null) {
+ throw new IOException(MessageFormat.format(
+ SshdText.get().signTooManyPublicKeys, keyFile));
+ }
+ // Try to find the private key so we don't go looking for
+ // the agent (or PKCS#11) in vain.
+ keyFile = getPrivateKeyFile(keyFile.getParentFile(),
+ keyFile.getName());
+ if (keyFile != null) {
+ try {
+ KeyPair pair = loadPrivateKey(keyFile.toPath(),
+ credentials);
+ if (pair != null) {
+ PublicKey pk = pair.getPublic();
+ if (pk == null) {
+ privateKey = pair.getPrivate();
+ } else {
+ PublicKey original = publicKey;
+ if (publicKey instanceof OpenSshCertificate cert) {
+ original = cert.getCertPubKey();
+ }
+ if (KeyUtils.compareKeys(original, pk)) {
+ privateKey = pair.getPrivate();
+ }
+ }
+ }
+ } catch (IOException e) {
+ // Apparently it wasn't a private key file. Ignore.
+ }
+ }
+ } catch (StreamCorruptedException e) {
+ // File is readable, but apparently not a public key. Try to
+ // load it as a private key.
+ KeyPair pair = loadPrivateKey(keyFile.toPath(), credentials);
+ if (pair != null) {
+ publicKey = pair.getPublic();
+ privateKey = pair.getPrivate();
+ }
+ }
+ }
+ if (publicKey == null) {
+ throw new IOException(MessageFormat
+ .format(SshdText.get().signNoPublicKey, signingKey));
+ }
+ if (publicKey instanceof OpenSshCertificate cert) {
+ String message = SshCertificateUtils.verify(cert,
+ committer.getWhenAsInstant());
+ if (message != null) {
+ throw new IOException(message);
+ }
+ }
+ if (privateKey == null) {
+ // Could be in the agent, or a PKCS#11 key. The normal procedure
+ // with PKCS#11 keys is to put them in the agent and let the agent
+ // deal with it.
+ //
+ // This may or may not work well. For instance, the agent might ask
+ // for a passphrase for PKCS#11 keys... also, the OpenSSH ssh-agent
+ // had a bug with signing using PKCS#11 certificates in the agent;
+ // see https://bugzilla.mindrot.org/show_bug.cgi?id=3613 . If there
+ // are troubles, we might do the PKCS#11 dance ourselves, but we'd
+ // need additional configuration for the PKCS#11 library. (Plus
+ // some refactoring in the Pkcs11Provider.)
+ return new AgentIdentity(publicKey);
+
+ }
+ return new KeyPairIdentity(new KeyPair(publicKey, privateKey));
+ }
+
+ private static File getPrivateKeyFile(File directory,
+ String publicKeyName) {
+ if (publicKeyName.endsWith(".pub")) { //$NON-NLS-1$
+ String privateKeyName = publicKeyName.substring(0,
+ publicKeyName.length() - 4);
+ if (!privateKeyName.isEmpty()) {
+ File keyFile = new File(directory, privateKeyName);
+ if (keyFile.isFile()) {
+ return keyFile;
+ }
+ if (privateKeyName.endsWith("-cert")) { //$NON-NLS-1$
+ privateKeyName = privateKeyName.substring(0,
+ privateKeyName.length() - 5);
+ if (!privateKeyName.isEmpty()) {
+ keyFile = new File(directory, privateKeyName);
+ if (keyFile.isFile()) {
+ return keyFile;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static KeyPair loadPrivateKey(Path path,
+ CredentialsProvider credentials)
+ throws CanceledException, GeneralSecurityException, IOException {
+ if (!Files.isRegularFile(path)) {
+ return null;
+ }
+ KeyPairResourceParser parser = SecurityUtils.getKeyPairResourceParser();
+ if (parser != null) {
+ PasswordProviderWrapper provider = null;
+ if (credentials != null) {
+ provider = new PasswordProviderWrapper(
+ () -> KeyPasswordProviderFactory.getInstance()
+ .apply(credentials));
+ }
+ try {
+ Collection<KeyPair> keyPairs = parser.loadKeyPairs(null, path,
+ provider);
+ if (keyPairs.size() != 1) {
+ throw new GeneralSecurityException(MessageFormat.format(
+ SshdText.get().signTooManyPrivateKeys, path));
+ }
+ return keyPairs.iterator().next();
+ } catch (AuthenticationCanceledException e) {
+ throw new CanceledException(e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private static GpgSignature armor(byte[] data) throws IOException {
+ try (ByteArrayOutputStream b = new ByteArrayOutputStream()) {
+ b.write(SshSignatureConstants.ARMOR_HEAD);
+ b.write('\n');
+ String encoded = Base64.encodeBytes(data);
+ int length = encoded.length();
+ int column = 0;
+ for (int i = 0; i < length; i++) {
+ b.write(encoded.charAt(i));
+ column++;
+ if (column == LINE_LENGTH) {
+ b.write('\n');
+ column = 0;
+ }
+ }
+ if (column > 0) {
+ b.write('\n');
+ }
+ b.write(SshSignatureConstants.ARMOR_END);
+ b.write('\n');
+ return new GpgSignature(b.toByteArray());
+ }
+ }
+
+ private static PublicKey fromEntry(List<AuthorizedKeyEntry> entries)
+ throws GeneralSecurityException, IOException {
+ if (entries == null || entries.size() != 1) {
+ return null;
+ }
+ return entries.get(0).resolvePublicKey(null,
+ PublicKeyEntryResolver.FAILING);
+ }
+
+ @Override
+ public boolean canLocateSigningKey(Repository repository, GpgConfig config,
+ PersonIdent committer, String signingKey,
+ CredentialsProvider credentialsProvider) throws CanceledException {
+ String key = signingKey;
+ if (key == null) {
+ key = config.getSigningKey();
+ }
+ return !(StringUtils.isEmptyOrNull(key)
+ && StringUtils.isEmptyOrNull(config.getSshDefaultKeyCommand()));
+ }
+
+ private static class KeyPairIdentity implements PublicKeyIdentity {
+
+ private final @NonNull KeyPair pair;
+
+ KeyPairIdentity(@NonNull KeyPair pair) {
+ this.pair = pair;
+ }
+
+ @Override
+ public KeyPair getKeyIdentity() {
+ return pair;
+ }
+
+ @Override
+ public Entry<String, byte[]> sign(SessionContext session, String algo,
+ byte[] data) throws Exception {
+ BuiltinSignatures factory = BuiltinSignatures.fromFactoryName(algo);
+ if (factory == null || !factory.isSupported()) {
+ throw new GeneralSecurityException(MessageFormat.format(
+ SshdText.get().signUnknownSignatureAlgorithm, algo));
+ }
+ Signature signer = factory.create();
+ signer.initSigner(null, pair.getPrivate());
+ signer.update(null, data);
+ return new SimpleImmutableEntry<>(factory.getName(),
+ signer.sign(null));
+ }
+ }
+
+ private static class AgentIdentity extends KeyPairIdentity {
+
+ AgentIdentity(PublicKey publicKey) {
+ super(new KeyPair(publicKey, null));
+ }
+
+ @Override
+ public Entry<String, byte[]> sign(SessionContext session, String algo,
+ byte[] data) throws Exception {
+ ConnectorFactory factory = ConnectorFactory.getDefault();
+ Connector connector = factory == null ? null
+ : factory.create("", null); //$NON-NLS-1$
+ if (connector == null) {
+ throw new IOException(SshdText.get().signNoAgent);
+ }
+ try (SshAgentClient agent = new SshAgentClient(connector)) {
+ return agent.sign(null, getKeyIdentity().getPublic(), algo,
+ data);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
index 2cd0669..900c9fb 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/PasswordProviderWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -47,6 +47,8 @@ private static class PerSessionState {
private final Supplier<KeyPasswordProvider> factory;
+ private PerSessionState noSessionState;
+
/**
* Creates a new {@link PasswordProviderWrapper}.
*
@@ -59,13 +61,18 @@ public PasswordProviderWrapper(
}
private PerSessionState getState(SessionContext context) {
- PerSessionState state = context.getAttribute(STATE);
+ PerSessionState state = context != null ? context.getAttribute(STATE)
+ : noSessionState;
if (state == null) {
state = new PerSessionState();
state.delegate = factory.get();
state.delegate.setAttempts(
PASSWORD_PROMPTS.getRequiredDefault().intValue());
- context.setAttribute(STATE, state);
+ if (context != null) {
+ context.setAttribute(STATE, state);
+ } else {
+ noSessionState = state;
+ }
}
return state;
}
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 05f04ac..0533b65 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -147,6 +147,71 @@ public static SshdText get() {
/***/ public String sshCommandTimeout;
/***/ public String sshProcessStillRunning;
/***/ public String sshProxySessionCloseFailed;
+ /***/ public String signAllowedSignersCertAuthorityError;
+ /***/ public String signAllowedSignersEmptyIdentity;
+ /***/ public String signAllowedSignersEmptyNamespaces;
+ /***/ public String signAllowedSignersFormatError;
+ /***/ public String signAllowedSignersInvalidDate;
+ /***/ public String signAllowedSignersLineFormat;
+ /***/ public String signAllowedSignersMultiple;
+ /***/ public String signAllowedSignersNoIdentities;
+ /***/ public String signAllowedSignersPublicKeyParsing;
+ /***/ public String signAllowedSignersUnterminatedQuote;
+ /***/ public String signCertAlgorithmMismatch;
+ /***/ public String signCertAlgorithmUnknown;
+ /***/ public String signCertificateExpired;
+ /***/ public String signCertificateInvalid;
+ /***/ public String signCertificateNotForName;
+ /***/ public String signCertificateRevoked;
+ /***/ public String signCertificateTooEarly;
+ /***/ public String signCertificateWithoutPrincipals;
+ /***/ public String signDefaultKeyEmpty;
+ /***/ public String signDefaultKeyFailed;
+ /***/ public String signDefaultKeyInterrupted;
+ /***/ public String signGarbageAtEnd;
+ /***/ public String signInvalidAlgorithm;
+ /***/ public String signInvalidKeyDSA;
+ /***/ public String signInvalidMagic;
+ /***/ public String signInvalidNamespace;
+ /***/ public String signInvalidSignature;
+ /***/ public String signInvalidVersion;
+ /***/ public String signKeyExpired;
+ /***/ public String signKeyRevoked;
+ /***/ public String signKeyTooEarly;
+ /***/ public String signKrlBlobLeftover;
+ /***/ public String signKrlBlobLengthInvalid;
+ /***/ public String signKrlBlobLengthInvalidExpected;
+ /***/ public String signKrlCaKeyLengthInvalid;
+ /***/ public String signKrlCertificateLeftover;
+ /***/ public String signKrlCertificateSubsectionLeftover;
+ /***/ public String signKrlCertificateSubsectionLength;
+ /***/ public String signKrlEmptyRange;
+ /***/ public String signKrlInvalidBitSetLength;
+ /***/ public String signKrlInvalidKeyIdLength;
+ /***/ public String signKrlInvalidMagic;
+ /***/ public String signKrlInvalidReservedLength;
+ /***/ public String signKrlInvalidVersion;
+ /***/ public String signKrlNoCertificateSubsection;
+ /***/ public String signKrlSerialZero;
+ /***/ public String signKrlShortRange;
+ /***/ public String signKrlUnknownSection;
+ /***/ public String signKrlUnknownSubsection;
+ /***/ public String signLogFailure;
+ /***/ public String signMismatchedSignatureAlgorithm;
+ /***/ public String signNoAgent;
+ /***/ public String signNoPrincipalMatched;
+ /***/ public String signNoPublicKey;
+ /***/ public String signNoSigningKey;
+ /***/ public String signNotUserCertificate;
+ /***/ public String signPublicKeyError;
+ /***/ public String signSeeLog;
+ /***/ public String signSignatureError;
+ /***/ public String signStderr;
+ /***/ public String signTooManyPrivateKeys;
+ /***/ public String signTooManyPublicKeys;
+ /***/ public String signUnknownHashAlgorithm;
+ /***/ public String signUnknownSignatureAlgorithm;
+ /***/ public String signWrongNamespace;
/***/ public String unknownProxyProtocol;
}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java
new file mode 100644
index 0000000..4d2d8b6
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/CachingSigningKeyDatabase.java
@@ -0,0 +1,49 @@
+/*
+ * 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.signing.ssh;
+
+/**
+ * A {@link SigningKeyDatabase} that caches data.
+ * <p>
+ * A signing key database may be used to check keys frequently; it may thus need
+ * to cache some data and it may need to cache data per repository. If an
+ * implementation does cache data, it is responsible itself for refreshing that
+ * cache at appropriate times. Clients can control the cache size somewhat via
+ * {@link #setCacheSize(int)}, although the meaning of the cache size (i.e., its
+ * unit) is left undefined here.
+ * </p>
+ *
+ * @since 7.1
+ */
+public interface CachingSigningKeyDatabase extends SigningKeyDatabase {
+
+ /**
+ * Retrieves the current cache size.
+ *
+ * @return the cache size, or -1 if this database has no cache.
+ */
+ int getCacheSize();
+
+ /**
+ * Sets the cache size to use.
+ *
+ * @param size
+ * the cache size, ignored if this database does not have a
+ * cache.
+ * @throws IllegalArgumentException
+ * if {@code size < 0}
+ */
+ void setCacheSize(int size);
+
+ /**
+ * Discards any cached data. A no-op if the database has no cache.
+ */
+ void clearCache();
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java
new file mode 100644
index 0000000..eec64c3
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SigningKeyDatabase.java
@@ -0,0 +1,94 @@
+/*
+ * 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.signing.ssh;
+
+import java.io.IOException;
+import java.security.PublicKey;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.signing.ssh.SigningDatabase;
+import org.eclipse.jgit.lib.GpgConfig;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * A database storing meta-information about signing keys and certificates.
+ *
+ * @since 7.1
+ */
+public interface SigningKeyDatabase {
+
+ /**
+ * Obtains the current global instance.
+ *
+ * @return the global {@link SigningKeyDatabase}
+ */
+ static SigningKeyDatabase getInstance() {
+ return SigningDatabase.getInstance();
+ }
+
+ /**
+ * Sets the global {@link SigningKeyDatabase}.
+ *
+ * @param database
+ * to set; if {@code null} a default database using the OpenSSH
+ * allowed signers file and the OpenSSH revocation list mechanism
+ * is used.
+ * @return the previously set {@link SigningKeyDatabase}
+ */
+ static SigningKeyDatabase setInstance(SigningKeyDatabase database) {
+ return SigningDatabase.setInstance(database);
+ }
+
+ /**
+ * Determines whether the gives key has been revoked.
+ *
+ * @param repository
+ * {@link Repository} the key is being used in
+ * @param config
+ * {@link GpgConfig} to use
+ * @param key
+ * {@link PublicKey} to check
+ * @return {@code true} if the key has been revoked, {@code false} otherwise
+ * @throws IOException
+ * if an I/O problem occurred
+ */
+ boolean isRevoked(@NonNull Repository repository, @NonNull GpgConfig config,
+ @NonNull PublicKey key) throws IOException;
+
+ /**
+ * Checks whether the given key is allowed to be used for signing, and if
+ * allowed returns the principal.
+ *
+ * @param repository
+ * {@link Repository} the key is being used in
+ * @param config
+ * {@link GpgConfig} to use
+ * @param key
+ * {@link PublicKey} to check
+ * @param namespace
+ * of the signature
+ * @param ident
+ * optional {@link PersonIdent} giving a signer's e-mail address
+ * and a signature time
+ * @return {@code null} if the database does not contain any information
+ * about the given key; the principal if it does and all checks
+ * passed
+ * @throws IOException
+ * if an I/O problem occurred
+ * @throws VerificationException
+ * if the database contains information about the key and the
+ * checks determined that the key is not allowed to be used for
+ * signing
+ */
+ String isAllowed(@NonNull Repository repository, @NonNull GpgConfig config,
+ @NonNull PublicKey key, @NonNull String namespace,
+ PersonIdent ident) throws IOException, VerificationException;
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java
new file mode 100644
index 0000000..c315428
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignatureVerifierFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.signing.ssh;
+
+import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
+import org.eclipse.jgit.lib.SignatureVerifier;
+import org.eclipse.jgit.internal.signing.ssh.SshSignatureVerifier;
+import org.eclipse.jgit.lib.SignatureVerifierFactory;
+
+/**
+ * Factory creating {@link SshSignatureVerifier}s.
+ *
+ * @since 7.1
+ */
+public final class SshSignatureVerifierFactory
+ implements SignatureVerifierFactory {
+
+ @Override
+ public GpgFormat getType() {
+ return GpgFormat.SSH;
+ }
+
+ @Override
+ public SignatureVerifier create() {
+ return new SshSignatureVerifier();
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignerFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignerFactory.java
new file mode 100644
index 0000000..5459b53
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/SshSignerFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.signing.ssh;
+
+import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
+import org.eclipse.jgit.lib.Signer;
+import org.eclipse.jgit.internal.signing.ssh.SshSigner;
+import org.eclipse.jgit.lib.SignerFactory;
+
+/**
+ * Factory creating {@link SshSigner}s.
+ *
+ * @since 7.1
+ */
+public final class SshSignerFactory implements SignerFactory {
+
+ @Override
+ public GpgFormat getType() {
+ return GpgFormat.SSH;
+ }
+
+ @Override
+ public Signer create() {
+ return new SshSigner();
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java
new file mode 100644
index 0000000..cd77111
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/signing/ssh/VerificationException.java
@@ -0,0 +1,63 @@
+/*
+ * 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.signing.ssh;
+
+/**
+ * An exception giving details about a failed
+ * {@link SigningKeyDatabase#isAllowed(org.eclipse.jgit.lib.Repository, org.eclipse.jgit.lib.GpgConfig, java.security.PublicKey, String, org.eclipse.jgit.lib.PersonIdent)}
+ * validation.
+ *
+ * @since 7.1
+ */
+public class VerificationException extends Exception {
+
+ private static final long serialVersionUID = 313760495170326160L;
+
+ private final boolean expired;
+
+ private final String reason;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param expired
+ * whether the checked public key or certificate was expired
+ * @param reason
+ * describing the check failure
+ */
+ public VerificationException(boolean expired, String reason) {
+ this.expired = expired;
+ this.reason = reason;
+ }
+
+ @Override
+ public String getMessage() {
+ return reason;
+ }
+
+ /**
+ * Tells whether the check failed because the public key was expired.
+ *
+ * @return {@code true} if the check failed because the public key was
+ * expired, {@code false} otherwise
+ */
+ public boolean isExpired() {
+ return expired;
+ }
+
+ /**
+ * Retrieves the check failure reason.
+ *
+ * @return the reason description
+ */
+ public String getReason() {
+ return reason;
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProviderFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProviderFactory.java
new file mode 100644
index 0000000..0537300
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/KeyPasswordProviderFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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.transport.sshd;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.transport.CredentialsProvider;
+
+/**
+ * Maintains a static singleton instance of a factory to create a
+ * {@link KeyPasswordProvider} from a {@link CredentialsProvider}.
+ *
+ * @since 7.1
+ */
+public final class KeyPasswordProviderFactory {
+
+ /**
+ * Creates a {@link KeyPasswordProvider} from a {@link CredentialsProvider}.
+ */
+ @FunctionalInterface
+ public interface KeyPasswordProviderCreator
+ extends Function<CredentialsProvider, KeyPasswordProvider> {
+ // Nothing
+ }
+
+ private static final KeyPasswordProviderCreator DEFAULT = IdentityPasswordProvider::new;
+
+ private static AtomicReference<KeyPasswordProviderCreator> INSTANCE = new AtomicReference<>(
+ DEFAULT);
+
+ private KeyPasswordProviderFactory() {
+ // No instantiation
+ }
+
+ /**
+ * Retrieves the currently set {@link KeyPasswordProviderCreator}.
+ *
+ * @return the {@link KeyPasswordProviderCreator}
+ */
+ @NonNull
+ public static KeyPasswordProviderCreator getInstance() {
+ return INSTANCE.get();
+ }
+
+ /**
+ * Sets a new {@link KeyPasswordProviderCreator}.
+ *
+ * @param provider
+ * to set; if {@code null}, sets a default provider.
+ * @return the previously set {@link KeyPasswordProviderCreator}
+ */
+ @NonNull
+ public static KeyPasswordProviderCreator setInstance(
+ KeyPasswordProviderCreator provider) {
+ if (provider == null) {
+ return INSTANCE.getAndSet(DEFAULT);
+ }
+ return INSTANCE.getAndSet(provider);
+ }
+}
diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
index 2c3cbe5..4a2eb9c 100644
--- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
+++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2018, 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
@@ -210,7 +210,7 @@ public SshdSession getSession(URIish uri,
home, sshDir);
KeyIdentityProvider defaultKeysProvider = toKeyIdentityProvider(
getDefaultKeys(sshDir));
- Supplier<KeyPasswordProvider> keyPasswordProvider = () -> createKeyPasswordProvider(
+ Supplier<KeyPasswordProvider> keyPasswordProvider = newKeyPasswordProvider(
credentialsProvider);
SshClient client = ClientBuilder.builder()
.factory(JGitSshClient::new)
@@ -574,12 +574,24 @@ protected final KeyCache getKeyCache() {
* @param provider
* the {@link CredentialsProvider} to delegate to for user
* interactions
- * @return a new {@link KeyPasswordProvider}
+ * @return a new {@link KeyPasswordProvider}, or {@code null} to use the
+ * global {@link KeyPasswordProviderFactory}
*/
- @NonNull
protected KeyPasswordProvider createKeyPasswordProvider(
CredentialsProvider provider) {
- return new IdentityPasswordProvider(provider);
+ return null;
+ }
+
+ private Supplier<KeyPasswordProvider> newKeyPasswordProvider(
+ CredentialsProvider credentials) {
+ return () -> {
+ KeyPasswordProvider provider = createKeyPasswordProvider(
+ credentials);
+ if (provider != null) {
+ return provider;
+ }
+ return KeyPasswordProviderFactory.getInstance().apply(credentials);
+ };
}
/**
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 8821702..71977c6 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit.ssh;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.ssh.jsch;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.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.hamcrest;version="[1.1.0,3.0.0)",
org.junit;version="[4.13,5.0.0)",
org.junit.experimental.theories;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 4c9097a..f67b1bd 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 92840a9..3a6e08c 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.0.2,7.1.0)"
+Fragment-Host: org.eclipse.jgit;bundle-version="[7.1.2,7.2.0)"
Bundle-Vendor: %Bundle-Vendor
Bundle-Localization: OSGI-INF/l10n/jsch
Bundle-ActivationPolicy: lazy
-Bundle-Version: 7.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="7.0.2"
+Export-Package: org.eclipse.jgit.transport.ssh.jsch;version="7.1.2"
Import-Package: com.jcraft.jsch;version="[0.1.37,0.2.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.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.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 c0e1acc..f4c12e6 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ssh.jsch;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ssh.jsch/pom.xml b/org.eclipse.jgit.ssh.jsch/pom.xml
index 1f4564c..4427602 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.ssh.jsch</artifactId>
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index eb95243..77a7f39 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-RequiredExecutionEnvironment: JavaSE-17
@@ -21,64 +21,64 @@
org.apache.commons.io;version="[2.15.0,3.0.0)",
org.apache.commons.io.output;version="[2.15.0,3.0.0)",
org.assertj.core.api;version="[3.14.0,4.0.0)",
- org.eclipse.jgit.annotations;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.api;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.api.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.archive;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.attributes;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.awtui;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.blame;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.diff;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.dircache;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.events;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.fnmatch;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.gitrepo;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.hooks;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.ignore;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.ignore.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.diff;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.fsck;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.commitgraph;version="7.0.2",
- org.eclipse.jgit.internal.storage.dfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.io;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.memory;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.connectivity;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.parser;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.junit.time;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lfs;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.logging;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.merge;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.notes;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.patch;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.pgm;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.pgm.internal;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revplot;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.file;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.storage.pack;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.submodule;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.http;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport.resolver;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.treewalk.filter;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.io;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util.sha1;version="[7.0.2,7.1.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.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/pom.xml b/org.eclipse.jgit.test/pom.xml
index d44ca07..479050b 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.test</artifactId>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 12300b3..6d5e45c 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -21,6 +21,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Map;
import java.util.concurrent.Callable;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
@@ -29,6 +30,7 @@
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.IndexDiff.StageState;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
@@ -117,6 +119,7 @@ public void testPullMerge() throws Exception {
+ db.getWorkTree().getAbsolutePath();
assertEquals(message, mergeCommit.getShortMessage());
}
+ assertTrue(target.status().call().isClean());
}
@Test
@@ -153,6 +156,10 @@ public void testPullConflict() throws Exception {
assertFileContentsEqual(targetFile, result);
assertEquals(RepositoryState.MERGING, target.getRepository()
.getRepositoryState());
+ Status status = target.status().call();
+ Map<String, StageState> conflicting = status.getConflictingStageState();
+ assertEquals(1, conflicting.size());
+ assertEquals(StageState.BOTH_MODIFIED, conflicting.get("SomeFile.txt"));
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerMissingPermissionsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerMissingPermissionsTest.java
deleted file mode 100644
index d0fbdbd..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerMissingPermissionsTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (c) 2019 Alex Jitianu <alex_jitianu@sync.ro> 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.api;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.Policy;
-import java.util.Collections;
-
-import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.util.FileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Tests that using a SecurityManager does not result in errors logged.
- */
-public class SecurityManagerMissingPermissionsTest extends RepositoryTestCase {
-
- /**
- * Collects all logging sent to the logging system.
- */
- private final ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
-
- private SecurityManager originalSecurityManager;
-
- private PrintStream defaultErrorOutput;
-
- @Override
- @Before
- public void setUp() throws Exception {
- originalSecurityManager = System.getSecurityManager();
-
- // slf4j-simple logs to System.err, redirect it to enable asserting
- // logged errors
- defaultErrorOutput = System.err;
- System.setErr(new PrintStream(errorOutput));
-
- refreshPolicyAllPermission(Policy.getPolicy());
- System.setSecurityManager(new SecurityManager());
- super.setUp();
- }
-
- /**
- * If a SecurityManager is active a lot of {@link java.io.FilePermission}
- * errors are thrown and logged while initializing a repository.
- *
- * @throws Exception
- */
- @Test
- public void testCreateNewRepos_MissingPermissions() throws Exception {
- File wcTree = new File(getTemporaryDirectory(),
- "CreateNewRepositoryTest_testCreateNewRepos");
-
- File marker = new File(getTemporaryDirectory(), "marker");
- Files.write(marker.toPath(), Collections.singletonList("Can write"));
- assertTrue("Can write in test directory", marker.isFile());
- FileUtils.delete(marker);
- assertFalse("Can delete in test direcory", marker.exists());
-
- Git git = Git.init().setBare(false)
- .setDirectory(new File(wcTree.getAbsolutePath())).call();
-
- addRepoToClose(git.getRepository());
-
- assertEquals("", errorOutput.toString());
- }
-
- @Override
- @After
- public void tearDown() throws Exception {
- System.setSecurityManager(originalSecurityManager);
- System.setErr(defaultErrorOutput);
- super.tearDown();
- }
-
- /**
- * Refresh the Java Security Policy.
- *
- * @param policy
- * the policy object
- *
- * @throws IOException
- * if the temporary file that contains the policy could not be
- * created
- */
- private static void refreshPolicyAllPermission(Policy policy)
- throws IOException {
- // Starting with an all permissions policy.
- String policyString = "grant { permission java.security.AllPermission; };";
-
- // Do not use TemporaryFilesFactory, it will create a dependency cycle
- Path policyFile = Files.createTempFile("testpolicy", ".txt");
-
- try {
- Files.write(policyFile, Collections.singletonList(policyString));
- System.setProperty("java.security.policy",
- policyFile.toUri().toURL().toString());
- policy.refresh();
- } finally {
- try {
- Files.delete(policyFile);
- } catch (IOException e) {
- // Do not log; the test tests for no logging having occurred
- e.printStackTrace();
- }
- }
- }
-
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerTest.java
deleted file mode 100644
index 2b930a1..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/SecurityManagerTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2019 Nail Samatov <sanail@yandex.ru> 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.api;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.FilePermission;
-import java.io.IOException;
-import java.lang.reflect.ReflectPermission;
-import java.nio.file.Files;
-import java.security.Permission;
-import java.security.SecurityPermission;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.PropertyPermission;
-import java.util.logging.LoggingPermission;
-
-import javax.security.auth.AuthPermission;
-
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.junit.JGitTestUtil;
-import org.eclipse.jgit.junit.MockSystemReader;
-import org.eclipse.jgit.junit.SeparateClassloaderTestRunner;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.SystemReader;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * <p>
- * Tests if jgit works if SecurityManager is enabled.
- * </p>
- *
- * <p>
- * Note: JGit's classes shouldn't be used before SecurityManager is configured.
- * If you use some JGit's class before SecurityManager is replaced then part of
- * the code can be invoked outside of our custom SecurityManager and this test
- * becomes useless.
- * </p>
- *
- * <p>
- * For example the class {@link org.eclipse.jgit.util.FS} is used widely in jgit
- * sources. It contains DETECTED static field. At the first usage of the class
- * FS the field DETECTED is initialized and during initialization many system
- * operations that SecurityManager can forbid are invoked.
- * </p>
- *
- * <p>
- * For this reason this test doesn't extend LocalDiskRepositoryTestCase (it uses
- * JGit's classes in setUp() method) and other JGit's utility classes. It's done
- * to affect SecurityManager as less as possible.
- * </p>
- *
- * <p>
- * We use SeparateClassloaderTestRunner to isolate FS.DETECTED field
- * initialization between different tests run.
- * </p>
- */
-@RunWith(SeparateClassloaderTestRunner.class)
-public class SecurityManagerTest {
- private File root;
-
- private SecurityManager originalSecurityManager;
-
- private List<Permission> permissions = new ArrayList<>();
-
- @Before
- public void setUp() throws Exception {
- // Create working directory
- SystemReader.setInstance(new MockSystemReader());
- root = Files.createTempDirectory("jgit-security").toFile();
-
- // Add system permissions
- permissions.add(new RuntimePermission("*"));
- permissions.add(new SecurityPermission("*"));
- permissions.add(new AuthPermission("*"));
- permissions.add(new ReflectPermission("*"));
- permissions.add(new PropertyPermission("*", "read,write"));
- permissions.add(new LoggingPermission("control", null));
-
- permissions.add(new FilePermission(
- System.getProperty("java.home") + "/-", "read"));
-
- String tempDir = System.getProperty("java.io.tmpdir");
- permissions.add(new FilePermission(tempDir, "read,write,delete"));
- permissions
- .add(new FilePermission(tempDir + "/-", "read,write,delete"));
-
- // Add permissions to dependent jar files.
- String classPath = System.getProperty("java.class.path");
- if (classPath != null) {
- for (String path : classPath.split(File.pathSeparator)) {
- permissions.add(new FilePermission(path, "read"));
- }
- }
- // Add permissions to jgit class files.
- String jgitSourcesRoot = new File(System.getProperty("user.dir"))
- .getParent();
- permissions.add(new FilePermission(jgitSourcesRoot + "/-", "read"));
-
- // Add permissions to working dir for jgit. Our git repositories will be
- // initialized and cloned here.
- permissions.add(new FilePermission(root.getPath() + "/-",
- "read,write,delete,execute"));
-
- // Replace Security Manager
- originalSecurityManager = System.getSecurityManager();
- System.setSecurityManager(new SecurityManager() {
-
- @Override
- public void checkPermission(Permission requested) {
- for (Permission permission : permissions) {
- if (permission.implies(requested)) {
- return;
- }
- }
-
- super.checkPermission(requested);
- }
- });
- }
-
- @After
- public void tearDown() throws Exception {
- System.setSecurityManager(originalSecurityManager);
-
- // Note: don't use this method before security manager is replaced in
- // setUp() method. The method uses FS.DETECTED internally and can affect
- // the test.
- FileUtils.delete(root, FileUtils.RECURSIVE | FileUtils.RETRY);
- }
-
- @Test
- public void testInitAndClone() throws IOException, GitAPIException {
- File remote = new File(root, "remote");
- File local = new File(root, "local");
-
- try (Git git = Git.init().setDirectory(remote).call()) {
- JGitTestUtil.write(new File(remote, "hello.txt"), "Hello world!");
- git.add().addFilepattern(".").call();
- git.commit().setMessage("Initial commit").call();
- }
-
- try (Git git = Git.cloneRepository().setURI(remote.toURI().toString())
- .setDirectory(local).call()) {
- assertTrue(new File(local, ".git").exists());
-
- JGitTestUtil.write(new File(local, "hi.txt"), "Hi!");
- git.add().addFilepattern(".").call();
- RevCommit commit1 = git.commit().setMessage("Commit on local repo")
- .call();
- assertEquals("Commit on local repo", commit1.getFullMessage());
- assertNotNull(TreeWalk.forPath(git.getRepository(), "hello.txt",
- commit1.getTree()));
- }
-
- }
-
-}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStatsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStatsTest.java
new file mode 100644
index 0000000..2c4b432
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStatsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.junit.Test;
+
+public class AggregatedBlockCacheStatsTest {
+ @Test
+ public void getName() {
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of());
+
+ assertThat(aggregatedBlockCacheStats.getName(),
+ equalTo(AggregatedBlockCacheStats.class.getName()));
+ }
+
+ @Test
+ public void getCurrentSize_aggregatesCurrentSizes() {
+ long[] currentSizes = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ packStats.addToLiveBytes(new TestKey(PackExt.PACK), 5);
+ currentSizes[PackExt.PACK.getPosition()] = 5;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ bitmapStats.addToLiveBytes(new TestKey(PackExt.BITMAP_INDEX), 6);
+ currentSizes[PackExt.BITMAP_INDEX.getPosition()] = 6;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ indexStats.addToLiveBytes(new TestKey(PackExt.INDEX), 7);
+ currentSizes[PackExt.INDEX.getPosition()] = 7;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getCurrentSize(),
+ currentSizes);
+ }
+
+ @Test
+ public void getHitCount_aggregatesHitCounts() {
+ long[] hitCounts = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ incrementCounter(5,
+ () -> packStats.incrementHit(new TestKey(PackExt.PACK)));
+ hitCounts[PackExt.PACK.getPosition()] = 5;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ incrementCounter(6, () -> bitmapStats
+ .incrementHit(new TestKey(PackExt.BITMAP_INDEX)));
+ hitCounts[PackExt.BITMAP_INDEX.getPosition()] = 6;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ incrementCounter(7,
+ () -> indexStats.incrementHit(new TestKey(PackExt.INDEX)));
+ hitCounts[PackExt.INDEX.getPosition()] = 7;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getHitCount(), hitCounts);
+ }
+
+ @Test
+ public void getMissCount_aggregatesMissCounts() {
+ long[] missCounts = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ incrementCounter(5,
+ () -> packStats.incrementMiss(new TestKey(PackExt.PACK)));
+ missCounts[PackExt.PACK.getPosition()] = 5;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ incrementCounter(6, () -> bitmapStats
+ .incrementMiss(new TestKey(PackExt.BITMAP_INDEX)));
+ missCounts[PackExt.BITMAP_INDEX.getPosition()] = 6;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ incrementCounter(7,
+ () -> indexStats.incrementMiss(new TestKey(PackExt.INDEX)));
+ missCounts[PackExt.INDEX.getPosition()] = 7;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getMissCount(), missCounts);
+ }
+
+ @Test
+ public void getTotalRequestCount_aggregatesRequestCounts() {
+ long[] totalRequestCounts = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ incrementCounter(5, () -> {
+ packStats.incrementHit(new TestKey(PackExt.PACK));
+ packStats.incrementMiss(new TestKey(PackExt.PACK));
+ });
+ totalRequestCounts[PackExt.PACK.getPosition()] = 10;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ incrementCounter(6, () -> {
+ bitmapStats.incrementHit(new TestKey(PackExt.BITMAP_INDEX));
+ bitmapStats.incrementMiss(new TestKey(PackExt.BITMAP_INDEX));
+ });
+ totalRequestCounts[PackExt.BITMAP_INDEX.getPosition()] = 12;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ incrementCounter(7, () -> {
+ indexStats.incrementHit(new TestKey(PackExt.INDEX));
+ indexStats.incrementMiss(new TestKey(PackExt.INDEX));
+ });
+ totalRequestCounts[PackExt.INDEX.getPosition()] = 14;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getTotalRequestCount(),
+ totalRequestCounts);
+ }
+
+ @Test
+ public void getHitRatio_aggregatesHitRatios() {
+ long[] hitRatios = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ incrementCounter(5,
+ () -> packStats.incrementHit(new TestKey(PackExt.PACK)));
+ hitRatios[PackExt.PACK.getPosition()] = 100;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ incrementCounter(6, () -> {
+ bitmapStats.incrementHit(new TestKey(PackExt.BITMAP_INDEX));
+ bitmapStats.incrementMiss(new TestKey(PackExt.BITMAP_INDEX));
+ });
+ hitRatios[PackExt.BITMAP_INDEX.getPosition()] = 50;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ incrementCounter(7,
+ () -> indexStats.incrementMiss(new TestKey(PackExt.INDEX)));
+ hitRatios[PackExt.INDEX.getPosition()] = 0;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getHitRatio(), hitRatios);
+ }
+
+ @Test
+ public void getEvictions_aggregatesEvictions() {
+ long[] evictions = createEmptyStatsArray();
+
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ incrementCounter(5,
+ () -> packStats.incrementEvict(new TestKey(PackExt.PACK)));
+ evictions[PackExt.PACK.getPosition()] = 5;
+
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats();
+ incrementCounter(6, () -> bitmapStats
+ .incrementEvict(new TestKey(PackExt.BITMAP_INDEX)));
+ evictions[PackExt.BITMAP_INDEX.getPosition()] = 6;
+
+ DfsBlockCacheStats indexStats = new DfsBlockCacheStats();
+ incrementCounter(7,
+ () -> indexStats.incrementEvict(new TestKey(PackExt.INDEX)));
+ evictions[PackExt.INDEX.getPosition()] = 7;
+
+ BlockCacheStats aggregatedBlockCacheStats = AggregatedBlockCacheStats
+ .fromStatsList(List.of(packStats, bitmapStats, indexStats));
+
+ assertArrayEquals(aggregatedBlockCacheStats.getEvictions(), evictions);
+ }
+
+ private static void incrementCounter(int amount, Runnable fn) {
+ for (int i = 0; i < amount; i++) {
+ fn.run();
+ }
+ }
+
+ private static long[] createEmptyStatsArray() {
+ return new long[PackExt.values().length];
+ }
+
+ private static class TestKey extends DfsStreamKey {
+ TestKey(PackExt packExt) {
+ super(0, packExt);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTableTest.java
new file mode 100644
index 0000000..2e2f86b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTableTest.java
@@ -0,0 +1,67 @@
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DEFAULT_NAME;
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.isA;
+
+import java.util.List;
+
+import org.junit.Test;
+
+public class ClockBlockCacheTableTest {
+ private static final String NAME = "name";
+
+ @Test
+ public void getName_nameNotConfigured_returnsDefaultName() {
+ ClockBlockCacheTable cacheTable = new ClockBlockCacheTable(
+ createBlockCacheConfig());
+
+ assertThat(cacheTable.getName(), equalTo(DEFAULT_NAME));
+ }
+
+ @Test
+ public void getName_nameConfigured_returnsConfiguredName() {
+ ClockBlockCacheTable cacheTable = new ClockBlockCacheTable(
+ createBlockCacheConfig().setName(NAME));
+
+ assertThat(cacheTable.getName(), equalTo(NAME));
+ }
+
+ @Test
+ public void getBlockCacheStats_nameNotConfigured_returnsBlockCacheStatsWithDefaultName() {
+ ClockBlockCacheTable cacheTable = new ClockBlockCacheTable(
+ createBlockCacheConfig());
+
+ assertThat(cacheTable.getBlockCacheStats(), hasSize(1));
+ assertThat(cacheTable.getBlockCacheStats().get(0).getName(),
+ equalTo(DEFAULT_NAME));
+ }
+
+ @Test
+ public void getBlockCacheStats_nameConfigured_returnsBlockCacheStatsWithConfiguredName() {
+ ClockBlockCacheTable cacheTable = new ClockBlockCacheTable(
+ createBlockCacheConfig().setName(NAME));
+
+ assertThat(cacheTable.getBlockCacheStats(), hasSize(1));
+ assertThat(cacheTable.getBlockCacheStats().get(0).getName(),
+ equalTo(NAME));
+ }
+
+ @Test
+ public void getAllBlockCacheStats() {
+ ClockBlockCacheTable cacheTable = new ClockBlockCacheTable(
+ createBlockCacheConfig());
+
+ List<BlockCacheStats> blockCacheStats = cacheTable.getBlockCacheStats();
+ assertThat(blockCacheStats, contains(isA(BlockCacheStats.class)));
+ }
+
+ private static DfsBlockCacheConfig createBlockCacheConfig() {
+ return new DfsBlockCacheConfig().setBlockSize(512)
+ .setConcurrencyLevel(4).setBlockLimit(1024);
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java
index c93f48d..afa3179 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfigTest.java
@@ -38,6 +38,7 @@
package org.eclipse.jgit.internal.storage.dfs;
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DEFAULT_NAME;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_CACHE_PREFIX;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
@@ -48,11 +49,17 @@
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThrows;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jgit.internal.JGitText;
@@ -173,6 +180,108 @@ public void fromConfig_generatesDfsBlockCachePackExtConfigs() {
}
@Test
+ public void fromConfig_withExistingCacheHotMap_configWithPackExtConfigsHasHotMaps() {
+ Config config = new Config();
+ addPackExtConfigEntry(config, "pack", List.of(PackExt.PACK),
+ /* blockLimit= */ 20 * 512, /* blockSize= */ 512);
+
+ addPackExtConfigEntry(config, "bitmap", List.of(PackExt.BITMAP_INDEX),
+ /* blockLimit= */ 25 * 1024, /* blockSize= */ 1024);
+
+ addPackExtConfigEntry(config, "index",
+ List.of(PackExt.INDEX, PackExt.OBJECT_SIZE_INDEX,
+ PackExt.REVERSE_INDEX),
+ /* blockLimit= */ 30 * 1024, /* blockSize= */ 1024);
+
+ Map<PackExt, Integer> cacheHotMap = Map.of(PackExt.PACK, 1,
+ PackExt.BITMAP_INDEX, 2, PackExt.INDEX, 3, PackExt.REFTABLE, 4);
+
+ DfsBlockCacheConfig cacheConfig = new DfsBlockCacheConfig();
+ cacheConfig.setCacheHotMap(cacheHotMap);
+ cacheConfig.fromConfig(config);
+
+ var configs = cacheConfig.getPackExtCacheConfigurations();
+ assertThat(cacheConfig.getCacheHotMap(), is(cacheHotMap));
+ assertThat(configs, hasSize(3));
+ var packConfig = getConfigForExt(configs, PackExt.PACK);
+ assertThat(packConfig.getCacheHotMap(), is(Map.of(PackExt.PACK, 1)));
+
+ var bitmapConfig = getConfigForExt(configs, PackExt.BITMAP_INDEX);
+ assertThat(bitmapConfig.getCacheHotMap(),
+ is(Map.of(PackExt.BITMAP_INDEX, 2)));
+
+ var indexConfig = getConfigForExt(configs, PackExt.INDEX);
+ assertThat(indexConfig.getCacheHotMap(), is(Map.of(PackExt.INDEX, 3)));
+ }
+
+ @Test
+ public void setCacheHotMap_configWithPackExtConfigs_setsHotMaps() {
+ Config config = new Config();
+ addPackExtConfigEntry(config, "pack", List.of(PackExt.PACK),
+ /* blockLimit= */ 20 * 512, /* blockSize= */ 512);
+
+ addPackExtConfigEntry(config, "bitmap", List.of(PackExt.BITMAP_INDEX),
+ /* blockLimit= */ 25 * 1024, /* blockSize= */ 1024);
+
+ addPackExtConfigEntry(config, "index",
+ List.of(PackExt.INDEX, PackExt.OBJECT_SIZE_INDEX,
+ PackExt.REVERSE_INDEX),
+ /* blockLimit= */ 30 * 1024, /* blockSize= */ 1024);
+
+ Map<PackExt, Integer> cacheHotMap = Map.of(PackExt.PACK, 1,
+ PackExt.BITMAP_INDEX, 2, PackExt.INDEX, 3, PackExt.REFTABLE, 4);
+
+ DfsBlockCacheConfig cacheConfig = new DfsBlockCacheConfig()
+ .fromConfig(config);
+ cacheConfig.setCacheHotMap(cacheHotMap);
+
+ var configs = cacheConfig.getPackExtCacheConfigurations();
+ assertThat(cacheConfig.getCacheHotMap(), is(cacheHotMap));
+ assertThat(configs, hasSize(3));
+ var packConfig = getConfigForExt(configs, PackExt.PACK);
+ assertThat(packConfig.getCacheHotMap(), is(Map.of(PackExt.PACK, 1)));
+
+ var bitmapConfig = getConfigForExt(configs, PackExt.BITMAP_INDEX);
+ assertThat(bitmapConfig.getCacheHotMap(),
+ is(Map.of(PackExt.BITMAP_INDEX, 2)));
+
+ var indexConfig = getConfigForExt(configs, PackExt.INDEX);
+ assertThat(indexConfig.getCacheHotMap(), is(Map.of(PackExt.INDEX, 3)));
+ }
+
+ @Test
+ public void fromConfigs_baseConfigOnly_nameSetFromConfigDfsSubSection() {
+ Config config = new Config();
+
+ DfsBlockCacheConfig blockCacheConfig = new DfsBlockCacheConfig()
+ .fromConfig(config);
+ assertThat(blockCacheConfig.getName(), equalTo(DEFAULT_NAME));
+ }
+
+ @Test
+ public void fromConfigs_namesSetFromConfigDfsCachePrefixSubSections() {
+ Config config = new Config();
+ config.setString(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_STREAM_RATIO, "0.5");
+ config.setString(CONFIG_CORE_SECTION, CONFIG_DFS_CACHE_PREFIX + "name1",
+ CONFIG_KEY_PACK_EXTENSIONS, PackExt.PACK.name());
+ config.setString(CONFIG_CORE_SECTION, CONFIG_DFS_CACHE_PREFIX + "name2",
+ CONFIG_KEY_PACK_EXTENSIONS, PackExt.BITMAP_INDEX.name());
+
+ DfsBlockCacheConfig blockCacheConfig = new DfsBlockCacheConfig()
+ .fromConfig(config);
+ assertThat(blockCacheConfig.getName(), equalTo("dfs"));
+ assertThat(
+ blockCacheConfig.getPackExtCacheConfigurations().get(0)
+ .getPackExtCacheConfiguration().getName(),
+ equalTo("dfs.name1"));
+ assertThat(
+ blockCacheConfig.getPackExtCacheConfigurations().get(1)
+ .getPackExtCacheConfiguration().getName(),
+ equalTo("dfs.name2"));
+ }
+
+ @Test
public void fromConfigs_dfsBlockCachePackExtConfigWithDuplicateExtensions_throws() {
Config config = new Config();
config.setString(CONFIG_CORE_SECTION, CONFIG_DFS_CACHE_PREFIX + "pack1",
@@ -216,6 +325,46 @@ public void fromConfigs_dfsBlockCachePackExtConfigWithUnknownExtensions_throws()
() -> new DfsBlockCacheConfig().fromConfig(config));
}
+ @Test
+ public void writeConfigurationDebug_writesConfigsToWriter()
+ throws Exception {
+ Config config = new Config();
+ config.setLong(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_BLOCK_LIMIT, 50 * 1024);
+ config.setInt(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_BLOCK_SIZE, 1024);
+ config.setInt(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_CONCURRENCY_LEVEL, 3);
+ config.setString(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_STREAM_RATIO, "0.5");
+ addPackExtConfigEntry(config, "pack", List.of(PackExt.PACK),
+ /* blockLimit= */ 20 * 512, /* blockSize= */ 512);
+
+ DfsBlockCacheConfig cacheConfig = new DfsBlockCacheConfig()
+ .fromConfig(config);
+ Map<PackExt, Integer> hotmap = Map.of(PackExt.PACK, 10);
+ cacheConfig.setCacheHotMap(hotmap);
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ cacheConfig.print(new PrintWriter(byteArrayOutputStream, true,
+ StandardCharsets.UTF_8));
+
+ String writenConfig = byteArrayOutputStream
+ .toString(StandardCharsets.UTF_8);
+
+ List<String> writenLines = Arrays.asList(writenConfig.split("\n"));
+ assertThat(writenLines,
+ equalTo(List.of("Name: dfs", " BlockLimit: " + (50 * 1024),
+ " BlockSize: 1024", " StreamRatio: 0.5",
+ " ConcurrencyLevel: 3",
+ " CacheHotMapEntry: " + PackExt.PACK + " : " + 10,
+ " Name: dfs.pack", " BlockLimit: " + 20 * 512,
+ " BlockSize: 512", " StreamRatio: 0.3",
+ " ConcurrencyLevel: 32",
+ " CacheHotMapEntry: " + PackExt.PACK + " : " + 10,
+ " PackExts: " + List.of(PackExt.PACK))));
+ }
+
private static void addPackExtConfigEntry(Config config, String configName,
List<PackExt> packExts, long blockLimit, int blockSize) {
String packExtConfigName = CONFIG_DFS_CACHE_PREFIX + configName;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
index fef0563..3c7cc07 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
@@ -13,20 +13,24 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.LongStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.LongStream;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DfsBlockCachePackExtConfig;
import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.IndexEventConsumer;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.junit.TestRepository;
@@ -39,14 +43,35 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class DfsBlockCacheTest {
@Rule
public TestName testName = new TestName();
+
private TestRng rng;
+
private DfsBlockCache cache;
+
private ExecutorService pool;
+ private enum CacheType {
+ SINGLE_TABLE_CLOCK_BLOCK_CACHE, EXT_SPLIT_TABLE_CLOCK_BLOCK_CACHE
+ }
+
+ @Parameters(name = "cache type: {0}")
+ public static Iterable<? extends Object> data() {
+ return Arrays.asList(CacheType.SINGLE_TABLE_CLOCK_BLOCK_CACHE,
+ CacheType.EXT_SPLIT_TABLE_CLOCK_BLOCK_CACHE);
+ }
+
+ @Parameter
+ public CacheType cacheType;
+
@Before
public void setUp() {
rng = new TestRng(testName.getMethodName());
@@ -448,8 +473,28 @@ private void resetCache() {
}
private void resetCache(int concurrencyLevel) {
- DfsBlockCache.reconfigure(new DfsBlockCacheConfig().setBlockSize(512)
- .setConcurrencyLevel(concurrencyLevel).setBlockLimit(1 << 20));
+ DfsBlockCacheConfig cacheConfig = new DfsBlockCacheConfig()
+ .setBlockSize(512).setConcurrencyLevel(concurrencyLevel)
+ .setBlockLimit(1 << 20);
+ switch (cacheType) {
+ case SINGLE_TABLE_CLOCK_BLOCK_CACHE:
+ // SINGLE_TABLE_CLOCK_BLOCK_CACHE doesn't modify the config.
+ break;
+ case EXT_SPLIT_TABLE_CLOCK_BLOCK_CACHE:
+ List<DfsBlockCachePackExtConfig> packExtCacheConfigs = new ArrayList<>();
+ for (PackExt packExt : PackExt.values()) {
+ DfsBlockCacheConfig extCacheConfig = new DfsBlockCacheConfig()
+ .setBlockSize(512).setConcurrencyLevel(concurrencyLevel)
+ .setBlockLimit(1 << 20)
+ .setPackExtCacheConfigurations(packExtCacheConfigs);
+ packExtCacheConfigs.add(new DfsBlockCachePackExtConfig(
+ EnumSet.of(packExt), extCacheConfig));
+ }
+ cacheConfig.setPackExtCacheConfigurations(packExtCacheConfigs);
+ break;
+ }
+ assertNotNull(cacheConfig);
+ DfsBlockCache.reconfigure(cacheConfig);
cache = DfsBlockCache.getInstance();
}
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 2be11d3..f9fbfe8 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
@@ -6,6 +6,7 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -15,8 +16,11 @@
import static org.junit.Assert.fail;
import java.io.IOException;
+import java.time.Instant;
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;
@@ -24,6 +28,7 @@
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.reftable.LogCursor;
import org.eclipse.jgit.internal.storage.reftable.RefCursor;
import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
@@ -37,6 +42,7 @@
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
@@ -44,6 +50,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.SystemReader;
import org.junit.After;
import org.junit.Before;
@@ -1275,6 +1282,90 @@ public void bitmapIndexWrittenDuringGc() throws Exception {
bitmapIndex.getXorBitmapCount() > 0);
}
+ @Test
+ public void gitGCWithRefLogExpire() throws Exception {
+ String master = "refs/heads/master";
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update(master, commit1);
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ run(gc);
+ 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);
+
+ try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
+ ReftableWriter w = new ReftableWriter(out);
+ w.setMinUpdateIndex(42);
+ w.setMaxUpdateIndex(42);
+ w.begin();
+ w.sortAndWriteRefs(Collections.singleton(next));
+ w.writeLog("refs/heads/branch", 1, who2, ObjectId.zeroId(),id(2), "Branch Message");
+ w.writeLog("refs/heads/branch1", 2, who3, ObjectId.zeroId(),id(3), "Branch Message1");
+ w.writeLog("refs/heads/branch2", 2, who4, ObjectId.zeroId(),id(4), "Branch Message2");
+ w.writeLog("refs/heads/branch3", 2, who5, ObjectId.zeroId(),id(5), "Branch Message3");
+ w.writeLog("refs/heads/branch4", 2, who6, ObjectId.zeroId(),id(6), "Branch Message4");
+ w.finish();
+ t1.addFileExt(REFTABLE);
+ t1.setReftableStats(w.getStats());
+ }
+ odb.commitPack(Collections.singleton(t1), null);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setReftableConfig(new ReftableConfig());
+ // Expire ref log entries older than 30 days
+ gc.setRefLogExpire(Instant.ofEpochMilli(thirty_days_ago));
+ run(gc);
+
+ // Single GC pack present with all objects.
+ assertEquals(1, odb.getPacks().length);
+ DfsPackFile pack = odb.getPacks()[0];
+ DfsPackDescription desc = pack.getPackDescription();
+
+ DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
+ try (DfsReader ctx = odb.newReader();
+ ReftableReader rr = table.open(ctx);
+ RefCursor rc = rr.allRefs();
+ LogCursor lc = rr.allLogs()) {
+ assertTrue(rc.next());
+ assertEquals(master, rc.getRef().getName());
+ assertEquals(commit1, rc.getRef().getObjectId());
+ assertTrue(rc.next());
+ assertEquals(next.getName(), rc.getRef().getName());
+ assertEquals(commit0, rc.getRef().getObjectId());
+ assertFalse(rc.next());
+ assertTrue(lc.next());
+ assertEquals(lc.getRefName(),"refs/heads/branch");
+ assertTrue(lc.next());
+ assertEquals(lc.getRefName(),"refs/heads/branch1");
+ assertTrue(lc.next());
+ assertEquals(lc.getRefName(),"refs/heads/branch2");
+ // Old entries are purged
+ assertFalse(lc.next());
+
+ }
+
+ }
+
+
private RevCommit commitChain(RevCommit parent, int length)
throws Exception {
for (int i = 0; i < length; i++) {
@@ -1364,4 +1455,12 @@ private int countPacks(PackSource source) throws IOException {
}
return cnt;
}
+ private static ObjectId id(int i) {
+ byte[] buf = new byte[OBJECT_ID_LENGTH];
+ buf[0] = (byte) (i & 0xff);
+ buf[1] = (byte) ((i >>> 8) & 0xff);
+ buf[2] = (byte) ((i >>> 16) & 0xff);
+ buf[3] = (byte) (i >>> 24);
+ return ObjectId.fromRaw(buf);
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java
index c516e30..c3b6aa8 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackCompacterTest.java
@@ -12,13 +12,18 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Optional;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -98,6 +103,40 @@ public void testEstimateGcPackSizeWithAnExistingGcPack() throws Exception {
pack.getPackDescription().getEstimatedPackSize());
}
+ @Test
+ public void testObjectSizeIndexWritten() throws Exception {
+ writeObjectSizeIndex(repo, true);
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update("master", commit1);
+
+ compact();
+
+ Optional<DfsPackFile> compactPack = Arrays.stream(odb.getPacks())
+ .filter(pack -> pack.getPackDescription()
+ .getPackSource() == COMPACT)
+ .findFirst();
+ assertTrue(compactPack.isPresent());
+ assertTrue(compactPack.get().getPackDescription().hasFileExt(OBJECT_SIZE_INDEX));
+ }
+
+ @Test
+ public void testObjectSizeIndexNotWritten() throws Exception {
+ writeObjectSizeIndex(repo, false);
+ RevCommit commit0 = commit().message("0").create();
+ RevCommit commit1 = commit().message("1").parent(commit0).create();
+ git.update("master", commit1);
+
+ compact();
+
+ Optional<DfsPackFile> compactPack = Arrays.stream(odb.getPacks())
+ .filter(pack -> pack.getPackDescription()
+ .getPackSource() == COMPACT)
+ .findFirst();
+ assertTrue(compactPack.isPresent());
+ assertFalse(compactPack.get().getPackDescription().hasFileExt(OBJECT_SIZE_INDEX));
+ }
+
private TestRepository<InMemoryRepository>.CommitBuilder commit() {
return git.commit();
}
@@ -108,4 +147,9 @@ private void compact() throws IOException {
compactor.compact(null);
odb.clearCache();
}
+
+ private static void writeObjectSizeIndex(DfsRepository repo, boolean should) {
+ repo.getConfig().setInt(ConfigConstants.CONFIG_PACK_SECTION, null,
+ ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, should ? 0 : -1);
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
index bc851f8..9680019 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileTest.java
@@ -41,6 +41,7 @@
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Before;
import org.junit.Test;
@@ -309,7 +310,7 @@ private ObjectId setupPack(int bs, int ps) throws IOException {
private void assertPackSize() throws IOException {
try (DfsReader ctx = db.getObjectDatabase().newReader();
- PackWriter pw = new PackWriter(ctx);
+ PackWriter pw = new PackWriter(new PackConfig(), ctx);
ByteArrayOutputStream os = new ByteArrayOutputStream();
PackOutputStream out = new PackOutputStream(
NullProgressMonitor.INSTANCE, os, pw)) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTableTest.java
index 8c003e0..e7627bc 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTableTest.java
@@ -10,7 +10,10 @@
package org.eclipse.jgit.internal.storage.dfs;
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;
@@ -30,13 +33,14 @@
import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.Ref;
import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.RefLoader;
import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DfsBlockCachePackExtConfig;
-import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.DfsBlockCacheStats;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.junit.Test;
import org.mockito.Mockito;
@SuppressWarnings({ "boxing", "unchecked" })
public class PackExtBlockCacheTableTest {
+ private static final String CACHE_NAME = "CacheName";
+
@Test
public void fromBlockCacheConfigs_createsDfsPackExtBlockCacheTables() {
DfsBlockCacheConfig cacheConfig = new DfsBlockCacheConfig();
@@ -385,6 +389,65 @@ public void get_packExtDoesNotMapToCacheTable_callsDefaultCache() {
}
@Test
+ public void getName() {
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats();
+ PackExtBlockCacheTable tables = PackExtBlockCacheTable.fromCacheTables(
+ cacheTableWithStats(/* name= */ "defaultName", packStats),
+ Map.of(PackExt.PACK, cacheTableWithStats(/* name= */ "packName",
+ packStats)));
+
+ assertThat(tables.getName(), equalTo("defaultName,packName"));
+ }
+
+ @Test
+ public void getAllBlockCacheStats() {
+ String defaultTableName = "default table";
+ DfsBlockCacheStats defaultStats = new DfsBlockCacheStats(
+ defaultTableName);
+ incrementCounter(4,
+ () -> defaultStats.incrementHit(new TestKey(PackExt.REFTABLE)));
+
+ String packTableName = "pack table";
+ DfsBlockCacheStats packStats = new DfsBlockCacheStats(packTableName);
+ incrementCounter(5,
+ () -> packStats.incrementHit(new TestKey(PackExt.PACK)));
+
+ String bitmapTableName = "bitmap table";
+ DfsBlockCacheStats bitmapStats = new DfsBlockCacheStats(
+ bitmapTableName);
+ incrementCounter(6, () -> bitmapStats
+ .incrementHit(new TestKey(PackExt.BITMAP_INDEX)));
+
+ DfsBlockCacheTable defaultTable = cacheTableWithStats(defaultStats);
+ DfsBlockCacheTable packTable = cacheTableWithStats(packStats);
+ DfsBlockCacheTable bitmapTable = cacheTableWithStats(bitmapStats);
+ PackExtBlockCacheTable tables = PackExtBlockCacheTable
+ .fromCacheTables(defaultTable, Map.of(PackExt.PACK, packTable,
+ PackExt.BITMAP_INDEX, bitmapTable));
+
+ List<BlockCacheStats> statsList = tables.getBlockCacheStats();
+ assertThat(statsList, hasSize(3));
+
+ long[] defaultTableHitCounts = createEmptyStatsArray();
+ defaultTableHitCounts[PackExt.REFTABLE.getPosition()] = 4;
+ assertArrayEquals(
+ getCacheStatsByName(statsList, defaultTableName).getHitCount(),
+ defaultTableHitCounts);
+
+ long[] packTableHitCounts = createEmptyStatsArray();
+ packTableHitCounts[PackExt.PACK.getPosition()] = 5;
+ assertArrayEquals(
+ getCacheStatsByName(statsList, packTableName).getHitCount(),
+ packTableHitCounts);
+
+ long[] bitmapHitCounts = createEmptyStatsArray();
+ bitmapHitCounts[PackExt.BITMAP_INDEX.getPosition()] = 6;
+ assertArrayEquals(
+ getCacheStatsByName(statsList, bitmapTableName).getHitCount(),
+ bitmapHitCounts);
+ }
+
+ @Test
public void getBlockCacheStats_getCurrentSize_consolidatesAllTableCurrentSizes() {
long[] currentSizes = createEmptyStatsArray();
@@ -406,7 +469,8 @@ public void getBlockCacheStats_getCurrentSize_consolidatesAllTableCurrentSizes()
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getCurrentSize(),
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats()).getCurrentSize(),
currentSizes);
}
@@ -435,7 +499,9 @@ public void getBlockCacheStats_GetHitCount_consolidatesAllTableHitCounts() {
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getHitCount(), hitCounts);
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats()).getHitCount(),
+ hitCounts);
}
@Test
@@ -463,7 +529,8 @@ public void getBlockCacheStats_getMissCount_consolidatesAllTableMissCounts() {
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getMissCount(),
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats()).getMissCount(),
missCounts);
}
@@ -498,8 +565,9 @@ public void getBlockCacheStats_getTotalRequestCount_consolidatesAllTableTotalReq
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getTotalRequestCount(),
- totalRequestCounts);
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats())
+ .getTotalRequestCount(), totalRequestCounts);
}
@Test
@@ -529,7 +597,9 @@ public void getBlockCacheStats_getHitRatio_consolidatesAllTableHitRatios() {
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getHitRatio(), hitRatios);
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats()).getHitRatio(),
+ hitRatios);
}
@Test
@@ -557,10 +627,21 @@ public void getBlockCacheStats_getEvictions_consolidatesAllTableEvictions() {
cacheTableWithStats(bitmapStats), PackExt.INDEX,
cacheTableWithStats(indexStats)));
- assertArrayEquals(tables.getBlockCacheStats().getEvictions(),
+ assertArrayEquals(AggregatedBlockCacheStats
+ .fromStatsList(tables.getBlockCacheStats()).getEvictions(),
evictions);
}
+ private BlockCacheStats getCacheStatsByName(
+ List<BlockCacheStats> blockCacheStats, String name) {
+ for (BlockCacheStats entry : blockCacheStats) {
+ if (entry.getName().equals(name)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
private static void incrementCounter(int amount, Runnable fn) {
for (int i = 0; i < amount; i++) {
fn.run();
@@ -572,9 +653,16 @@ private static void incrementCounter(int amount, Runnable fn) {
}
private static DfsBlockCacheTable cacheTableWithStats(
- DfsBlockCacheStats dfsBlockCacheStats) {
+ BlockCacheStats dfsBlockCacheStats) {
+ return cacheTableWithStats(CACHE_NAME, dfsBlockCacheStats);
+ }
+
+ private static DfsBlockCacheTable cacheTableWithStats(String name,
+ BlockCacheStats dfsBlockCacheStats) {
DfsBlockCacheTable cacheTable = mock(DfsBlockCacheTable.class);
- when(cacheTable.getBlockCacheStats()).thenReturn(dfsBlockCacheStats);
+ when(cacheTable.getName()).thenReturn(name);
+ when(cacheTable.getBlockCacheStats())
+ .thenReturn(List.of(dfsBlockCacheStats));
return cacheTable;
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
index bd36337..41a33df 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
@@ -29,6 +29,7 @@
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
similarity index 99%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
index ad2c891..cd73c6a 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
@@ -66,7 +66,7 @@
import org.junit.Test;
import org.mockito.Mockito;
-public class PackWriterTest extends SampleDataRepositoryTestCase {
+public class BasePackWriterTest extends SampleDataRepositoryTestCase {
private static final List<RevObject> EMPTY_LIST_REVS = Collections
.<RevObject> emptyList();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesSinceBitmapStatisticsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesSinceBitmapStatisticsTest.java
new file mode 100644
index 0000000..cd1264e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcNumberOfPackFilesSinceBitmapStatisticsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2024 Jacek Centkowski <geminica.programs@gmail.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
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.stream.StreamSupport;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class GcNumberOfPackFilesSinceBitmapStatisticsTest extends GcTestCase {
+ @Test
+ public void testShouldReportZeroObjectsForInitializedRepo()
+ throws IOException {
+ assertEquals(0L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+ }
+
+ @Test
+ public void testShouldReportAllPackFilesWhenNoGcWasPerformed()
+ throws Exception {
+ tr.packAndPrune();
+ long result = gc.getStatistics().numberOfPackFilesSinceBitmap;
+
+ assertEquals(repo.getObjectDatabase().getPacks().size(), result);
+ }
+
+ @Test
+ public void testShouldReportNoObjectsDirectlyAfterGc() throws Exception {
+ // given
+ addCommit(null);
+ gc.gc().get();
+ assertEquals(1L, repositoryBitmapFiles());
+ assertEquals(0L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+ }
+
+ @Test
+ public void testShouldReportNewObjectsSinceGcWhenRepositoryProgresses()
+ throws Exception {
+ // commit & gc
+ RevCommit parent = addCommit(null);
+ gc.gc().get();
+ assertEquals(1L, repositoryBitmapFiles());
+
+ // progress & pack
+ addCommit(parent);
+ tr.packAndPrune();
+
+ assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+ }
+
+ @Test
+ public void testShouldReportNewObjectsFromTheLatestBitmapWhenRepositoryProgresses()
+ throws Exception {
+ // commit & gc
+ RevCommit parent = addCommit(null);
+ gc.gc().get();
+ assertEquals(1L, repositoryBitmapFiles());
+
+ // progress & gc
+ parent = addCommit(parent);
+ gc.gc().get();
+ assertEquals(2L, repositoryBitmapFiles());
+
+ // progress & pack
+ addCommit(parent);
+ tr.packAndPrune();
+
+ assertEquals(1L, gc.getStatistics().numberOfPackFilesSinceBitmap);
+ }
+
+ private RevCommit addCommit(RevCommit parent) throws Exception {
+ PersonIdent ident = new PersonIdent("repo-metrics", "repo@metrics.com");
+ TestRepository<FileRepository>.CommitBuilder builder = tr.commit()
+ .author(ident);
+ if (parent != null) {
+ builder.parent(parent);
+ }
+ RevCommit commit = builder.create();
+ tr.update("master", commit);
+ parent = commit;
+ return parent;
+ }
+
+ private long repositoryBitmapFiles() throws IOException {
+ return StreamSupport
+ .stream(Files
+ .newDirectoryStream(repo.getObjectDatabase()
+ .getPackDirectory().toPath(), "pack-*.bitmap")
+ .spliterator(), false)
+ .count();
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
index c572955..f84be21 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertSame;
import java.io.File;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.BrokenBarrierException;
@@ -31,6 +30,8 @@
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.PackRefsCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@@ -49,7 +50,7 @@ public void looseRefPacked() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
- gc.packRefs();
+ packRefs(false);
assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED);
}
@@ -60,7 +61,7 @@ public void emptyRefDirectoryDeleted() throws Exception {
String name = repo.findRef(ref).getName();
Path dir = repo.getCommonDirectory().toPath().resolve(name).getParent();
assertNotNull(dir);
- gc.packRefs();
+ packRefs(true);
assertFalse(Files.exists(dir));
}
@@ -75,9 +76,9 @@ public void concurrentOnlyOneWritesPackedRefs() throws Exception {
Callable<Integer> packRefs = () -> {
syncPoint.await();
try {
- gc.packRefs();
+ packRefs(false);
return 0;
- } catch (IOException e) {
+ } catch (GitAPIException e) {
return 1;
}
};
@@ -102,7 +103,7 @@ public void whileRefLockedRefNotPackedNoError()
"refs/tags/t1"));
try {
refLock.lock();
- gc.packRefs();
+ packRefs(false);
} finally {
refLock.unlock();
}
@@ -145,7 +146,7 @@ public boolean isForceUpdate() {
Future<Result> result2 = pool.submit(() -> {
refUpdateLockedRef.await();
- gc.packRefs();
+ packRefs(false);
packRefsDone.await();
return null;
});
@@ -173,19 +174,20 @@ public void dontPackHEAD_nonBare() throws Exception {
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
"refs/heads/master");
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
- gc.packRefs();
+ PackRefsCommand packRefsCommand = git.packRefs().setAll(true);
+ packRefsCommand.call();
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
"refs/heads/master");
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
git.checkout().setName("refs/heads/side").call();
- gc.packRefs();
+ packRefsCommand.call();
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
// check for detached HEAD
git.checkout().setName(first.getName()).call();
- gc.packRefs();
+ packRefsCommand.call();
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
}
@@ -208,7 +210,7 @@ public void dontPackHEAD_bare() throws Exception {
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
"refs/heads/master");
assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
- gc.packRefs();
+ packRefs(true);
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
assertEquals(repo.exactRef("HEAD").getTarget().getName(),
"refs/heads/master");
@@ -216,9 +218,14 @@ public void dontPackHEAD_bare() throws Exception {
// check for non-detached HEAD
repo.updateRef(Constants.HEAD).link("refs/heads/side");
- gc.packRefs();
+ packRefs(true);
assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(),
second.getId());
}
+
+ private void packRefs(boolean all) throws GitAPIException {
+ new PackRefsCommand(repo).setAll(all).call();
+ }
+
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
index 49e8a7b..e067beb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
+import java.time.ZoneOffset;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -374,8 +375,10 @@ public void test008_FailOnWrongVersion() throws IOException {
public void test009_CreateCommitOldFormat() throws IOException {
final ObjectId treeId = insertTree(new TreeFormatter());
final CommitBuilder c = new CommitBuilder();
- c.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
- c.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
+ c.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
+ c.setCommitter(new PersonIdent(committer,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
c.setMessage("A Commit\n");
c.setTreeId(treeId);
assertEquals(treeId, c.getTreeId());
@@ -411,7 +414,8 @@ public void test020_createBlobTag() throws IOException {
final TagBuilder t = new TagBuilder();
t.setObjectId(emptyId, Constants.OBJ_BLOB);
t.setTag("test020");
- t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
+ t.setTagger(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
t.setMessage("test020 tagged\n");
ObjectId actid = insertTag(t);
assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", actid.name());
@@ -419,8 +423,9 @@ public void test020_createBlobTag() throws IOException {
RevTag mapTag = parseTag(actid);
assertEquals(Constants.OBJ_BLOB, mapTag.getObject().getType());
assertEquals("test020 tagged\n", mapTag.getFullMessage());
- assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
- .getTaggerIdent());
+ assertEquals(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)),
+ mapTag.getTaggerIdent());
assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag
.getObject().getId().name());
}
@@ -434,7 +439,8 @@ public void test021_createTreeTag() throws IOException {
final TagBuilder t = new TagBuilder();
t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE);
t.setTag("test021");
- t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
+ t.setTagger(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
t.setMessage("test021 tagged\n");
ObjectId actid = insertTag(t);
assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", actid.name());
@@ -442,8 +448,9 @@ public void test021_createTreeTag() throws IOException {
RevTag mapTag = parseTag(actid);
assertEquals(Constants.OBJ_TREE, mapTag.getObject().getType());
assertEquals("test021 tagged\n", mapTag.getFullMessage());
- assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
- .getTaggerIdent());
+ assertEquals(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)),
+ mapTag.getTaggerIdent());
assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag
.getObject().getId().name());
}
@@ -455,17 +462,18 @@ public void test022_createCommitTag() throws IOException {
almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId);
final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree);
final CommitBuilder almostEmptyCommit = new CommitBuilder();
- almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L,
- -2 * 60)); // not exactly the same
- almostEmptyCommit.setCommitter(new PersonIdent(author, 1154236443000L,
- -2 * 60));
+ almostEmptyCommit.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-2)));
+ almostEmptyCommit.setCommitter(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-2)));
almostEmptyCommit.setMessage("test022\n");
almostEmptyCommit.setTreeId(almostEmptyTreeId);
ObjectId almostEmptyCommitId = insertCommit(almostEmptyCommit);
final TagBuilder t = new TagBuilder();
t.setObjectId(almostEmptyCommitId, Constants.OBJ_COMMIT);
t.setTag("test022");
- t.setTagger(new PersonIdent(author, 1154236443000L, -4 * 60));
+ t.setTagger(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
t.setMessage("test022 tagged\n");
ObjectId actid = insertTag(t);
assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", actid.name());
@@ -473,8 +481,9 @@ public void test022_createCommitTag() throws IOException {
RevTag mapTag = parseTag(actid);
assertEquals(Constants.OBJ_COMMIT, mapTag.getObject().getType());
assertEquals("test022 tagged\n", mapTag.getFullMessage());
- assertEquals(new PersonIdent(author, 1154236443000L, -4 * 60), mapTag
- .getTaggerIdent());
+ assertEquals(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)),
+ mapTag.getTaggerIdent());
assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag
.getObject().getId().name());
}
@@ -488,9 +497,9 @@ public void test023_createCommitNonAnullii() throws IOException {
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(almostEmptyTreeId);
commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com",
- 4294967295000L, 60));
+ Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1)));
commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com",
- 4294967295000L, 60));
+ Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1)));
commit.setEncoding(UTF_8);
commit.setMessage("\u00dcbergeeks");
ObjectId cid = insertCommit(commit);
@@ -509,9 +518,9 @@ public void test024_createCommitNonAscii() throws IOException {
CommitBuilder commit = new CommitBuilder();
commit.setTreeId(almostEmptyTreeId);
commit.setAuthor(new PersonIdent("Joe H\u00e4cker", "joe@example.com",
- 4294967295000L, 60));
+ Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1)));
commit.setCommitter(new PersonIdent("Joe Hacker", "joe2@example.com",
- 4294967295000L, 60));
+ Instant.ofEpochMilli(4294967295000L), ZoneOffset.ofHours(1)));
commit.setEncoding(ISO_8859_1);
commit.setMessage("\u00dcbergeeks");
ObjectId cid = insertCommit(commit);
@@ -544,8 +553,10 @@ public void test026_CreateCommitMultipleparents() throws IOException {
.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), treeId);
final CommitBuilder c1 = new CommitBuilder();
- c1.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
- c1.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
+ c1.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
+ c1.setCommitter(new PersonIdent(committer,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
c1.setMessage("A Commit\n");
c1.setTreeId(treeId);
assertEquals(treeId, c1.getTreeId());
@@ -555,8 +566,10 @@ public void test026_CreateCommitMultipleparents() throws IOException {
assertEquals(cmtid1, actid1);
final CommitBuilder c2 = new CommitBuilder();
- c2.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
- c2.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
+ c2.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
+ c2.setCommitter(new PersonIdent(committer,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
c2.setMessage("A Commit 2\n");
c2.setTreeId(treeId);
assertEquals(treeId, c2.getTreeId());
@@ -577,8 +590,10 @@ public void test026_CreateCommitMultipleparents() throws IOException {
assertEquals(actid1, rm2.getParent(0));
final CommitBuilder c3 = new CommitBuilder();
- c3.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
- c3.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
+ c3.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
+ c3.setCommitter(new PersonIdent(committer,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
c3.setMessage("A Commit 3\n");
c3.setTreeId(treeId);
assertEquals(treeId, c3.getTreeId());
@@ -600,8 +615,10 @@ public void test026_CreateCommitMultipleparents() throws IOException {
assertEquals(actid2, rm3.getParent(1));
final CommitBuilder c4 = new CommitBuilder();
- c4.setAuthor(new PersonIdent(author, 1154236443000L, -4 * 60));
- c4.setCommitter(new PersonIdent(committer, 1154236443000L, -4 * 60));
+ c4.setAuthor(new PersonIdent(author,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
+ c4.setCommitter(new PersonIdent(committer,
+ Instant.ofEpochMilli(1154236443000L), ZoneOffset.ofHours(-4)));
c4.setMessage("A Commit 4\n");
c4.setTreeId(treeId);
assertEquals(treeId, c3.getTreeId());
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
index 97da175..943a68b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PersonIdentTest.java
@@ -55,7 +55,8 @@ public void testNewIdentInstant() {
p.getWhenAsInstant());
assertEquals("A U Thor <author@example.com> 1142878501 -0500",
p.toExternalString());
- assertEquals(ZoneId.of("GMT-05:00"), p.getZoneId());
+ assertEquals(ZoneId.of("GMT-05:00").getRules().getOffset(
+ Instant.ofEpochMilli(1142878501000L)), p.getZoneOffset());
}
@Test
@@ -69,7 +70,8 @@ public void testNewIdentInstant2() {
p.getWhenAsInstant());
assertEquals("A U Thor <author@example.com> 1142878501 +0530",
p.toExternalString());
- assertEquals(ZoneId.of("GMT+05:30"), p.getZoneId());
+ assertEquals(ZoneId.of("GMT+05:30").getRules().getOffset(
+ Instant.ofEpochMilli(1142878501000L)), p.getZoneOffset());
}
@SuppressWarnings("unused")
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
index 3a036ac..c6a6321 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergerTest.java
@@ -1792,7 +1792,77 @@ public void checkModeMergeConflictInVirtualAncestor(MergeStrategy strategy) thro
// children
mergeResult = git.merge().include(commitC3S).call();
assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
+ }
+ /**
+ * Merging two commits when binary files have equal content, but conflicting content in the
+ * virtual ancestor.
+ *
+ * <p>
+ * This test has the same set up as
+ * {@code checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren}, only
+ * with the content conflict in A1 and A2.
+ */
+ @Theory
+ public void checkBinaryMergeConflictInVirtualAncestor(MergeStrategy strategy) throws Exception {
+ if (!strategy.equals(MergeStrategy.RECURSIVE)) {
+ return;
+ }
+
+ Git git = Git.wrap(db);
+
+ // master
+ writeTrashFile("c", "initial file");
+ git.add().addFilepattern("c").call();
+ RevCommit commitI = git.commit().setMessage("Initial commit").call();
+
+ writeTrashFile("a", "\0\1\1\1\1\0"); // content in Ancestor 1
+ git.add().addFilepattern("a").call();
+ RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call();
+
+ writeTrashFile("a", "\0\1\2\3\4\5\0"); // content in Child 1 (commited on master)
+ git.add().addFilepattern("a").call();
+ // commit C1M
+ git.commit().setMessage("Child 1 on master").call();
+
+ git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call();
+ writeTrashFile("a", "\0\2\2\2\2\0"); // content in Ancestor 1
+ git.add().addFilepattern("a").call();
+ RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call();
+
+ // second branch
+ git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call();
+ writeTrashFile("a", "\0\5\4\3\2\1\0"); // content in Child 2 (commited on second-branch)
+ git.add().addFilepattern("a").call();
+ // commit C2S
+ git.commit().setMessage("Child 2 on second-branch").call();
+
+ // Merge branch-to-merge into second-branch
+ MergeResult mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
+ assertEquals(mergeResult.getNewHead(), null);
+ assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
+ // Resolve the conflict manually
+ writeTrashFile("a", "\0\3\3\3\3\0"); // merge conflict resolution
+ git.add().addFilepattern("a").call();
+ RevCommit commitC3S = git.commit().setMessage("Child 3 on second bug - resolve merge conflict").call();
+
+ // Merge branch-to-merge into master
+ git.checkout().setName("master").call();
+ mergeResult = git.merge().include(commitA2).setStrategy(strategy).call();
+ assertEquals(mergeResult.getNewHead(), null);
+ assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING);
+
+ // Resolve the conflict manually - set the same value as in resolution above
+ writeTrashFile("a", "\0\3\3\3\3\0"); // merge conflict resolution
+ git.add().addFilepattern("a").call();
+ // commit C4M
+ git.commit().setMessage("Child 4 on master - resolve merge conflict").call();
+
+ // Merge C4M (second-branch) into master (C3S)
+ // Conflict in virtual base should be here, but there are no conflicts in
+ // children
+ mergeResult = git.merge().include(commitC3S).call();
+ assertEquals(mergeResult.getMergeStatus(), MergeStatus.MERGED);
}
/**
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 2bd4003..aaecfd2 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
@@ -2862,7 +2862,7 @@ public void testSingleBranchCloneTagChain() throws Exception {
RevTag heavyTag2 = remote.tag("middleTagRing", heavyTag1);
remote.lightweightTag("refTagRing", heavyTag2);
- UploadPack uploadPack = new UploadPack(remote.getRepository());
+ try (UploadPack uploadPack = new UploadPack(remote.getRepository())) {
ByteArrayOutputStream cli = new ByteArrayOutputStream();
PacketLineOut clientWant = new PacketLineOut(cli);
@@ -2872,7 +2872,6 @@ public void testSingleBranchCloneTagChain() throws Exception {
clientWant.writeString("done\n");
try (ByteArrayOutputStream serverResponse = new ByteArrayOutputStream()) {
-
uploadPack.setPreUploadHook(new PreUploadHook() {
@Override
public void onBeginNegotiateRound(UploadPack up,
@@ -2925,6 +2924,7 @@ public void onSendPack(UploadPack up,
assertTrue(objDb.has(heavyTag2.toObjectId()));
}
}
+}
@Test
public void testSingleBranchShallowCloneTagChainWithReflessTag() throws Exception {
@@ -2936,7 +2936,7 @@ public void testSingleBranchShallowCloneTagChainWithReflessTag() throws Exceptio
RevTag tag3 = remote.tag("t3", tag2);
remote.lightweightTag("t3", tag3);
- UploadPack uploadPack = new UploadPack(remote.getRepository());
+ try (UploadPack uploadPack = new UploadPack(remote.getRepository())) {
ByteArrayOutputStream cli = new ByteArrayOutputStream();
PacketLineOut clientWant = new PacketLineOut(cli);
@@ -2946,7 +2946,6 @@ public void testSingleBranchShallowCloneTagChainWithReflessTag() throws Exceptio
clientWant.writeString("done\n");
try (ByteArrayOutputStream serverResponse = new ByteArrayOutputStream()) {
-
uploadPack.setPreUploadHook(new PreUploadHook() {
@Override
public void onBeginNegotiateRound(UploadPack up,
@@ -2994,6 +2993,7 @@ public void onSendPack(UploadPack up,
assertTrue(objDb.has(one.toObjectId()));
}
}
+}
@Test
public void testSafeToClearRefsInFetchV0() throws Exception {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java
new file mode 100644
index 0000000..a59d7bc
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserBadlyFormattedTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012, Christian Halstrick 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.util;
+
+import static org.junit.Assert.assertThrows;
+
+import java.text.ParseException;
+
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests which assert that unparseable Strings lead to ParseExceptions
+ */
+@RunWith(Theories.class)
+public class GitTimeParserBadlyFormattedTest {
+ private String dateStr;
+
+ @Before
+ public void setUp() {
+ MockSystemReader mockSystemReader = new MockSystemReader();
+ SystemReader.setInstance(mockSystemReader);
+ }
+
+ @After
+ public void tearDown() {
+ SystemReader.setInstance(null);
+ }
+
+ public GitTimeParserBadlyFormattedTest(String dateStr) {
+ this.dateStr = dateStr;
+ }
+
+ @DataPoints
+ public static String[] getDataPoints() {
+ return new String[] { "", ".", "...", "1970", "3000.3000.3000", "3 yesterday ago",
+ "now yesterday ago", "yesterdays", "3.day. 2.week.ago",
+ "day ago", "Gra Feb 21 15:35:00 2007 +0100",
+ "Sun Feb 21 15:35:00 2007 +0100",
+ "Wed Feb 21 15:35:00 Grand +0100" };
+ }
+
+ @Theory
+ public void badlyFormattedWithoutRef() {
+ assertThrows(
+ "The expected ParseException while parsing '" + dateStr
+ + "' did not occur.",
+ ParseException.class, () -> GitTimeParser.parse(dateStr));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserTest.java
new file mode 100644
index 0000000..0e5eb28
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitTimeParserTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2024, Christian Halstrick 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.text.ParseException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Period;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+
+import org.eclipse.jgit.junit.MockSystemReader;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GitTimeParserTest {
+ MockSystemReader mockSystemReader;
+
+ @Before
+ public void setUp() {
+ mockSystemReader = new MockSystemReader();
+ SystemReader.setInstance(mockSystemReader);
+ }
+
+ @After
+ public void tearDown() {
+ SystemReader.setInstance(null);
+ }
+
+ @Test
+ public void yesterday() throws ParseException {
+ LocalDateTime parse = GitTimeParser.parse("yesterday");
+
+ LocalDateTime now = SystemReader.getInstance().civilNow();
+ assertEquals(Period.between(parse.toLocalDate(), now.toLocalDate()),
+ Period.ofDays(1));
+ }
+
+ @Test
+ public void never() throws ParseException {
+ LocalDateTime parse = GitTimeParser.parse("never");
+ assertEquals(LocalDateTime.MAX, parse);
+ }
+
+ @Test
+ public void now_pointInTime() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 15:35:00 +0100");
+
+ LocalDateTime parsedNow = GitTimeParser.parse("now", aTime);
+
+ assertEquals(aTime, parsedNow);
+ }
+
+ @Test
+ public void now_systemTime() throws ParseException {
+ LocalDateTime firstNow = GitTimeParser.parse("now");
+ assertEquals(SystemReader.getInstance().civilNow(), firstNow);
+ mockSystemReader.tick(10);
+ LocalDateTime secondNow = GitTimeParser.parse("now");
+ assertTrue(secondNow.isAfter(firstNow));
+ }
+
+ @Test
+ public void weeksAgo() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 15:35:00 +0100");
+
+ LocalDateTime parse = GitTimeParser.parse("2 weeks ago", aTime);
+ assertEquals(asLocalDateTime("2007-02-07 15:35:00 +0100"), parse);
+ }
+
+ @Test
+ public void daysAndWeeksAgo() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 15:35:00 +0100");
+
+ LocalDateTime twoWeeksAgoActual = GitTimeParser.parse("2 weeks ago",
+ aTime);
+
+ LocalDateTime twoWeeksAgoExpected = asLocalDateTime(
+ "2007-02-07 15:35:00 +0100");
+ assertEquals(twoWeeksAgoExpected, twoWeeksAgoActual);
+
+ LocalDateTime combinedWhitespace = GitTimeParser
+ .parse("3 days 2 weeks ago", aTime);
+ LocalDateTime combinedWhitespaceExpected = asLocalDateTime(
+ "2007-02-04 15:35:00 +0100");
+ assertEquals(combinedWhitespaceExpected, combinedWhitespace);
+
+ LocalDateTime combinedDots = GitTimeParser.parse("3.day.2.week.ago",
+ aTime);
+ LocalDateTime combinedDotsExpected = asLocalDateTime(
+ "2007-02-04 15:35:00 +0100");
+ assertEquals(combinedDotsExpected, combinedDots);
+ }
+
+ @Test
+ public void hoursAgo() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 17:35:00 +0100");
+
+ LocalDateTime twoHoursAgoActual = GitTimeParser.parse("2 hours ago",
+ aTime);
+
+ LocalDateTime twoHoursAgoExpected = asLocalDateTime(
+ "2007-02-21 15:35:00 +0100");
+ assertEquals(twoHoursAgoExpected, twoHoursAgoActual);
+ }
+
+ @Test
+ public void hoursAgo_acrossDay() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 00:35:00 +0100");
+
+ LocalDateTime twoHoursAgoActual = GitTimeParser.parse("2 hours ago",
+ aTime);
+
+ LocalDateTime twoHoursAgoExpected = asLocalDateTime(
+ "2007-02-20 22:35:00 +0100");
+ assertEquals(twoHoursAgoExpected, twoHoursAgoActual);
+ }
+
+ @Test
+ public void minutesHoursAgoCombined() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-04 15:35:00 +0100");
+
+ LocalDateTime combinedWhitespace = GitTimeParser
+ .parse("3 hours 2 minutes ago", aTime);
+ LocalDateTime combinedWhitespaceExpected = asLocalDateTime(
+ "2007-02-04 12:33:00 +0100");
+ assertEquals(combinedWhitespaceExpected, combinedWhitespace);
+
+ LocalDateTime combinedDots = GitTimeParser
+ .parse("3.hours.2.minutes.ago", aTime);
+ LocalDateTime combinedDotsExpected = asLocalDateTime(
+ "2007-02-04 12:33:00 +0100");
+ assertEquals(combinedDotsExpected, combinedDots);
+ }
+
+ @Test
+ public void minutesAgo() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 17:35:10 +0100");
+
+ LocalDateTime twoMinutesAgo = GitTimeParser.parse("2 minutes ago",
+ aTime);
+
+ LocalDateTime twoMinutesAgoExpected = asLocalDateTime(
+ "2007-02-21 17:33:10 +0100");
+ assertEquals(twoMinutesAgoExpected, twoMinutesAgo);
+ }
+
+ @Test
+ public void minutesAgo_acrossDay() throws ParseException {
+ LocalDateTime aTime = asLocalDateTime("2007-02-21 00:35:10 +0100");
+
+ LocalDateTime minutesAgoActual = GitTimeParser.parse("40 minutes ago",
+ aTime);
+
+ LocalDateTime minutesAgoExpected = asLocalDateTime(
+ "2007-02-20 23:55:10 +0100");
+ assertEquals(minutesAgoExpected, minutesAgoActual);
+ }
+
+ @Test
+ public void iso() throws ParseException {
+ String dateStr = "2007-02-21 15:35:00 +0100";
+
+ LocalDateTime actual = GitTimeParser.parse(dateStr);
+
+ LocalDateTime expected = asLocalDateTime(dateStr);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void rfc() throws ParseException {
+ String dateStr = "Wed, 21 Feb 2007 15:35:00 +0100";
+
+ LocalDateTime actual = GitTimeParser.parse(dateStr);
+
+ LocalDateTime expected = asLocalDateTime(dateStr,
+ "EEE, dd MMM yyyy HH:mm:ss Z");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void shortFmt() throws ParseException {
+ assertParsing("2007-02-21", "yyyy-MM-dd");
+ }
+
+ @Test
+ public void shortWithDots() throws ParseException {
+ assertParsing("2007.02.21", "yyyy.MM.dd");
+ }
+
+ @Test
+ public void shortWithSlash() throws ParseException {
+ assertParsing("02/21/2007", "MM/dd/yyyy");
+ }
+
+ @Test
+ public void shortWithDotsReverse() throws ParseException {
+ assertParsing("21.02.2007", "dd.MM.yyyy");
+ }
+
+ @Test
+ public void defaultFmt() throws ParseException {
+ assertParsing("Wed Feb 21 15:35:00 2007 +0100",
+ "EEE MMM dd HH:mm:ss yyyy Z");
+ }
+
+ @Test
+ public void local() throws ParseException {
+ assertParsing("Wed Feb 21 15:35:00 2007", "EEE MMM dd HH:mm:ss yyyy");
+ }
+
+ private static void assertParsing(String dateStr, String format)
+ throws ParseException {
+ LocalDateTime actual = GitTimeParser.parse(dateStr);
+
+ LocalDateTime expected = asLocalDateTime(dateStr, format);
+ assertEquals(expected, actual);
+ }
+
+ private static LocalDateTime asLocalDateTime(String dateStr) {
+ return asLocalDateTime(dateStr, "yyyy-MM-dd HH:mm:ss Z");
+ }
+
+ private static LocalDateTime asLocalDateTime(String dateStr,
+ String pattern) {
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
+ TemporalAccessor ta = fmt
+ .withZone(SystemReader.getInstance().getTimeZoneId())
+ .withLocale(SystemReader.getInstance().getLocale())
+ .parse(dateStr);
+ return ta.isSupported(ChronoField.HOUR_OF_DAY) ? LocalDateTime.from(ta)
+ : LocalDate.from(ta).atStartOfDay();
+ }
+}
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 355bbba..e517889 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
@@ -10,10 +10,13 @@
package org.eclipse.jgit.util;
+import static java.time.Instant.EPOCH;
+import static java.time.ZoneOffset.UTC;
import static org.junit.Assert.assertEquals;
-import java.util.Date;
-import java.util.TimeZone;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import org.eclipse.jgit.lib.PersonIdent;
import org.junit.Test;
@@ -22,8 +25,8 @@ public class RawParseUtils_ParsePersonIdentTest {
@Test
public void testParsePersonIdent_legalCases() {
- final Date when = new Date(1234567890000L);
- final TimeZone tz = TimeZone.getTimeZone("GMT-7");
+ Instant when = Instant.ofEpochMilli(1234567890000L);
+ ZoneId tz = ZoneOffset.ofHours(-7);
assertPersonIdent("Me <me@example.com> 1234567890 -0700",
new PersonIdent("Me", "me@example.com", when, tz));
@@ -50,8 +53,8 @@ public void testParsePersonIdent_legalCases() {
@Test
public void testParsePersonIdent_fuzzyCases() {
- final Date when = new Date(1234567890000L);
- final TimeZone tz = TimeZone.getTimeZone("GMT-7");
+ Instant when = Instant.ofEpochMilli(1234567890000L);
+ ZoneId tz = ZoneOffset.ofHours(-7);
assertPersonIdent(
"A U Thor <author@example.com>, C O. Miter <comiter@example.com> 1234567890 -0700",
@@ -64,8 +67,8 @@ public void testParsePersonIdent_fuzzyCases() {
@Test
public void testParsePersonIdent_incompleteCases() {
- final Date when = new Date(1234567890000L);
- final TimeZone tz = TimeZone.getTimeZone("GMT-7");
+ Instant when = Instant.ofEpochMilli(1234567890000L);
+ ZoneId tz = ZoneOffset.ofHours(-7);
assertPersonIdent("Me <> 1234567890 -0700", new PersonIdent("Me", "",
when, tz));
@@ -76,26 +79,26 @@ public void testParsePersonIdent_incompleteCases() {
assertPersonIdent(" <> 1234567890 -0700", new PersonIdent("", "", when,
tz));
- assertPersonIdent("<>", new PersonIdent("", "", 0, 0));
+ assertPersonIdent("<>", new PersonIdent("", "", EPOCH, UTC));
- assertPersonIdent(" <>", new PersonIdent("", "", 0, 0));
+ assertPersonIdent(" <>", new PersonIdent("", "", EPOCH, UTC));
assertPersonIdent("<me@example.com>", new PersonIdent("",
- "me@example.com", 0, 0));
+ "me@example.com", EPOCH, UTC));
assertPersonIdent(" <me@example.com>", new PersonIdent("",
- "me@example.com", 0, 0));
+ "me@example.com", EPOCH, UTC));
- assertPersonIdent("Me <>", new PersonIdent("Me", "", 0, 0));
+ assertPersonIdent("Me <>", new PersonIdent("Me", "", EPOCH, UTC));
assertPersonIdent("Me <me@example.com>", new PersonIdent("Me",
- "me@example.com", 0, 0));
+ "me@example.com", EPOCH, UTC));
assertPersonIdent("Me <me@example.com> 1234567890", new PersonIdent(
- "Me", "me@example.com", 0, 0));
+ "Me", "me@example.com", EPOCH, UTC));
assertPersonIdent("Me <me@example.com> 1234567890 ", new PersonIdent(
- "Me", "me@example.com", 0, 0));
+ "Me", "me@example.com", EPOCH, UTC));
}
@Test
diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF
index 7c98d77..399977a 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.0.2.qualifier
+Bundle-Version: 7.1.2.qualifier
Bundle-Vendor: %Bundle-Vendor
Bundle-RequiredExecutionEnvironment: JavaSE-17
-Export-Package: org.eclipse.jgit.awtui;version="7.0.2"
-Import-Package: org.eclipse.jgit.errors;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.lib;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.nls;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revplot;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.revwalk;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.transport;version="[7.0.2,7.1.0)",
- org.eclipse.jgit.util;version="[7.0.2,7.1.0)"
+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)"
diff --git a/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/SOURCE-MANIFEST.MF
index 0d64d01..7ed704c 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit.ui;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit.ui;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml
index ea73b43..90f74a4 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.0.2-SNAPSHOT</version>
+ <version>7.1.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
new file mode 100644
index 0000000..023018e
--- /dev/null
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -0,0 +1,91 @@
+<?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">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="org.eclipse.jgit.diff.DiffDriver"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/diff/DiffFormatter.java" type="org.eclipse.jgit.diff.DiffFormatter">
+ <filter id="1142947843">
+ <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_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="ATTR_BUILTIN_UNION_MERGE_DRIVER"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/merge/ContentMergeStrategy.java" type="org.eclipse.jgit.merge.ContentMergeStrategy">
+ <filter id="1176502275">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="UNION"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
+ <filter id="336658481">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
+ <message_argument value="attributesNodeProvider"/>
+ </message_arguments>
+ </filter>
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="attributesNodeProvider"/>
+ </message_arguments>
+ </filter>
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="setAttributesNodeProvider(AttributesNodeProvider)"/>
+ </message_arguments>
+ </filter>
+ </resource>
+ <resource path="src/org/eclipse/jgit/revwalk/RevWalk.java" type="org.eclipse.jgit.revwalk.RevWalk">
+ <filter id="1142947843">
+ <message_arguments>
+ <message_argument value="6.10.1"/>
+ <message_argument value="isMergedIntoAnyCommit(RevCommit, Collection<RevCommit>)"/>
+ </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>
+ <message_argument value="6.10.2"/>
+ <message_argument value="org.eclipse.jgit.util.Iterators"/>
+ </message_arguments>
+ </filter>
+ </resource>
+</component>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index c4dc76f..ef3d8ec 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -40,7 +40,7 @@
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
-org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 0d4f772..1cce9dc 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.0.2.qualifier
+Bundle-Version: 7.1.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.0.2",
- org.eclipse.jgit.api;version="7.0.2";
+Export-Package: org.eclipse.jgit.annotations;version="7.1.2",
+ org.eclipse.jgit.api;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.notes,
org.eclipse.jgit.dircache,
@@ -25,18 +25,18 @@
org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.blame,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="7.0.2";
+ org.eclipse.jgit.api.errors;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="7.0.2";
+ org.eclipse.jgit.attributes;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="7.0.2";
+ org.eclipse.jgit.blame;version="7.1.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.0.2";
+ org.eclipse.jgit.diff;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
@@ -44,53 +44,53 @@
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="7.0.2";
+ org.eclipse.jgit.dircache;version="7.1.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.0.2";
+ org.eclipse.jgit.errors;version="7.1.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.0.2";
+ org.eclipse.jgit.events;version="7.1.2";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="7.0.2",
- org.eclipse.jgit.gitrepo;version="7.0.2";
+ org.eclipse.jgit.fnmatch;version="7.1.2",
+ org.eclipse.jgit.gitrepo;version="7.1.2";
uses:="org.xml.sax.helpers,
org.eclipse.jgit.api,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="7.0.2";x-internal:=true,
- org.eclipse.jgit.hooks;version="7.0.2";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="7.0.2",
- org.eclipse.jgit.ignore.internal;version="7.0.2";
+ 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";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="7.0.2";
+ org.eclipse.jgit.internal;version="7.1.2";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.diff;version="7.0.2";
+ org.eclipse.jgit.internal.diff;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.diffmergetool;version="7.0.2";
+ org.eclipse.jgit.internal.diffmergetool;version="7.1.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.0.2";
+ org.eclipse.jgit.internal.fsck;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="7.0.2";
+ org.eclipse.jgit.internal.revwalk;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.commitgraph;version="7.0.2";
+ org.eclipse.jgit.internal.storage.commitgraph;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="7.0.2";
+ org.eclipse.jgit.internal.storage.dfs;version="7.1.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.0.2";
+ org.eclipse.jgit.internal.storage.file;version="7.1.2";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
@@ -102,36 +102,36 @@
org.eclipse.jgit.ssh.apache,
org.eclipse.jgit.ssh.apache.test,
org.eclipse.jgit.ssh.jsch.test",
- org.eclipse.jgit.internal.storage.io;version="7.0.2";
+ org.eclipse.jgit.internal.storage.io;version="7.1.2";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.memory;version="7.0.2";
+ org.eclipse.jgit.internal.storage.memory;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.pack;version="7.0.2";
+ org.eclipse.jgit.internal.storage.pack;version="7.1.2";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="7.0.2";
+ org.eclipse.jgit.internal.storage.reftable;version="7.1.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.0.2";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="7.0.2";
+ org.eclipse.jgit.internal.submodule;version="7.1.2";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="7.0.2";
+ org.eclipse.jgit.internal.transport.http;version="7.1.2";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="7.0.2";
+ org.eclipse.jgit.internal.transport.parser;version="7.1.2";
x-friends:="org.eclipse.jgit.http.server,
org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="7.0.2";
+ org.eclipse.jgit.internal.transport.ssh;version="7.1.2";
x-friends:="org.eclipse.jgit.ssh.apache,
org.eclipse.jgit.ssh.jsch,
org.eclipse.jgit.test",
- org.eclipse.jgit.internal.util;version="7.0.2";
+ org.eclipse.jgit.internal.util;version="7.1.2";
x-friends:=" org.eclipse.jgit.junit",
- org.eclipse.jgit.lib;version="7.0.2";
+ org.eclipse.jgit.lib;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util.sha1,
org.eclipse.jgit.dircache,
@@ -145,12 +145,12 @@
org.eclipse.jgit.util,
org.eclipse.jgit.submodule,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="7.0.2";
+ org.eclipse.jgit.lib.internal;version="7.1.2";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm,
org.eclipse.egit.ui",
- org.eclipse.jgit.logging;version="7.0.2",
- org.eclipse.jgit.merge;version="7.0.2";
+ org.eclipse.jgit.logging;version="7.1.2",
+ org.eclipse.jgit.merge;version="7.1.2";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
@@ -159,40 +159,40 @@
org.eclipse.jgit.util,
org.eclipse.jgit.api,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="7.0.2",
- org.eclipse.jgit.notes;version="7.0.2";
+ org.eclipse.jgit.nls;version="7.1.2",
+ org.eclipse.jgit.notes;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="7.0.2";
+ org.eclipse.jgit.patch;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="7.0.2";
+ org.eclipse.jgit.revplot;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="7.0.2";
+ 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.0.2";
+ org.eclipse.jgit.revwalk.filter;version="7.1.2";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="7.0.2";
+ org.eclipse.jgit.storage.file;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="7.0.2";
+ org.eclipse.jgit.storage.pack;version="7.1.2";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="7.0.2";
+ org.eclipse.jgit.submodule;version="7.1.2";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.transport;version="7.0.2";
+ org.eclipse.jgit.transport;version="7.1.2";
uses:="javax.crypto,
org.eclipse.jgit.util.io,
org.eclipse.jgit.lib,
@@ -205,21 +205,21 @@
org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.storage.pack,
org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="7.0.2";
+ org.eclipse.jgit.transport.http;version="7.1.2";
uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="7.0.2";
+ org.eclipse.jgit.transport.resolver;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="7.0.2";
+ org.eclipse.jgit.treewalk;version="7.1.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.0.2";
+ org.eclipse.jgit.treewalk.filter;version="7.1.2";
uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="7.0.2";
+ org.eclipse.jgit.util;version="7.1.2";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.hooks,
org.eclipse.jgit.revwalk,
@@ -232,12 +232,12 @@
org.eclipse.jgit.treewalk,
javax.net.ssl,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="7.0.2";
+ org.eclipse.jgit.util.io;version="7.1.2";
uses:="org.eclipse.jgit.attributes,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="7.0.2",
- org.eclipse.jgit.util.time;version="7.0.2"
+ org.eclipse.jgit.util.sha1;version="7.1.2",
+ org.eclipse.jgit.util.time;version="7.1.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 2315007..1bf5168 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.0.2.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="7.0.2.qualifier";roots="."
+Bundle-Version: 7.1.2.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="7.1.2.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index a18b1fd..df45481 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.0.2-SNAPSHOT</version>
+ <version>7.1.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 fd0995a..4e2073b 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -500,7 +500,7 @@
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
-mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
+mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}\nFailing paths: {2}
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
mergeToolNotGivenError=No merge tool provided and no defaults configured.
mergeToolNullError=Parameter for merge tool cannot be null.
@@ -595,6 +595,8 @@
packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
packRefs=Pack refs
+packRefsFailed=Packing refs failed
+packRefsSuccessful=Packed refs successfully
packSizeNotSetYet=Pack size not yet set since it has not yet been received
packTooLargeForIndexVersion1=Pack too large for index version 1
packWasDeleted=Pack file {0} was deleted, removing it from pack list
@@ -638,8 +640,6 @@
readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
readLastModifiedFailed=Reading lastModified of {0} failed
-readPipeIsNotAllowed=FS.readPipe() isn't allowed for command ''{0}''. Working directory: ''{1}''.
-readPipeIsNotAllowedRequiredPermission=FS.readPipe() isn't allowed for command ''{0}''. Working directory: ''{1}''. Required permission: {2}.
readTimedOut=Read timed out after {0} ms
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 3dc53ec..5bc035a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -714,6 +714,16 @@ public GarbageCollectCommand gc() {
}
/**
+ * Return a command object to execute a {@code PackRefs} command
+ *
+ * @return a {@link org.eclipse.jgit.api.PackRefsCommand}
+ * @since 7.1
+ */
+ public PackRefsCommand packRefs() {
+ return new PackRefsCommand(repo);
+ }
+
+ /**
* Return a command object to find human-readable names of revisions.
*
* @return a {@link org.eclipse.jgit.api.NameRevCommand}.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java
new file mode 100644
index 0000000..29a69c5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.api;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Optimize storage of references.
+ *
+ * @since 7.1
+ */
+public class PackRefsCommand extends GitCommand<String> {
+ private ProgressMonitor monitor;
+
+ private boolean all;
+
+ /**
+ * Creates a new {@link PackRefsCommand} instance with default values.
+ *
+ * @param repo
+ * the repository this command will be used on
+ */
+ public PackRefsCommand(Repository repo) {
+ super(repo);
+ this.monitor = NullProgressMonitor.INSTANCE;
+ }
+
+ /**
+ * Set progress monitor
+ *
+ * @param monitor
+ * a progress monitor
+ * @return this instance
+ */
+ public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) {
+ this.monitor = monitor;
+ return this;
+ }
+
+ /**
+ * Specify whether to pack all the references.
+ *
+ * @param all
+ * if <code>true</code> all the loose refs will be packed
+ * @return this instance
+ */
+ public PackRefsCommand setAll(boolean all) {
+ this.all = all;
+ return this;
+ }
+
+ /**
+ * Whether to pack all the references
+ *
+ * @return whether to pack all the references
+ */
+ public boolean isAll() {
+ return all;
+ }
+
+ @Override
+ public String call() throws GitAPIException {
+ checkCallable();
+ try {
+ repo.getRefDatabase().packRefs(monitor, this);
+ return JGitText.get().packRefsSuccessful;
+ } catch (IOException e) {
+ throw new JGitInternalException(JGitText.get().packRefsFailed, e);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
index 76dc87e..fdfe533 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
@@ -360,18 +360,22 @@ public static boolean isBinary(byte[] raw, int length, boolean complete) {
length = maxLength;
isComplete = false;
}
- byte last = 'x'; // Just something inconspicuous.
- for (int ptr = 0; ptr < length; ptr++) {
- byte curr = raw[ptr];
- if (isBinary(curr, last)) {
+
+ int ptr = -1;
+ byte current;
+ while (ptr < length - 2) {
+ current = raw[++ptr];
+ if (current == '\0' || (current == '\r' && raw[++ptr] != '\n')) {
return true;
}
- last = curr;
}
- if (isComplete) {
- // Buffer contains everything...
- return last == '\r'; // ... so this must be a lone CR
+
+ if (ptr == length - 2) {
+ // if '\r' be last, then if isComplete then return binary
+ current = raw[++ptr];
+ return current == '\0' || (current == '\r' && isComplete);
}
+
return false;
}
@@ -467,26 +471,30 @@ public static boolean isCrLfText(byte[] raw, int length) {
*/
public static boolean isCrLfText(byte[] raw, int length, boolean complete) {
boolean has_crlf = false;
- byte last = 'x'; // Just something inconspicuous
- for (int ptr = 0; ptr < length; ptr++) {
- byte curr = raw[ptr];
- if (isBinary(curr, last)) {
+
+ int ptr = -1;
+ byte current;
+ while (ptr < length - 2) {
+ current = raw[++ptr];
+ if (current == '\0') {
return false;
}
- if (curr == '\n' && last == '\r') {
+ if (current == '\r') {
+ if (raw[++ptr] != '\n') {
+ return false;
+ }
has_crlf = true;
}
- last = curr;
}
- if (last == '\r') {
- if (complete) {
- // Lone CR: it's binary after all.
+
+ if (ptr == length - 2) {
+ // if '\r' be last, then if isComplete then return binary
+ current = raw[++ptr];
+ if (current == '\0' || (current == '\r' && complete)) {
return false;
}
- // Tough call. If the next byte, which we don't have, would be a
- // '\n', it'd be a CR-LF text, otherwise it'd be binary. Just decide
- // based on what we already scanned; it wasn't binary until now.
}
+
return has_crlf;
}
@@ -578,4 +586,5 @@ public static RawText load(ObjectLoader ldr, int threshold)
return new RawText(data, RawParseUtils.lineMapOrBinary(data, 0, (int) sz));
}
}
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
index 5de7bac..fb98df7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -80,7 +80,7 @@ class SimilarityRenameDetector {
private long[] matrix;
/** Score a pair must exceed to be considered a rename. */
- private int renameScore = 60;
+ private int renameScore = 50;
/**
* File size threshold (in bytes) for detecting renames. Files larger
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 c309182..c8012d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -625,6 +625,8 @@ public static JGitText get() {
/***/ public String packingCancelledDuringObjectsWriting;
/***/ public String packObjectCountMismatch;
/***/ public String packRefs;
+ /***/ public String packRefsFailed;
+ /***/ public String packRefsSuccessful;
/***/ public String packSizeNotSetYet;
/***/ public String packTooLargeForIndexVersion1;
/***/ public String packWasDeleted;
@@ -668,8 +670,6 @@ public static JGitText get() {
/***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed;
/***/ public String readLastModifiedFailed;
- /***/ public String readPipeIsNotAllowed;
- /***/ public String readPipeIsNotAllowedRequiredPermission;
/***/ public String readTimedOut;
/***/ public String receivePackObjectTooLarge1;
/***/ public String receivePackObjectTooLarge2;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java
new file mode 100644
index 0000000..295b702
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * Aggregates values for all given {@link BlockCacheStats}.
+ */
+class AggregatedBlockCacheStats implements BlockCacheStats {
+ private final List<BlockCacheStats> blockCacheStats;
+
+ static BlockCacheStats fromStatsList(
+ List<BlockCacheStats> blockCacheStats) {
+ if (blockCacheStats.size() == 1) {
+ return blockCacheStats.get(0);
+ }
+ return new AggregatedBlockCacheStats(blockCacheStats);
+ }
+
+ private AggregatedBlockCacheStats(List<BlockCacheStats> blockCacheStats) {
+ this.blockCacheStats = blockCacheStats;
+ }
+
+ @Override
+ public String getName() {
+ return AggregatedBlockCacheStats.class.getName();
+ }
+
+ @Override
+ public long[] getCurrentSize() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getCurrentSize());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getHitCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getHitCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getMissCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getMissCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getTotalRequestCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getTotalRequestCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getHitRatio() {
+ long[] hit = getHitCount();
+ long[] miss = getMissCount();
+ long[] ratio = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < ratio.length; i++) {
+ if (i >= hit.length) {
+ ratio[i] = 0;
+ } else if (i >= miss.length) {
+ ratio[i] = 100;
+ } else {
+ long total = hit[i] + miss[i];
+ ratio[i] = total == 0 ? 0 : hit[i] * 100 / total;
+ }
+ }
+ return ratio;
+ }
+
+ @Override
+ public long[] getEvictions() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getEvictions());
+ }
+ return sums;
+ }
+
+ private static long[] emptyPackStats() {
+ return new long[PackExt.values().length];
+ }
+
+ private static long[] add(long[] first, long[] second) {
+ long[] sums = new long[Integer.max(first.length, second.length)];
+ int i;
+ for (i = 0; i < Integer.min(first.length, second.length); i++) {
+ sums[i] = first[i] + second[i];
+ }
+ for (int j = i; j < first.length; j++) {
+ sums[j] = first[i];
+ }
+ for (int j = i; j < second.length; j++) {
+ sums[j] = second[i];
+ }
+ return sums;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java
index ce71a71..587d482 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java
@@ -12,6 +12,7 @@
import java.io.IOException;
import java.time.Duration;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReferenceArray;
@@ -47,6 +48,11 @@
* invocations is also fixed in size.
*/
final class ClockBlockCacheTable implements DfsBlockCacheTable {
+ /**
+ * Table name.
+ */
+ private final String name;
+
/** Number of entries in {@link #table}. */
private final int tableSize;
@@ -129,14 +135,20 @@ final class ClockBlockCacheTable implements DfsBlockCacheTable {
-1, 0, null);
clockHand.next = clockHand;
- this.dfsBlockCacheStats = new DfsBlockCacheStats();
+ this.name = cfg.getName();
+ this.dfsBlockCacheStats = new DfsBlockCacheStats(this.name);
this.refLockWaitTime = cfg.getRefLockWaitTimeConsumer();
this.indexEventConsumer = cfg.getIndexEventConsumer();
}
@Override
- public BlockCacheStats getBlockCacheStats() {
- return dfsBlockCacheStats;
+ public List<BlockCacheStats> getBlockCacheStats() {
+ return List.of(dfsBlockCacheStats);
+ }
+
+ @Override
+ public String getName() {
+ return name;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 3e1300c..f8e0831 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -11,7 +11,10 @@
package org.eclipse.jgit.internal.storage.dfs;
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
import java.io.IOException;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.LongStream;
@@ -97,7 +100,12 @@ private DfsBlockCache(DfsBlockCacheConfig cfg) {
double streamRatio = cfg.getStreamRatio();
maxStreamThroughCache = (long) (maxBytes * streamRatio);
- dfsBlockCacheTable = new ClockBlockCacheTable(cfg);
+ if (!cfg.getPackExtCacheConfigurations().isEmpty()) {
+ dfsBlockCacheTable = PackExtBlockCacheTable
+ .fromBlockCacheConfigs(cfg);
+ } else {
+ dfsBlockCacheTable = new ClockBlockCacheTable(cfg);
+ }
for (int i = 0; i < PackExt.values().length; ++i) {
Integer limit = cfg.getCacheHotMap().get(PackExt.values()[i]);
@@ -119,7 +127,7 @@ boolean shouldCopyThroughCache(long length) {
* @return total number of bytes in the cache, per pack file extension.
*/
public long[] getCurrentSize() {
- return dfsBlockCacheTable.getBlockCacheStats().getCurrentSize();
+ return getAggregatedBlockCacheStats().getCurrentSize();
}
/**
@@ -138,7 +146,7 @@ public long getFillPercentage() {
* extension.
*/
public long[] getHitCount() {
- return dfsBlockCacheTable.getBlockCacheStats().getHitCount();
+ return getAggregatedBlockCacheStats().getHitCount();
}
/**
@@ -149,7 +157,7 @@ public long getFillPercentage() {
* extension.
*/
public long[] getMissCount() {
- return dfsBlockCacheTable.getBlockCacheStats().getMissCount();
+ return getAggregatedBlockCacheStats().getMissCount();
}
/**
@@ -158,8 +166,7 @@ public long getFillPercentage() {
* @return total number of requests (hit + miss), per pack file extension.
*/
public long[] getTotalRequestCount() {
- return dfsBlockCacheTable.getBlockCacheStats()
- .getTotalRequestCount();
+ return getAggregatedBlockCacheStats().getTotalRequestCount();
}
/**
@@ -168,7 +175,7 @@ public long getFillPercentage() {
* @return hit ratios
*/
public long[] getHitRatio() {
- return dfsBlockCacheTable.getBlockCacheStats().getHitRatio();
+ return getAggregatedBlockCacheStats().getHitRatio();
}
/**
@@ -179,7 +186,18 @@ public long getFillPercentage() {
* file extension.
*/
public long[] getEvictions() {
- return dfsBlockCacheTable.getBlockCacheStats().getEvictions();
+ return getAggregatedBlockCacheStats().getEvictions();
+ }
+
+ /**
+ * Get the list of {@link BlockCacheStats} for all underlying caches.
+ * <p>
+ * Useful in monitoring caches with breakdown.
+ *
+ * @return the list of {@link BlockCacheStats} for all underlying caches.
+ */
+ public List<BlockCacheStats> getAllBlockCacheStats() {
+ return dfsBlockCacheTable.getBlockCacheStats();
}
/**
@@ -259,6 +277,11 @@ <T> T get(DfsStreamKey key, long position) {
return dfsBlockCacheTable.get(key, position);
}
+ private BlockCacheStats getAggregatedBlockCacheStats() {
+ return AggregatedBlockCacheStats
+ .fromStatsList(dfsBlockCacheTable.getBlockCacheStats());
+ }
+
static final class Ref<T> {
final DfsStreamKey key;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index fa68b97..17bf518 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -19,6 +19,7 @@
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACK_EXTENSIONS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
+import java.io.PrintWriter;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.ArrayList;
@@ -29,6 +30,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jgit.internal.JGitText;
@@ -49,6 +51,10 @@ public class DfsBlockCacheConfig {
/** Default number of max cache hits. */
public static final int DEFAULT_CACHE_HOT_MAX = 1;
+ static final String DEFAULT_NAME = "<default>"; //$NON-NLS-1$
+
+ private String name;
+
private long blockLimit;
private int blockSize;
@@ -69,6 +75,7 @@ public class DfsBlockCacheConfig {
* Create a default configuration.
*/
public DfsBlockCacheConfig() {
+ name = DEFAULT_NAME;
setBlockLimit(32 * MB);
setBlockSize(64 * KB);
setStreamRatio(0.30);
@@ -78,6 +85,72 @@ public DfsBlockCacheConfig() {
}
/**
+ * Print the current cache configuration to the given {@link PrintWriter}.
+ *
+ * @param writer
+ * {@link PrintWriter} to write the cache's configuration to.
+ */
+ public void print(PrintWriter writer) {
+ print(/* linePrefix= */ "", /* pad= */ " ", writer); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ /**
+ * Print the current cache configuration to the given {@link PrintWriter}.
+ *
+ * @param linePrefix
+ * prefix to prepend all writen lines with. Ex a string of 0 or
+ * more " " entries.
+ * @param pad
+ * filler used to extend linePrefix. Ex a multiple of " ".
+ * @param writer
+ * {@link PrintWriter} to write the cache's configuration to.
+ */
+ @SuppressWarnings("nls")
+ private void print(String linePrefix, String pad, PrintWriter writer) {
+ String currentPrefixLevel = linePrefix;
+ if (!name.isEmpty() || !packExtCacheConfigurations.isEmpty()) {
+ writer.println(linePrefix + "Name: "
+ + (name.isEmpty() ? DEFAULT_NAME : this.name));
+ currentPrefixLevel += pad;
+ }
+ writer.println(currentPrefixLevel + "BlockLimit: " + blockLimit);
+ writer.println(currentPrefixLevel + "BlockSize: " + blockSize);
+ writer.println(currentPrefixLevel + "StreamRatio: " + streamRatio);
+ writer.println(
+ currentPrefixLevel + "ConcurrencyLevel: " + concurrencyLevel);
+ for (Map.Entry<PackExt, Integer> entry : cacheHotMap.entrySet()) {
+ writer.println(currentPrefixLevel + "CacheHotMapEntry: "
+ + entry.getKey() + " : " + entry.getValue());
+ }
+ for (DfsBlockCachePackExtConfig extConfig : packExtCacheConfigurations) {
+ extConfig.print(currentPrefixLevel, pad, writer);
+ }
+ }
+
+ /**
+ * Get the name for the block cache configured by this cache config.
+ *
+ * @return the name for the block cache configured by this cache config.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name for the block cache configured by this cache config.
+ * <p>
+ * Made visible for testing.
+ *
+ * @param name
+ * the name for the block cache configured by this cache config.
+ * @return {@code this}
+ */
+ DfsBlockCacheConfig setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
* Get maximum number bytes of heap memory to dedicate to caching pack file
* data.
*
@@ -226,12 +299,24 @@ public Map<PackExt, Integer> getCacheHotMap() {
* map of hot count per pack extension for {@code DfsBlockCache}.
* @return {@code this}
*/
+ /*
+ * TODO The cache HotMap configuration should be set as a config option and
+ * not passed in through a setter.
+ */
public DfsBlockCacheConfig setCacheHotMap(
Map<PackExt, Integer> cacheHotMap) {
this.cacheHotMap = Collections.unmodifiableMap(cacheHotMap);
+ setCacheHotMapToPackExtConfigs(this.cacheHotMap);
return this;
}
+ private void setCacheHotMapToPackExtConfigs(
+ Map<PackExt, Integer> cacheHotMap) {
+ for (DfsBlockCachePackExtConfig packExtConfig : packExtCacheConfigurations) {
+ packExtConfig.setCacheHotMap(cacheHotMap);
+ }
+ }
+
/**
* Get the consumer of cache index events.
*
@@ -308,6 +393,11 @@ private void fromConfig(String section, String subSection, Config rc) {
Long.valueOf(cfgBlockLimit), Long.valueOf(cfgBlockSize)));
}
+ // Set name only if `core dfs` is configured, otherwise fall back to the
+ // default.
+ if (rc.getSubsections(section).contains(subSection)) {
+ this.name = subSection;
+ }
setBlockLimit(cfgBlockLimit);
setBlockSize(cfgBlockSize);
@@ -354,6 +444,7 @@ private void loadPackExtConfigs(Config config) {
cacheConfigs.add(cacheConfig);
}
packExtCacheConfigurations = cacheConfigs;
+ setCacheHotMapToPackExtConfigs(this.cacheHotMap);
}
private static <T> Set<T> intersection(Set<T> first, Set<T> second) {
@@ -472,6 +563,14 @@ DfsBlockCacheConfig getPackExtCacheConfiguration() {
return packExtCacheConfiguration;
}
+ void setCacheHotMap(Map<PackExt, Integer> cacheHotMap) {
+ Map<PackExt, Integer> packExtHotMap = packExts.stream()
+ .filter(cacheHotMap::containsKey)
+ .collect(Collectors.toUnmodifiableMap(Function.identity(),
+ cacheHotMap::get));
+ packExtCacheConfiguration.setCacheHotMap(packExtHotMap);
+ }
+
private static DfsBlockCachePackExtConfig fromConfig(Config config,
String section, String subSection) {
String packExtensions = config.getString(section, subSection,
@@ -498,5 +597,11 @@ private static DfsBlockCachePackExtConfig fromConfig(Config config,
return new DfsBlockCachePackExtConfig(EnumSet.copyOf(packExts),
dfsBlockCacheConfig);
}
+
+ void print(String linePrefix, String pad, PrintWriter writer) {
+ packExtCacheConfiguration.print(linePrefix, pad, writer);
+ writer.println(linePrefix + pad + "PackExts: " //$NON-NLS-1$
+ + packExts.stream().sorted().collect(Collectors.toList()));
+ }
}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java
new file mode 100644
index 0000000..436f574
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * Keeps track of stats for a Block Cache table.
+ */
+class DfsBlockCacheStats implements BlockCacheStats {
+ private final String name;
+
+ /**
+ * Number of times a block was found in the cache, per pack file extension.
+ */
+ private final AtomicReference<AtomicLong[]> statHit;
+
+ /**
+ * Number of times a block was not found, and had to be loaded, per pack
+ * file extension.
+ */
+ private final AtomicReference<AtomicLong[]> statMiss;
+
+ /**
+ * Number of blocks evicted due to cache being full, per pack file
+ * extension.
+ */
+ private final AtomicReference<AtomicLong[]> statEvict;
+
+ /**
+ * Number of bytes currently loaded in the cache, per pack file extension.
+ */
+ private final AtomicReference<AtomicLong[]> liveBytes;
+
+ DfsBlockCacheStats() {
+ this(""); //$NON-NLS-1$
+ }
+
+ DfsBlockCacheStats(String name) {
+ this.name = name;
+ statHit = new AtomicReference<>(newCounters());
+ statMiss = new AtomicReference<>(newCounters());
+ statEvict = new AtomicReference<>(newCounters());
+ liveBytes = new AtomicReference<>(newCounters());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Increment the {@code statHit} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementHit(DfsStreamKey key) {
+ getStat(statHit, key).incrementAndGet();
+ }
+
+ /**
+ * Increment the {@code statMiss} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementMiss(DfsStreamKey key) {
+ getStat(statMiss, key).incrementAndGet();
+ }
+
+ /**
+ * Increment the {@code statEvict} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementEvict(DfsStreamKey key) {
+ getStat(statEvict, key).incrementAndGet();
+ }
+
+ /**
+ * Add {@code size} to the {@code liveBytes} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ * @param size
+ * amount to increment the count by.
+ */
+ void addToLiveBytes(DfsStreamKey key, long size) {
+ getStat(liveBytes, key).addAndGet(size);
+ }
+
+ @Override
+ public long[] getCurrentSize() {
+ return getStatVals(liveBytes);
+ }
+
+ @Override
+ public long[] getHitCount() {
+ return getStatVals(statHit);
+ }
+
+ @Override
+ public long[] getMissCount() {
+ return getStatVals(statMiss);
+ }
+
+ @Override
+ public long[] getTotalRequestCount() {
+ AtomicLong[] hit = statHit.get();
+ AtomicLong[] miss = statMiss.get();
+ long[] cnt = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < hit.length; i++) {
+ cnt[i] += hit[i].get();
+ }
+ for (int i = 0; i < miss.length; i++) {
+ cnt[i] += miss[i].get();
+ }
+ return cnt;
+ }
+
+ @Override
+ public long[] getHitRatio() {
+ AtomicLong[] hit = statHit.get();
+ AtomicLong[] miss = statMiss.get();
+ long[] ratio = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < ratio.length; i++) {
+ if (i >= hit.length) {
+ ratio[i] = 0;
+ } else if (i >= miss.length) {
+ ratio[i] = 100;
+ } else {
+ long hitVal = hit[i].get();
+ long missVal = miss[i].get();
+ long total = hitVal + missVal;
+ ratio[i] = total == 0 ? 0 : hitVal * 100 / total;
+ }
+ }
+ return ratio;
+ }
+
+ @Override
+ public long[] getEvictions() {
+ return getStatVals(statEvict);
+ }
+
+ private static AtomicLong[] newCounters() {
+ AtomicLong[] ret = new AtomicLong[PackExt.values().length];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = new AtomicLong();
+ }
+ return ret;
+ }
+
+ private static long[] getStatVals(AtomicReference<AtomicLong[]> stat) {
+ AtomicLong[] stats = stat.get();
+ long[] cnt = new long[stats.length];
+ for (int i = 0; i < stats.length; i++) {
+ cnt[i] = stats[i].get();
+ }
+ return cnt;
+ }
+
+ private static AtomicLong getStat(AtomicReference<AtomicLong[]> stats,
+ DfsStreamKey key) {
+ int pos = key.packExtPos;
+ while (true) {
+ AtomicLong[] vals = stats.get();
+ if (pos < vals.length) {
+ return vals[pos];
+ }
+ AtomicLong[] expect = vals;
+ vals = new AtomicLong[Math.max(pos + 1, PackExt.values().length)];
+ System.arraycopy(expect, 0, vals, 0, expect.length);
+ for (int i = expect.length; i < vals.length; i++) {
+ vals[i] = new AtomicLong();
+ }
+ if (stats.compareAndSet(expect, vals)) {
+ return vals[pos];
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java
index 309f2d1..c3fd07b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java
@@ -11,10 +11,7 @@
package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jgit.internal.storage.pack.PackExt;
+import java.util.List;
/**
* Block cache table.
@@ -129,18 +126,37 @@ <T> DfsBlockCache.Ref<T> getOrLoadRef(DfsStreamKey key, long position,
<T> T get(DfsStreamKey key, long position);
/**
- * Get the {@link BlockCacheStats} object for this block cache table's
- * statistics.
+ * Get the list of {@link BlockCacheStats} held by this cache.
+ * <p>
+ * The returned list has a {@link BlockCacheStats} per configured cache
+ * table, with a minimum of 1 {@link BlockCacheStats} object returned.
*
- * @return the {@link BlockCacheStats} tracking this block cache table's
- * statistics.
+ * Use {@link AggregatedBlockCacheStats} to combine the results of the stats
+ * in the list for an aggregated view of the cache's stats.
+ *
+ * @return the list of {@link BlockCacheStats} held by this cache.
*/
- BlockCacheStats getBlockCacheStats();
+ List<BlockCacheStats> getBlockCacheStats();
+
+ /**
+ * Get the name of the table.
+ *
+ * @return this table's name.
+ */
+ String getName();
/**
* Provides methods used with Block Cache statistics.
*/
interface BlockCacheStats {
+
+ /**
+ * Get the name of the block cache generating this instance.
+ *
+ * @return this cache's name.
+ */
+ String getName();
+
/**
* Get total number of bytes in the cache, per pack file extension.
*
@@ -190,174 +206,4 @@ interface BlockCacheStats {
*/
long[] getEvictions();
}
-
- /**
- * Keeps track of stats for a Block Cache table.
- */
- class DfsBlockCacheStats implements BlockCacheStats {
- /**
- * Number of times a block was found in the cache, per pack file
- * extension.
- */
- private final AtomicReference<AtomicLong[]> statHit;
-
- /**
- * Number of times a block was not found, and had to be loaded, per pack
- * file extension.
- */
- private final AtomicReference<AtomicLong[]> statMiss;
-
- /**
- * Number of blocks evicted due to cache being full, per pack file
- * extension.
- */
- private final AtomicReference<AtomicLong[]> statEvict;
-
- /**
- * Number of bytes currently loaded in the cache, per pack file
- * extension.
- */
- private final AtomicReference<AtomicLong[]> liveBytes;
-
- DfsBlockCacheStats() {
- statHit = new AtomicReference<>(newCounters());
- statMiss = new AtomicReference<>(newCounters());
- statEvict = new AtomicReference<>(newCounters());
- liveBytes = new AtomicReference<>(newCounters());
- }
-
- /**
- * Increment the {@code statHit} count.
- *
- * @param key
- * key identifying which liveBytes entry to update.
- */
- void incrementHit(DfsStreamKey key) {
- getStat(statHit, key).incrementAndGet();
- }
-
- /**
- * Increment the {@code statMiss} count.
- *
- * @param key
- * key identifying which liveBytes entry to update.
- */
- void incrementMiss(DfsStreamKey key) {
- getStat(statMiss, key).incrementAndGet();
- }
-
- /**
- * Increment the {@code statEvict} count.
- *
- * @param key
- * key identifying which liveBytes entry to update.
- */
- void incrementEvict(DfsStreamKey key) {
- getStat(statEvict, key).incrementAndGet();
- }
-
- /**
- * Add {@code size} to the {@code liveBytes} count.
- *
- * @param key
- * key identifying which liveBytes entry to update.
- * @param size
- * amount to increment the count by.
- */
- void addToLiveBytes(DfsStreamKey key, long size) {
- getStat(liveBytes, key).addAndGet(size);
- }
-
- @Override
- public long[] getCurrentSize() {
- return getStatVals(liveBytes);
- }
-
- @Override
- public long[] getHitCount() {
- return getStatVals(statHit);
- }
-
- @Override
- public long[] getMissCount() {
- return getStatVals(statMiss);
- }
-
- @Override
- public long[] getTotalRequestCount() {
- AtomicLong[] hit = statHit.get();
- AtomicLong[] miss = statMiss.get();
- long[] cnt = new long[Math.max(hit.length, miss.length)];
- for (int i = 0; i < hit.length; i++) {
- cnt[i] += hit[i].get();
- }
- for (int i = 0; i < miss.length; i++) {
- cnt[i] += miss[i].get();
- }
- return cnt;
- }
-
- @Override
- public long[] getHitRatio() {
- AtomicLong[] hit = statHit.get();
- AtomicLong[] miss = statMiss.get();
- long[] ratio = new long[Math.max(hit.length, miss.length)];
- for (int i = 0; i < ratio.length; i++) {
- if (i >= hit.length) {
- ratio[i] = 0;
- } else if (i >= miss.length) {
- ratio[i] = 100;
- } else {
- long hitVal = hit[i].get();
- long missVal = miss[i].get();
- long total = hitVal + missVal;
- ratio[i] = total == 0 ? 0 : hitVal * 100 / total;
- }
- }
- return ratio;
- }
-
- @Override
- public long[] getEvictions() {
- return getStatVals(statEvict);
- }
-
- private static AtomicLong[] newCounters() {
- AtomicLong[] ret = new AtomicLong[PackExt.values().length];
- for (int i = 0; i < ret.length; i++) {
- ret[i] = new AtomicLong();
- }
- return ret;
- }
-
- private static long[] getStatVals(AtomicReference<AtomicLong[]> stat) {
- AtomicLong[] stats = stat.get();
- long[] cnt = new long[stats.length];
- for (int i = 0; i < stats.length; i++) {
- cnt[i] = stats[i].get();
- }
- return cnt;
- }
-
- private static AtomicLong getStat(AtomicReference<AtomicLong[]> stats,
- DfsStreamKey key) {
- int pos = key.packExtPos;
- while (true) {
- AtomicLong[] vals = stats.get();
- if (pos < vals.length) {
- return vals[pos];
- }
- AtomicLong[] expect = vals;
- vals = new AtomicLong[Math.max(pos + 1,
- PackExt.values().length)];
- System.arraycopy(expect, 0, vals, 0, expect.length);
- for (int i = expect.length; i < vals.length; i++) {
- vals[i] = new AtomicLong();
- }
- if (stats.compareAndSet(expect, vals)) {
- return vals[pos];
- }
- }
- }
- }
-}
\ No newline at end of file
+}
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 a177669..e6068a1 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
@@ -18,13 +18,13 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable;
import static org.eclipse.jgit.internal.storage.pack.PackExt.COMMIT_GRAPH;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -100,6 +100,7 @@ public class DfsGarbageCollector {
private Set<ObjectId> allTags;
private Set<ObjectId> nonHeads;
private Set<ObjectId> tagTargets;
+ private Instant refLogExpire;
/**
* Initialize a garbage collector.
@@ -200,6 +201,22 @@ public DfsGarbageCollector setReftableInitialMinUpdateIndex(long u) {
return this;
}
+
+ /**
+ * Set time limit to the reflog history.
+ * <p>
+ * Garbage Collector prunes entries from reflog history older than {@code refLogExpire}
+ * <p>
+ *
+ * @param refLogExpire
+ * instant in time which defines refLog expiration
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setRefLogExpire(Instant refLogExpire) {
+ this.refLogExpire = refLogExpire;
+ return this;
+ }
+
/**
* Set maxUpdateIndex for the initial reftable created during conversion.
*
@@ -687,14 +704,7 @@ private DfsPackDescription writePack(PackSource source, PackWriter pw,
pack.setBlockSize(PACK, out.blockSize());
}
- try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
- CountingOutputStream cnt = new CountingOutputStream(out);
- pw.writeIndex(cnt);
- pack.addFileExt(INDEX);
- pack.setFileSize(INDEX, cnt.getCount());
- pack.setBlockSize(INDEX, out.blockSize());
- pack.setIndexVersion(pw.getIndexVersion());
- }
+ pw.writeIndex(objdb.getPackIndexWriter(pack, pw.getIndexVersion()));
if (source != UNREACHABLE_GARBAGE && packConfig.getMinBytesForObjSizeIndex() >= 0) {
try (DfsOutputStream out = objdb.writeFile(pack,
@@ -741,6 +751,10 @@ private void writeReftable(DfsPackDescription pack) throws IOException {
compact.addAll(stack.readers());
compact.setIncludeDeletes(includeDeletes);
compact.setConfig(configureReftable(reftableConfig, out));
+ if(refLogExpire != null ){
+ compact.setReflogExpireOldestReflogTimeMillis(
+ refLogExpire.toEpochMilli());
+ }
compact.compact();
pack.addFileExt(REFTABLE);
pack.setReftableStats(compact.getStats());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
index a07d841..16315bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
@@ -41,12 +41,13 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
@@ -54,7 +55,6 @@
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ObjectStream;
-import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.PackedObjectInfo;
import org.eclipse.jgit.util.BlockList;
import org.eclipse.jgit.util.IO;
@@ -71,6 +71,8 @@ public class DfsInserter extends ObjectInserter {
private static final int INDEX_VERSION = 2;
final DfsObjDatabase db;
+
+ private final int minBytesForObjectSizeIndex;
int compression = Deflater.BEST_COMPRESSION;
List<PackedObjectInfo> objectList;
@@ -83,8 +85,6 @@ public class DfsInserter extends ObjectInserter {
private boolean rollback;
private boolean checkExisting = true;
- private int minBytesForObjectSizeIndex = -1;
-
/**
* Initialize a new inserter.
*
@@ -93,8 +93,9 @@ public class DfsInserter extends ObjectInserter {
*/
protected DfsInserter(DfsObjDatabase db) {
this.db = db;
- PackConfig pc = new PackConfig(db.getRepository().getConfig());
- this.minBytesForObjectSizeIndex = pc.getMinBytesForObjSizeIndex();
+ this.minBytesForObjectSizeIndex = db.getRepository().getConfig().getInt(
+ ConfigConstants.CONFIG_PACK_SECTION,
+ ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, -1);
}
/**
@@ -112,21 +113,6 @@ public void checkExisting(boolean check) {
void setCompressionLevel(int compression) {
this.compression = compression;
}
-
- /**
- * Set minimum size for an object to be included in the object size index.
- *
- * <p>
- * Use 0 for all and -1 for nothing (the pack won't have object size index).
- *
- * @param minBytes
- * only objects with size bigger or equal to this are included in
- * the index.
- */
- protected void setMinBytesForObjectSizeIndex(int minBytes) {
- this.minBytesForObjectSizeIndex = minBytes;
- }
-
@Override
public DfsPackParser newPackParser(InputStream in) throws IOException {
return new DfsPackParser(db, this, in);
@@ -333,7 +319,7 @@ PackIndex writePackIndex(DfsPackDescription pack, byte[] packHash,
private static void index(OutputStream out, byte[] packHash,
List<PackedObjectInfo> list) throws IOException {
- PackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
+ BasePackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
}
void writeObjectSizeIndex(DfsPackDescription pack,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
index 616563f..efd666f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
@@ -12,6 +12,7 @@
import static java.util.stream.Collectors.joining;
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -27,7 +28,9 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndexWriterV1;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.internal.storage.pack.PackBitmapIndexWriter;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -771,4 +774,35 @@ public PackBitmapIndexWriter getPackBitmapIndexWriter(
}
};
}
+
+ /**
+ * Returns a writer to store the pack index in this object database.
+ *
+ * @param pack
+ * Pack file to which the index is associated.
+ * @param indexVersion
+ * which version of the index to write
+ * @return a writer to store the index associated with the pack
+ * @throws IOException
+ * when some I/O problem occurs while creating or writing to
+ * output stream
+ */
+ public PackIndexWriter getPackIndexWriter(
+ DfsPackDescription pack, int indexVersion)
+ throws IOException {
+ return (objectsToStore, packDataChecksum) -> {
+ try (DfsOutputStream out = writeFile(pack, INDEX);
+ CountingOutputStream cnt = new CountingOutputStream(out)) {
+ final PackIndexWriter iw = BasePackIndexWriter
+ .createVersion(cnt,
+ indexVersion);
+ iw.write(objectsToStore, packDataChecksum);
+ pack.addFileExt(INDEX);
+ pack.setFileSize(INDEX, cnt.getCount());
+ pack.setBlockSize(INDEX, out.blockSize());
+ pack.setIndexVersion(indexVersion);
+ }
+ };
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index 86144b3..f9c01b9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -12,7 +12,7 @@
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
@@ -249,6 +249,7 @@ private void compactPacks(DfsReader ctx, ProgressMonitor pm)
try {
writePack(objdb, outDesc, pw, pm);
writeIndex(objdb, outDesc, pw);
+ writeObjectSizeIndex(objdb, outDesc, pw);
PackStatistics stats = pw.getStatistics();
@@ -458,13 +459,20 @@ private static void writePack(DfsObjDatabase objdb,
private static void writeIndex(DfsObjDatabase objdb,
DfsPackDescription pack,
PackWriter pw) throws IOException {
- try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
+ pw.writeIndex(objdb.getPackIndexWriter(pack, pw.getIndexVersion()));
+ }
+
+ private static void writeObjectSizeIndex(DfsObjDatabase objdb,
+ DfsPackDescription pack,
+ PackWriter pw) throws IOException {
+ try (DfsOutputStream out = objdb.writeFile(pack, OBJECT_SIZE_INDEX)) {
CountingOutputStream cnt = new CountingOutputStream(out);
- pw.writeIndex(cnt);
- pack.addFileExt(INDEX);
- pack.setFileSize(INDEX, cnt.getCount());
- pack.setBlockSize(INDEX, out.blockSize());
- pack.setIndexVersion(pw.getIndexVersion());
+ pw.writeObjectSizeIndex(cnt);
+ if (cnt.getCount() > 0) {
+ pack.addFileExt(OBJECT_SIZE_INDEX);
+ pack.setFileSize(OBJECT_SIZE_INDEX, cnt.getCount());
+ pack.setBlockSize(OBJECT_SIZE_INDEX, out.blockSize());
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index 9cfcbaa..62f6753 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -511,18 +511,15 @@ public long getObjectSize(AnyObjectId objectId, int typeHint)
throw new MissingObjectException(objectId.copy(), typeHint);
}
- if (typeHint != Constants.OBJ_BLOB || !pack.hasObjectSizeIndex(this)) {
+ if (typeHint != Constants.OBJ_BLOB || !safeHasObjectSizeIndex(pack)) {
return pack.getObjectSize(this, objectId);
}
- long sz = pack.getIndexedObjectSize(this, objectId);
+ Optional<Long> maybeSz = safeGetIndexedObjectSize(pack, objectId);
+ long sz = maybeSz.orElse(-1L);
if (sz >= 0) {
- stats.objectSizeIndexHit += 1;
return sz;
}
-
- // Object wasn't in the index
- stats.objectSizeIndexMiss += 1;
return pack.getObjectSize(this, objectId);
}
@@ -541,23 +538,61 @@ public boolean isNotLargerThan(AnyObjectId objectId, int typeHint,
}
stats.isNotLargerThanCallCount += 1;
- if (typeHint != Constants.OBJ_BLOB || !pack.hasObjectSizeIndex(this)) {
+ if (typeHint != Constants.OBJ_BLOB || !safeHasObjectSizeIndex(pack)) {
return pack.getObjectSize(this, objectId) <= limit;
}
- long sz = pack.getIndexedObjectSize(this, objectId);
+ Optional<Long> maybeSz = safeGetIndexedObjectSize(pack, objectId);
+ if (maybeSz.isEmpty()) {
+ // Exception in object size index
+ return pack.getObjectSize(this, objectId) <= limit;
+ }
+
+ long sz = maybeSz.get();
+ if (sz >= 0) {
+ return sz <= limit;
+ }
+
+ if (isLimitInsideIndexThreshold(pack, limit)) {
+ // With threshold T, not-found means object < T
+ // If limit L > T, then object < T < L
+ return true;
+ }
+
+ return pack.getObjectSize(this, objectId) <= limit;
+ }
+
+ private boolean safeHasObjectSizeIndex(DfsPackFile pack) {
+ try {
+ return pack.hasObjectSizeIndex(this);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private Optional<Long> safeGetIndexedObjectSize(DfsPackFile pack,
+ AnyObjectId objectId) {
+ long sz;
+ try {
+ sz = pack.getIndexedObjectSize(this, objectId);
+ } catch (IOException e) {
+ // Do not count the exception as an index miss
+ return Optional.empty();
+ }
if (sz < 0) {
stats.objectSizeIndexMiss += 1;
} else {
stats.objectSizeIndexHit += 1;
}
+ return Optional.of(sz);
+ }
- // Got size from index or we didn't but we are sure it should be there.
- if (sz >= 0 || pack.getObjectSizeIndexThreshold(this) <= limit) {
- return sz <= limit;
+ private boolean isLimitInsideIndexThreshold(DfsPackFile pack, long limit) {
+ try {
+ return pack.getObjectSizeIndexThreshold(this) <= limit;
+ } catch (IOException e) {
+ return false;
}
-
- return pack.getObjectSize(this, objectId) <= limit;
}
private DfsPackFile findPackWithObject(AnyObjectId objectId)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 5f5e819..c397469 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -13,6 +13,7 @@
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_BASE_CACHE_LIMIT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_BUFFER;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_THRESHOLD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_USE_OBJECT_SIZE_INDEX;
@@ -197,6 +198,9 @@ public DfsReaderOptions fromConfig(Config rc) {
setUseObjectSizeIndex(rc.getBoolean(CONFIG_CORE_SECTION,
CONFIG_DFS_SECTION, CONFIG_KEY_USE_OBJECT_SIZE_INDEX,
false));
+ setLoadRevIndexInParallel(
+ rc.getBoolean(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL, false));
return this;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java
index 858f731..bb44f93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java
@@ -37,6 +37,11 @@
* type.
*/
class PackExtBlockCacheTable implements DfsBlockCacheTable {
+ /**
+ * Table name.
+ */
+ private final String name;
+
private final DfsBlockCacheTable defaultBlockCacheTable;
// Holds the unique tables backing the extBlockCacheTables values.
@@ -120,13 +125,19 @@ static PackExtBlockCacheTable fromCacheTables(
Set<DfsBlockCacheTable> blockCacheTables = new HashSet<>();
blockCacheTables.add(defaultBlockCacheTable);
blockCacheTables.addAll(packExtBlockCacheTables.values());
- return new PackExtBlockCacheTable(defaultBlockCacheTable,
+ String name = defaultBlockCacheTable.getName() + "," //$NON-NLS-1$
+ + packExtBlockCacheTables.values().stream()
+ .map(DfsBlockCacheTable::getName).sorted()
+ .collect(Collectors.joining(",")); //$NON-NLS-1$
+ return new PackExtBlockCacheTable(name, defaultBlockCacheTable,
List.copyOf(blockCacheTables), packExtBlockCacheTables);
}
- private PackExtBlockCacheTable(DfsBlockCacheTable defaultBlockCacheTable,
+ private PackExtBlockCacheTable(String name,
+ DfsBlockCacheTable defaultBlockCacheTable,
List<DfsBlockCacheTable> blockCacheTableList,
Map<PackExt, DfsBlockCacheTable> extBlockCacheTables) {
+ this.name = name;
this.defaultBlockCacheTable = defaultBlockCacheTable;
this.blockCacheTableList = blockCacheTableList;
this.extBlockCacheTables = extBlockCacheTables;
@@ -177,10 +188,15 @@ public <T> T get(DfsStreamKey key, long position) {
}
@Override
- public BlockCacheStats getBlockCacheStats() {
- return new CacheStats(blockCacheTableList.stream()
- .map(DfsBlockCacheTable::getBlockCacheStats)
- .collect(Collectors.toList()));
+ public List<BlockCacheStats> getBlockCacheStats() {
+ return blockCacheTableList.stream()
+ .flatMap(cacheTable -> cacheTable.getBlockCacheStats().stream())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String getName() {
+ return name;
}
private DfsBlockCacheTable getTable(PackExt packExt) {
@@ -196,94 +212,4 @@ private DfsBlockCacheTable getTable(DfsStreamKey key) {
private static PackExt getPackExt(DfsStreamKey key) {
return PackExt.values()[key.packExtPos];
}
-
- private static class CacheStats implements BlockCacheStats {
- private final List<BlockCacheStats> blockCacheStats;
-
- private CacheStats(List<BlockCacheStats> blockCacheStats) {
- this.blockCacheStats = blockCacheStats;
- }
-
- @Override
- public long[] getCurrentSize() {
- long[] sums = emptyPackStats();
- for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
- sums = add(sums, blockCacheStatsEntry.getCurrentSize());
- }
- return sums;
- }
-
- @Override
- public long[] getHitCount() {
- long[] sums = emptyPackStats();
- for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
- sums = add(sums, blockCacheStatsEntry.getHitCount());
- }
- return sums;
- }
-
- @Override
- public long[] getMissCount() {
- long[] sums = emptyPackStats();
- for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
- sums = add(sums, blockCacheStatsEntry.getMissCount());
- }
- return sums;
- }
-
- @Override
- public long[] getTotalRequestCount() {
- long[] sums = emptyPackStats();
- for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
- sums = add(sums, blockCacheStatsEntry.getTotalRequestCount());
- }
- return sums;
- }
-
- @Override
- public long[] getHitRatio() {
- long[] hit = getHitCount();
- long[] miss = getMissCount();
- long[] ratio = new long[Math.max(hit.length, miss.length)];
- for (int i = 0; i < ratio.length; i++) {
- if (i >= hit.length) {
- ratio[i] = 0;
- } else if (i >= miss.length) {
- ratio[i] = 100;
- } else {
- long total = hit[i] + miss[i];
- ratio[i] = total == 0 ? 0 : hit[i] * 100 / total;
- }
- }
- return ratio;
- }
-
- @Override
- public long[] getEvictions() {
- long[] sums = emptyPackStats();
- for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
- sums = add(sums, blockCacheStatsEntry.getEvictions());
- }
- return sums;
- }
-
- private static long[] emptyPackStats() {
- return new long[PackExt.values().length];
- }
-
- private static long[] add(long[] first, long[] second) {
- long[] sums = new long[Integer.max(first.length, second.length)];
- int i;
- for (i = 0; i < Integer.min(first.length, second.length); i++) {
- sums[i] = first[i] + second[i];
- }
- for (int j = i; j < first.length; j++) {
- sums[j] = first[i];
- }
- for (int j = i; j < second.length; j++) {
- sums[j] = second[i];
- }
- return sums;
- }
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
similarity index 97%
rename from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java
rename to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
index 87e0b44..b89cc1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
@@ -19,6 +19,7 @@
import java.util.List;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.PackedObjectInfo;
import org.eclipse.jgit.util.NB;
@@ -31,7 +32,7 @@
* random access to any object in the pack by associating an ObjectId to the
* byte offset within the pack where the object's data can be read.
*/
-public abstract class PackIndexWriter {
+public abstract class BasePackIndexWriter implements PackIndexWriter {
/** Magic constant indicating post-version 1 format. */
protected static final byte[] TOC = { -1, 't', 'O', 'c' };
@@ -147,7 +148,7 @@ public static PackIndexWriter createVersion(final OutputStream dst,
* the stream this instance outputs to. If not already buffered
* it will be automatically wrapped in a buffered stream.
*/
- protected PackIndexWriter(OutputStream dst) {
+ protected BasePackIndexWriter(OutputStream dst) {
out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst
: new BufferedOutputStream(dst),
Constants.newMessageDigest());
@@ -172,6 +173,7 @@ protected PackIndexWriter(OutputStream dst) {
* an error occurred while writing to the output stream, or this
* index format cannot store the object data supplied.
*/
+ @Override
public void write(final List<? extends PackedObjectInfo> toStore,
final byte[] packDataChecksum) throws IOException {
entries = toStore;
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 80240e5..25b7583 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
@@ -28,8 +28,10 @@
import java.util.stream.Collectors;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.PackRefsCommand;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.events.RefsChangedEvent;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
@@ -39,6 +41,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
@@ -108,6 +111,22 @@ public boolean hasFastTipsWithSha1() throws IOException {
}
/**
+ * {@inheritDoc}
+ *
+ * For Reftable, all the data is compacted into a single table.
+ */
+ @Override
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ pm.beginTask(JGitText.get().packRefs, 1);
+ try {
+ compactFully();
+ } finally {
+ pm.endTask();
+ }
+ }
+
+ /**
* Runs a full compaction for GC purposes.
* @throws IOException on I/O errors
*/
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 b5d29a3..84c8565 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
@@ -33,6 +33,7 @@
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;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
@@ -60,7 +61,6 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
@@ -595,13 +595,12 @@ private boolean shouldAutoDetach() {
@Override
public void autoGC(ProgressMonitor monitor) {
GC gc = new GC(this);
- gc.setPackConfig(new PackConfig(this));
gc.setProgressMonitor(monitor);
gc.setAuto(true);
gc.setBackground(shouldAutoDetach());
try {
gc.gc();
- } catch (ParseException | IOException e) {
+ } catch (ParseException | IOException | GitAPIException e) {
throw new JGitInternalException(JGitText.get().gcFailed, e);
}
}
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 67b43cb..3105a3a 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
@@ -63,6 +63,8 @@
import java.util.stream.Stream;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.PackRefsCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CancelledException;
import org.eclipse.jgit.errors.CorruptObjectException;
@@ -233,9 +235,11 @@ public GC(FileRepository repo) {
* @throws java.text.ParseException
* If the configuration parameter "gc.pruneexpire" couldn't be
* parsed
+ * @throws GitAPIException
+ * If packing refs failed
*/
public CompletableFuture<Collection<Pack>> gc()
- throws IOException, ParseException {
+ throws IOException, ParseException, GitAPIException {
if (!background) {
return CompletableFuture.completedFuture(doGc());
}
@@ -254,7 +258,7 @@ public CompletableFuture<Collection<Pack>> gc()
gcLog.commit();
}
return newPacks;
- } catch (IOException | ParseException e) {
+ } catch (IOException | ParseException | GitAPIException e) {
try {
gcLog.write(e.getMessage());
StringWriter sw = new StringWriter();
@@ -277,7 +281,8 @@ private ExecutorService executor() {
return (executor != null) ? executor : WorkQueue.getExecutor();
}
- private Collection<Pack> doGc() throws IOException, ParseException {
+ private Collection<Pack> doGc()
+ throws IOException, ParseException, GitAPIException {
if (automatic && !needGc()) {
return Collections.emptyList();
}
@@ -286,7 +291,8 @@ private Collection<Pack> doGc() throws IOException, ParseException {
return Collections.emptyList();
}
pm.start(6 /* tasks */);
- packRefs();
+ new PackRefsCommand(repo).setProgressMonitor(pm).setAll(true)
+ .call();
// TODO: implement reflog_expire(pm, repo);
Collection<Pack> newPacks = repack();
prune(Collections.emptySet());
@@ -780,43 +786,6 @@ private static boolean equals(Ref r1, Ref r2) {
}
/**
- * Pack ref storage. For a RefDirectory database, this packs all
- * non-symbolic, loose refs into packed-refs. For Reftable, all of the data
- * is compacted into a single table.
- *
- * @throws java.io.IOException
- * if an IO error occurred
- */
- public void packRefs() throws IOException {
- RefDatabase refDb = repo.getRefDatabase();
- if (refDb instanceof FileReftableDatabase) {
- // TODO: abstract this more cleanly.
- pm.beginTask(JGitText.get().packRefs, 1);
- try {
- ((FileReftableDatabase) refDb).compactFully();
- } finally {
- pm.endTask();
- }
- return;
- }
-
- Collection<Ref> refs = refDb.getRefsByPrefix(Constants.R_REFS);
- List<String> refsToBePacked = new ArrayList<>(refs.size());
- pm.beginTask(JGitText.get().packRefs, refs.size());
- try {
- for (Ref ref : refs) {
- checkCancelled();
- if (!ref.isSymbolic() && ref.getStorage().isLoose())
- refsToBePacked.add(ref.getName());
- pm.update(1);
- }
- ((RefDirectory) repo.getRefDatabase()).pack(refsToBePacked);
- } finally {
- pm.endTask();
- }
- }
-
- /**
* Packs all objects which reachable from any of the heads into one pack
* file. Additionally all objects which are not reachable from any head but
* which are reachable from any of the other refs (e.g. tags), special refs
@@ -1505,7 +1474,7 @@ public static class RepoStatistics {
public long numberOfPackFiles;
/**
- * The number of pack files that were created after the last bitmap
+ * The number of pack files that were created since the last bitmap
* generation.
*/
public long numberOfPackFilesSinceBitmap;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 9f27f4b..746e124 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -28,6 +28,7 @@
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
@@ -110,7 +111,7 @@ public class ObjectDirectoryPackParser extends PackParser {
* @param version
* the version to write. The special version 0 designates the
* oldest (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public void setIndexVersion(int version) {
indexVersion = version;
@@ -386,9 +387,9 @@ private void writeIdx() throws IOException {
try (FileOutputStream os = new FileOutputStream(tmpIdx)) {
final PackIndexWriter iw;
if (indexVersion <= 0)
- iw = PackIndexWriter.createOldestPossible(os, list);
+ iw = BasePackIndexWriter.createOldestPossible(os, list);
else
- iw = PackIndexWriter.createVersion(os, indexVersion);
+ iw = BasePackIndexWriter.createVersion(os, indexVersion);
iw.write(list, packHash);
os.getChannel().force(true);
}
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 c0540d5..7189ce2 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
@@ -109,7 +109,7 @@ static PackIndex read(InputStream fd) throws IOException,
}
private static boolean isTOC(byte[] h) {
- final byte[] toc = PackIndexWriter.TOC;
+ final byte[] toc = BasePackIndexWriter.TOC;
for (int i = 0; i < toc.length; i++)
if (h[i] != toc[i])
return false;
@@ -302,8 +302,9 @@ 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;
/**
@@ -321,7 +322,6 @@ public long getOffset() {
* @return hex string describing the object id of this entry.
*/
public String name() {
- ensureId();
return idBuffer.name();
}
@@ -331,7 +331,6 @@ public String name() {
* @return a copy of the object id.
*/
public ObjectId toObjectId() {
- ensureId();
return idBuffer.toObjectId();
}
@@ -342,32 +341,33 @@ public ObjectId toObjectId() {
*/
public MutableEntry cloneEntry() {
final MutableEntry r = new MutableEntry();
- ensureId();
r.idBuffer.fromObjectId(idBuffer);
r.offset = offset;
return r;
}
-
- void ensureId() {
- // Override in implementations.
- }
}
/**
* Base implementation of the iterator over index entries.
*/
abstract class EntriesIterator implements Iterator<MutableEntry> {
- protected final MutableEntry entry = initEntry();
-
private final long objectCount;
+ private final MutableEntry entry = new MutableEntry();
+
+ /** Counts number of entries accessed so far. */
+ private long returnedNumber = 0;
+
+ /**
+ * Construct an iterator that can move objectCount times forward.
+ *
+ * @param objectCount
+ * the number of objects in the PackFile.
+ */
protected EntriesIterator(long objectCount) {
this.objectCount = objectCount;
}
- protected long returnedNumber = 0;
-
- protected abstract MutableEntry initEntry();
@Override
public boolean hasNext() {
@@ -379,7 +379,55 @@ public boolean hasNext() {
* element.
*/
@Override
- public abstract MutableEntry next();
+ public MutableEntry next() {
+ readNext();
+ returnedNumber++;
+ return entry;
+ }
+
+ /**
+ * Used by subclasses to load the next entry into the MutableEntry.
+ * <p>
+ * Subclasses are expected to populate the entry with
+ * {@link #setIdBuffer} and {@link #setOffset}.
+ */
+ protected abstract void readNext();
+
+
+ /**
+ * Copies to the entry an {@link ObjectId} from the int buffer and
+ * position idx
+ *
+ * @param raw
+ * the raw data
+ * @param idx
+ * the index into {@code raw}
+ */
+ protected void setIdBuffer(int[] raw, int idx) {
+ entry.idBuffer.fromRaw(raw, idx);
+ }
+
+ /**
+ * Copies to the entry an {@link ObjectId} from the byte array at
+ * position idx.
+ *
+ * @param raw
+ * the raw data
+ * @param idx
+ * the index into {@code raw}
+ */
+ protected void setIdBuffer(byte[] raw, int idx) {
+ entry.idBuffer.fromRaw(raw, idx);
+ }
+
+ /**
+ * Sets the {@code offset} to the entry
+ *
+ * @param offset the offset in the pack file
+ */
+ protected void setOffset(long offset) {
+ entry.offset = offset;
+ }
@Override
public void remove() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index d7c8378..be48358 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -203,7 +203,7 @@ public boolean hasCRC32Support() {
@Override
public Iterator<MutableEntry> iterator() {
- return new IndexV1Iterator(objectCnt);
+ return new EntriesIteratorV1(this);
}
@Override
@@ -246,36 +246,30 @@ private static int idOffset(int mid) {
return packChecksum;
}
- private class IndexV1Iterator extends EntriesIterator {
- int levelOne;
+ private static class EntriesIteratorV1 extends EntriesIterator {
+ private int levelOne;
- int levelTwo;
+ private int levelTwo;
- IndexV1Iterator(long objectCount) {
- super(objectCount);
+ private final PackIndexV1 packIndex;
+
+ private EntriesIteratorV1(PackIndexV1 packIndex) {
+ super(packIndex.objectCnt);
+ this.packIndex = packIndex;
}
@Override
- protected MutableEntry initEntry() {
- return new MutableEntry() {
- @Override
- protected void ensureId() {
- idBuffer.fromRaw(idxdata[levelOne], levelTwo
- - Constants.OBJECT_ID_LENGTH);
- }
- };
- }
-
- @Override
- public MutableEntry next() {
- for (; levelOne < idxdata.length; levelOne++) {
- if (idxdata[levelOne] == null)
+ protected void readNext() {
+ for (; levelOne < packIndex.idxdata.length; levelOne++) {
+ if (packIndex.idxdata[levelOne] == null)
continue;
- if (levelTwo < idxdata[levelOne].length) {
- entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo);
- levelTwo += Constants.OBJECT_ID_LENGTH + 4;
- returnedNumber++;
- return entry;
+ if (levelTwo < packIndex.idxdata[levelOne].length) {
+ super.setOffset(NB.decodeUInt32(packIndex.idxdata[levelOne],
+ levelTwo));
+ this.levelTwo += Constants.OBJECT_ID_LENGTH + 4;
+ super.setIdBuffer(packIndex.idxdata[levelOne],
+ levelTwo - Constants.OBJECT_ID_LENGTH);
+ return;
}
levelTwo = 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
index caf8b71..36e54fc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
@@ -224,7 +224,7 @@ public boolean hasCRC32Support() {
@Override
public Iterator<MutableEntry> iterator() {
- return new EntriesIteratorV2(objectCnt);
+ return new EntriesIteratorV2(this);
}
@Override
@@ -289,41 +289,34 @@ else if (cmp == 0) {
return packChecksum;
}
- private class EntriesIteratorV2 extends EntriesIterator {
- int levelOne;
+ private static class EntriesIteratorV2 extends EntriesIterator {
+ private int levelOne = 0;
- int levelTwo;
+ private int levelTwo = 0;
- EntriesIteratorV2(long objectCount){
- super(objectCount);
+ private final PackIndexV2 packIndex;
+
+ private EntriesIteratorV2(PackIndexV2 packIndex) {
+ super(packIndex.objectCnt);
+ this.packIndex = packIndex;
}
@Override
- protected MutableEntry initEntry() {
- return new MutableEntry() {
- @Override
- protected void ensureId() {
- idBuffer.fromRaw(names[levelOne], levelTwo
- - Constants.OBJECT_ID_LENGTH / 4);
- }
- };
- }
-
- @Override
- public MutableEntry next() {
- for (; levelOne < names.length; levelOne++) {
- if (levelTwo < names[levelOne].length) {
+ protected void readNext() {
+ for (; levelOne < packIndex.names.length; levelOne++) {
+ if (levelTwo < packIndex.names[levelOne].length) {
int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4;
- long offset = NB.decodeUInt32(offset32[levelOne], idx);
+ long offset = NB.decodeUInt32(packIndex.offset32[levelOne],
+ idx);
if ((offset & IS_O64) != 0) {
idx = (8 * (int) (offset & ~IS_O64));
- offset = NB.decodeUInt64(offset64, idx);
+ offset = NB.decodeUInt64(packIndex.offset64, idx);
}
- entry.offset = offset;
-
- levelTwo += Constants.OBJECT_ID_LENGTH / 4;
- returnedNumber++;
- return entry;
+ super.setOffset(offset);
+ this.levelTwo += Constants.OBJECT_ID_LENGTH / 4;
+ super.setIdBuffer(packIndex.names[levelOne],
+ levelTwo - Constants.OBJECT_ID_LENGTH / 4);
+ return;
}
levelTwo = 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
index 7e28b5e..f0b6193 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
@@ -21,10 +21,10 @@
/**
* Creates the version 1 (old style) pack table of contents files.
*
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
* @see PackIndexV1
*/
-class PackIndexWriterV1 extends PackIndexWriter {
+class PackIndexWriterV1 extends BasePackIndexWriter {
static boolean canStore(PackedObjectInfo oe) {
// We are limited to 4 GB per pack as offset is 32 bit unsigned int.
//
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
index fc5ef61..b72b35a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
@@ -19,10 +19,10 @@
/**
* Creates the version 2 pack table of contents files.
*
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
* @see PackIndexV2
*/
-class PackIndexWriterV2 extends PackIndexWriter {
+class PackIndexWriterV2 extends BasePackIndexWriter {
private static final int MAX_OFFSET_32 = 0x7fffffff;
private static final int IS_OFFSET_64 = 0x80000000;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
index 1b092a3..55e047b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
@@ -77,6 +77,7 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -320,7 +321,8 @@ public void flush() throws IOException {
private static void writePackIndex(File idx, byte[] packHash,
List<PackedObjectInfo> list) throws IOException {
try (OutputStream os = new FileOutputStream(idx)) {
- PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION);
+ PackIndexWriter w = BasePackIndexWriter.createVersion(os,
+ INDEX_VERSION);
w.write(list, packHash);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
index ef9753c..720a3bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
@@ -75,20 +75,20 @@ long findNextOffset(long offset, long maxOffset)
throws CorruptObjectException;
/**
- * Find the position in the primary index of the object at the given pack
+ * Find the position in the reverse index of the object at the given pack
* offset.
*
* @param offset
* the pack offset of the object
- * @return the position in the primary index of the object
+ * @return the position in the reverse index of the object
*/
int findPosition(long offset);
/**
- * Find the object that is in the given position in the primary index.
+ * Find the object that is in the given position in the reverse index.
*
* @param nthPosition
- * the position of the object in the primary index
+ * the position of the object in the reverse index
* @return the object in that position
*/
ObjectId findObjectByPosition(int nthPosition);
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 cc48176..a1b7a9e 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
@@ -59,6 +59,7 @@
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.PackRefsCommand;
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -71,6 +72,7 @@
import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefComparator;
import org.eclipse.jgit.lib.RefDatabase;
@@ -292,6 +294,33 @@ public void refresh() {
clearReferences();
}
+ /**
+ * {@inheritDoc}
+ *
+ * For a RefDirectory database, by default this packs non-symbolic, loose
+ * tag refs into packed-refs. If {@code all} flag is set, this packs all the
+ * non-symbolic, loose refs.
+ */
+ @Override
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ String prefix = packRefs.isAll() ? R_REFS : R_TAGS;
+ Collection<Ref> refs = getRefsByPrefix(prefix);
+ List<String> refsToBePacked = new ArrayList<>(refs.size());
+ pm.beginTask(JGitText.get().packRefs, refs.size());
+ try {
+ for (Ref ref : refs) {
+ if (!ref.isSymbolic() && ref.getStorage().isLoose()) {
+ refsToBePacked.add(ref.getName());
+ }
+ pm.update(1);
+ }
+ pack(refsToBePacked);
+ } finally {
+ pm.endTask();
+ }
+ }
+
@Override
public boolean isNameConflicting(String name) throws IOException {
// Cannot be nested within an existing reference.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
index 01f514b..11c4547 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
@@ -205,7 +205,7 @@ public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
* @param cnt
* number of bytes to copy. This value may exceed the number of
* bytes remaining in the window starting at offset
- * <code>pos</code>.
+ * <code>position</code>.
* @return number of bytes actually copied; this may be less than
* <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
* available.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java
new file mode 100644
index 0000000..f69e68d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.pack;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/**
+ * Represents a function that accepts a collection of objects to write into a
+ * primary pack index storage format.
+ */
+public interface PackIndexWriter {
+ /**
+ * Write all object entries to the index stream.
+ *
+ * @param toStore
+ * sorted list of objects to store in the index. The caller must
+ * have previously sorted the list using
+ * {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native
+ * {@link java.lang.Comparable} implementation.
+ * @param packDataChecksum
+ * checksum signature of the entire pack data content. This is
+ * traditionally the last 20 bytes of the pack file's own stream.
+ * @throws java.io.IOException
+ * an error occurred while writing to the output stream, or the
+ * underlying format cannot store the object data supplied.
+ */
+ void write(List<? extends PackedObjectInfo> toStore,
+ byte[] packDataChecksum) throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index ca4598d..27fb814 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -58,10 +58,10 @@
import org.eclipse.jgit.errors.SearchForReuseTimeout;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackReverseIndexWriter;
-import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
import org.eclipse.jgit.lib.BatchingProgressMonitor;
@@ -118,7 +118,7 @@
* {@link #preparePack(ProgressMonitor, Set, Set)}, and streaming with
* {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. If the
* pack is being stored as a file the matching index can be written out after
- * writing the pack by {@link #writeIndex(OutputStream)}. An optional bitmap
+ * writing the pack by {@link #writeIndex(PackIndexWriter)}. An optional bitmap
* index can be made by calling {@link #prepareBitmapIndex(ProgressMonitor)}
* followed by {@link #writeBitmapIndex(PackBitmapIndexWriter)}.
* </p>
@@ -303,19 +303,6 @@ public PackWriter(Repository repo) {
}
/**
- * Create a writer to load objects from the specified reader.
- * <p>
- * Objects for packing are specified in {@link #preparePack(Iterator)} or
- * {@link #preparePack(ProgressMonitor, Set, Set)}.
- *
- * @param reader
- * reader to read from the repository with.
- */
- public PackWriter(ObjectReader reader) {
- this(new PackConfig(), reader);
- }
-
- /**
* Create writer for specified repository.
* <p>
* Objects for packing are specified in {@link #preparePack(Iterator)} or
@@ -1091,7 +1078,7 @@ public int getIndexVersion() {
if (indexVersion <= 0) {
for (BlockList<ObjectToPack> objs : objectsLists)
indexVersion = Math.max(indexVersion,
- PackIndexWriter.oldestPossibleFormat(objs));
+ BasePackIndexWriter.oldestPossibleFormat(objs));
}
return indexVersion;
}
@@ -1112,12 +1099,28 @@ public int getIndexVersion() {
* the index data could not be written to the supplied stream.
*/
public void writeIndex(OutputStream indexStream) throws IOException {
+ writeIndex(BasePackIndexWriter.createVersion(indexStream,
+ getIndexVersion()));
+ }
+
+ /**
+ * Create an index file to match the pack file just written.
+ * <p>
+ * Called after
+ * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}.
+ * <p>
+ * Writing an index is only required for local pack storage. Packs sent on
+ * the network do not need to create an index.
+ *
+ * @param iw
+ * an {@link PackIndexWriter} instance to write the index
+ * @throws java.io.IOException
+ * the index data could not be written to the supplied stream.
+ */
+ public void writeIndex(PackIndexWriter iw) throws IOException {
if (isIndexDisabled())
throw new IOException(JGitText.get().cachedPacksPreventsIndexCreation);
-
long writeStart = System.currentTimeMillis();
- final PackIndexWriter iw = PackIndexWriter.createVersion(
- indexStream, getIndexVersion());
iw.write(sortByName(), packcsum);
stats.timeWriting += System.currentTimeMillis() - writeStart;
}
@@ -2498,7 +2501,7 @@ private final boolean have(ObjectToPack ptr, AnyObjectId objectId) {
* object graph at selected commits. Writing a bitmap index is an optional
* feature that not all pack users may require.
* <p>
- * Called after {@link #writeIndex(OutputStream)}.
+ * Called after {@link #writeIndex(PackIndexWriter)}.
* <p>
* To reduce memory internal state is cleared during this method, rendering
* the PackWriter instance useless for anything further than a call to write
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index 3e75a9d..542d6e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -427,7 +427,35 @@ protected List<String> validate(String key, List<String> value) {
return value;
}
- private static boolean patternMatchesHost(String pattern, String name) {
+ /**
+ * Tells whether a given {@code name} matches the given list of patterns,
+ * accounting for negative matches.
+ *
+ * @param patterns
+ * to test {@code name} against; any pattern starting with an
+ * exclamation mark is a negative pattern
+ * @param name
+ * to test
+ * @return {@code true} if the {@code name} matches at least one of the
+ * non-negative patterns and none of the negative patterns,
+ * {@code false} otherwise
+ * @since 7.1
+ */
+ public static boolean patternMatch(Iterable<String> patterns, String name) {
+ boolean doesMatch = false;
+ for (String pattern : patterns) {
+ if (pattern.startsWith("!")) { //$NON-NLS-1$
+ if (patternMatches(pattern.substring(1), name)) {
+ return false;
+ }
+ } else if (!doesMatch && patternMatches(pattern, name)) {
+ doesMatch = true;
+ }
+ }
+ return doesMatch;
+ }
+
+ private static boolean patternMatches(String pattern, String name) {
if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
final FileNameMatcher fn;
try {
@@ -680,18 +708,7 @@ public HostEntry(List<String> patterns) {
}
boolean matches(String hostName) {
- boolean doesMatch = false;
- for (String pattern : patterns) {
- if (pattern.startsWith("!")) { //$NON-NLS-1$
- if (patternMatchesHost(pattern.substring(1), hostName)) {
- return false;
- }
- } else if (!doesMatch
- && patternMatchesHost(pattern, hostName)) {
- doesMatch = true;
- }
- }
- return doesMatch;
+ return patternMatch(patterns, hostName);
}
private static String toKey(String key) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index d232be6..0c1da83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -15,6 +15,7 @@
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
import static org.eclipse.jgit.lib.Constants.CONFIG;
import static org.eclipse.jgit.lib.Constants.DOT_GIT;
+import static org.eclipse.jgit.lib.Constants.GITDIR_FILE;
import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_COMMON_DIR_KEY;
@@ -23,7 +24,6 @@
import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;
import static org.eclipse.jgit.lib.Constants.OBJECTS;
-import static org.eclipse.jgit.lib.Constants.GITDIR_FILE;
import java.io.File;
import java.io.IOException;
@@ -485,7 +485,7 @@ public B readEnvironment(SystemReader sr) {
if (getAlternateObjectDirectories() == null) {
String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
if (val != null) {
- for (String path : val.split(File.pathSeparator))
+ for (String path : val.split(File.pathSeparator, -1))
addAlternateObjectDirectory(new File(path));
}
}
@@ -505,7 +505,7 @@ public B readEnvironment(SystemReader sr) {
if (ceilingDirectories == null) {
String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY);
if (val != null) {
- for (String path : val.split(File.pathSeparator))
+ for (String path : val.split(File.pathSeparator, -1))
addCeilingDirectory(new File(path));
}
}
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 acb54d7..a57f1b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -206,7 +206,36 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_SIGNINGKEY = "signingKey";
/**
+ * The "ssh" subsection key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_SSH_SUBSECTION = "ssh";
+
+ /**
+ * The "defaultKeyCommand" key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND = "defaultKeyCommand";
+
+ /**
+ * The "allowedSignersFile" key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE = "allowedSignersFile";
+
+ /**
+ * The "revocationFile" key,
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_REVOCATION_FILE = "revocationFile";
+
+ /**
* The "commit" section
+ *
* @since 5.2
*/
public static final String CONFIG_COMMIT_SECTION = "commit";
@@ -1027,4 +1056,11 @@ public final class ConfigConstants {
* @since 7.0
*/
public static final String CONFIG_KEY_USE_OBJECT_SIZE_INDEX = "useObjectSizeIndex";
+
+ /**
+ * The "loadRevIndexInParallel" key
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL = "loadRevIndexInParallel";
}
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 fb5c904..76ed36a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
@@ -61,6 +61,12 @@ public String toConfigValue() {
private final boolean forceAnnotated;
+ private final String sshDefaultKeyCommand;
+
+ private final String sshAllowedSignersFile;
+
+ private final String sshRevocationFile;
+
/**
* Create a new GPG config that reads the configuration from config.
*
@@ -88,6 +94,17 @@ public GpgConfig(Config config) {
ConfigConstants.CONFIG_KEY_GPGSIGN, false);
forceAnnotated = config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION,
ConfigConstants.CONFIG_KEY_FORCE_SIGN_ANNOTATED, false);
+ sshDefaultKeyCommand = config.getString(
+ ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND);
+ sshAllowedSignersFile = config.getString(
+ ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE);
+ sshRevocationFile = config.getString(ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_REVOCATION_FILE);
}
/**
@@ -151,4 +168,37 @@ public boolean isSignAllTags() {
public boolean isSignAnnotated() {
return forceAnnotated;
}
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.defaultKeyCommand}.
+ *
+ * @return the value of {@code gpg.ssh.defaultKeyCommand}
+ *
+ * @since 7.1
+ */
+ public String getSshDefaultKeyCommand() {
+ return sshDefaultKeyCommand;
+ }
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.allowedSignersFile}.
+ *
+ * @return the value of {@code gpg.ssh.allowedSignersFile}
+ *
+ * @since 7.1
+ */
+ public String getSshAllowedSignersFile() {
+ return sshAllowedSignersFile;
+ }
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.revocationFile}.
+ *
+ * @return the value of {@code gpg.ssh.revocationFile}
+ *
+ * @since 7.1
+ */
+ public String getSshRevocationFile() {
+ return sshRevocationFile;
+ }
}
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 3ba055a..5d3db9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -13,9 +13,11 @@
package org.eclipse.jgit.lib;
import java.io.Serializable;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -49,6 +51,17 @@ 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
+ * @since 7.1
+ */
+ public static ZoneId getZoneId(int tzOffset) {
+ return ZoneOffset.ofHoursMinutes(tzOffset / 60, tzOffset % 60);
+ }
+
+ /**
* Format a timezone offset.
*
* @param r
@@ -121,13 +134,17 @@ public static void appendSanitized(StringBuilder r, String str) {
}
}
+ // Write offsets as [+-]HHMM
+ private static final DateTimeFormatter OFFSET_FORMATTER = DateTimeFormatter
+ .ofPattern("Z", Locale.US); //$NON-NLS-1$
+
private final String name;
private final String emailAddress;
- private final long when;
+ private final Instant when;
- private final int tzOffset;
+ private final ZoneId tzOffset;
/**
* Creates new PersonIdent from config info in repository, with current time.
@@ -160,7 +177,7 @@ public PersonIdent(PersonIdent pi) {
* a {@link java.lang.String} object.
*/
public PersonIdent(String aName, String aEmailAddress) {
- this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime());
+ this(aName, aEmailAddress, SystemReader.getInstance().now());
}
/**
@@ -177,7 +194,7 @@ public PersonIdent(String aName, String aEmailAddress) {
*/
public PersonIdent(String aName, String aEmailAddress,
ProposedTimestamp when) {
- this(aName, aEmailAddress, when.millis());
+ this(aName, aEmailAddress, when.instant());
}
/**
@@ -189,8 +206,25 @@ public PersonIdent(String aName, String aEmailAddress,
* local time
* @param tz
* time zone
+ * @deprecated Use {@link #PersonIdent(PersonIdent, Instant, ZoneId)} instead.
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
+ this(pi.getName(), pi.getEmailAddress(), when.toInstant(), tz.toZoneId());
+ }
+
+ /**
+ * Copy a PersonIdent, but alter the clone's time stamp
+ *
+ * @param pi
+ * original {@link org.eclipse.jgit.lib.PersonIdent}
+ * @param when
+ * local time
+ * @param tz
+ * time zone offset
+ * @since 7.1
+ */
+ public PersonIdent(PersonIdent pi, Instant when, ZoneId tz) {
this(pi.getName(), pi.getEmailAddress(), when, tz);
}
@@ -202,9 +236,12 @@ public PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
* original {@link org.eclipse.jgit.lib.PersonIdent}
* @param aWhen
* local time
+ * @deprecated Use the variant with an Instant instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, Date aWhen) {
- this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset);
+ this(pi.getName(), pi.getEmailAddress(), aWhen.toInstant(),
+ pi.tzOffset);
}
/**
@@ -218,7 +255,7 @@ public PersonIdent(PersonIdent pi, Date aWhen) {
* @since 6.1
*/
public PersonIdent(PersonIdent pi, Instant aWhen) {
- this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
+ this(pi.getName(), pi.getEmailAddress(), aWhen, pi.tzOffset);
}
/**
@@ -230,11 +267,12 @@ public PersonIdent(PersonIdent pi, Instant aWhen) {
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(final String aName, final String aEmailAddress,
final Date aWhen, final TimeZone aTZ) {
- this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
- .getTime()) / (60 * 1000));
+ this(aName, aEmailAddress, aWhen.toInstant(), aTZ.toZoneId());
}
/**
@@ -252,10 +290,16 @@ public PersonIdent(final String aName, final String aEmailAddress,
*/
public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
ZoneId zoneId) {
- this(aName, aEmailAddress, aWhen.toEpochMilli(),
- TimeZone.getTimeZone(zoneId)
- .getOffset(aWhen
- .toEpochMilli()) / (60 * 1000));
+ if (aName == null)
+ throw new IllegalArgumentException(
+ JGitText.get().personIdentNameNonNull);
+ if (aEmailAddress == null)
+ throw new IllegalArgumentException(
+ JGitText.get().personIdentEmailNonNull);
+ name = aName;
+ emailAddress = aEmailAddress;
+ when = aWhen;
+ tzOffset = zoneId;
}
/**
@@ -267,15 +311,18 @@ public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
- this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
+ this(pi.getName(), pi.getEmailAddress(), Instant.ofEpochMilli(aWhen),
+ getZoneId(aTZ));
}
private PersonIdent(final String aName, final String aEmailAddress,
- long when) {
+ Instant when) {
this(aName, aEmailAddress, when, SystemReader.getInstance()
- .getTimezone(when));
+ .getTimeZoneAt(when));
}
private PersonIdent(UserConfig config) {
@@ -298,19 +345,12 @@ private PersonIdent(UserConfig config) {
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(final String aName, final String aEmailAddress,
final long aWhen, final int aTZ) {
- if (aName == null)
- throw new IllegalArgumentException(
- JGitText.get().personIdentNameNonNull);
- if (aEmailAddress == null)
- throw new IllegalArgumentException(
- JGitText.get().personIdentEmailNonNull);
- name = aName;
- emailAddress = aEmailAddress;
- when = aWhen;
- tzOffset = aTZ;
+ this(aName, aEmailAddress, Instant.ofEpochMilli(aWhen), getZoneId(aTZ));
}
/**
@@ -335,9 +375,12 @@ public String getEmailAddress() {
* Get timestamp
*
* @return timestamp
+ *
+ * @deprecated Use getWhenAsInstant instead
*/
+ @Deprecated(since = "7.1")
public Date getWhen() {
- return new Date(when);
+ return Date.from(when);
}
/**
@@ -347,16 +390,19 @@ public Date getWhen() {
* @since 6.1
*/
public Instant getWhenAsInstant() {
- return Instant.ofEpochMilli(when);
+ return when;
}
/**
* Get this person's declared time zone
*
* @return this person's declared time zone; null if time zone is unknown.
+ *
+ * @deprecated Use getZoneId instead
*/
+ @Deprecated(since = "7.1")
public TimeZone getTimeZone() {
- return getTimeZone(tzOffset);
+ return TimeZone.getTimeZone(tzOffset);
}
/**
@@ -366,7 +412,17 @@ public TimeZone getTimeZone() {
* @since 6.1
*/
public ZoneId getZoneId() {
- return getTimeZone().toZoneId();
+ return tzOffset;
+ }
+
+ /**
+ * Return the offset in this timezone at the specific time
+ *
+ * @return the offset
+ * @since 7.1
+ */
+ public ZoneOffset getZoneOffset() {
+ return tzOffset.getRules().getOffset(when);
}
/**
@@ -374,9 +430,11 @@ public ZoneId getZoneId() {
*
* @return this person's declared time zone as minutes east of UTC. If the
* timezone is to the west of UTC it is negative.
+ * @deprecated Use {@link #getZoneOffset()} and read minutes from there
*/
+ @Deprecated(since = "7.1")
public int getTimeZoneOffset() {
- return tzOffset;
+ return getZoneOffset().getTotalSeconds() / 60;
}
/**
@@ -388,7 +446,7 @@ public int getTimeZoneOffset() {
public int hashCode() {
int hc = getEmailAddress().hashCode();
hc *= 31;
- hc += (int) (when / 1000L);
+ hc += when.hashCode();
return hc;
}
@@ -398,7 +456,9 @@ public boolean equals(Object o) {
final PersonIdent p = (PersonIdent) o;
return getName().equals(p.getName())
&& getEmailAddress().equals(p.getEmailAddress())
- && when / 1000L == p.when / 1000L;
+ // commmit timestamps are stored with 1 second precision
+ && when.truncatedTo(ChronoUnit.SECONDS)
+ .equals(p.when.truncatedTo(ChronoUnit.SECONDS));
}
return false;
}
@@ -414,9 +474,9 @@ public String toExternalString() {
r.append(" <"); //$NON-NLS-1$
appendSanitized(r, getEmailAddress());
r.append("> "); //$NON-NLS-1$
- r.append(when / 1000);
+ r.append(when.toEpochMilli() / 1000);
r.append(' ');
- appendTimezone(r, tzOffset);
+ r.append(OFFSET_FORMATTER.format(getZoneOffset()));
return r.toString();
}
@@ -424,19 +484,16 @@ public String toExternalString() {
@SuppressWarnings("nls")
public String toString() {
final StringBuilder r = new StringBuilder();
- final SimpleDateFormat dtfmt;
- dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
- dtfmt.setTimeZone(getTimeZone());
-
+ DateTimeFormatter dtfmt = DateTimeFormatter
+ .ofPattern("EEE MMM d HH:mm:ss yyyy Z", Locale.US) //$NON-NLS-1$
+ .withZone(tzOffset);
r.append("PersonIdent[");
r.append(getName());
r.append(", ");
r.append(getEmailAddress());
r.append(", ");
- r.append(dtfmt.format(Long.valueOf(when)));
+ r.append(dtfmt.format(when));
r.append("]");
-
return r.toString();
}
}
-
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 2cf2418..09cb5a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -26,6 +26,7 @@
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.PackRefsCommand;
/**
* Abstraction of name to {@link org.eclipse.jgit.lib.ObjectId} mapping.
@@ -160,7 +161,7 @@ public Collection<String> getConflictingNames(String name)
if (existing.startsWith(prefix))
conflicting.add(existing);
- return conflicting;
+ return Collections.unmodifiableList(conflicting);
}
/**
@@ -593,4 +594,22 @@ public static Ref findRef(Map<String, Ref> map, String name) {
}
return null;
}
+
+ /**
+ * Optimize pack ref storage.
+ *
+ * @param pm
+ * a progress monitor
+ *
+ * @param packRefs
+ * {@link PackRefsCommand} to control ref packing behavior
+ *
+ * @throws java.io.IOException
+ * if an IO error occurred
+ * @since 7.1
+ */
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ // nothing
+ }
}
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 1162a61..fc5ab62 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -22,6 +22,7 @@
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
+import java.util.stream.Collectors;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -185,12 +186,15 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
if (mergeTrees(bcTree, currentBase.getTree(),
nextBase.getTree(), true))
currentBase = createCommitForTree(resultTree, parents);
- else
+ else {
+ String failedPaths = failingPathsMessage();
throw new NoMergeBaseException(
NoMergeBaseException.MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION,
MessageFormat.format(
JGitText.get().mergeRecursiveConflictsWhenMergingCommonAncestors,
- currentBase.getName(), nextBase.getName()));
+ currentBase.getName(), nextBase.getName(),
+ failedPaths));
+ }
}
} finally {
inCore = oldIncore;
@@ -236,4 +240,17 @@ private static PersonIdent mockAuthor(List<RevCommit> parents) {
new Date((time + 1) * 1000L),
TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
}
+
+ private String failingPathsMessage() {
+ int max = 25;
+ String failedPaths = failingPaths.entrySet().stream().limit(max)
+ .map(entry -> entry.getKey() + ":" + entry.getValue()) //$NON-NLS-1$
+ .collect(Collectors.joining("\n")); //$NON-NLS-1$
+
+ if (failingPaths.size() > max) {
+ failedPaths = String.format("%s\n... (%s failing paths omitted)", //$NON-NLS-1$
+ failedPaths, Integer.valueOf(failingPaths.size() - max));
+ }
+ return failedPaths;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index a50a644..dc96f65 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -1281,6 +1281,13 @@ protected boolean processEntry(CanonicalTreeParser base,
default:
break;
}
+ if (ignoreConflicts) {
+ // If the path is selected to be treated as binary via attributes, we do not perform
+ // content merge. When ignoreConflicts = true, we simply keep OURS to allow virtual commit
+ // to be built.
+ keep(ourDce);
+ return true;
+ }
// add the conflicting path to merge result
String currentPath = tw.getPathString();
MergeResult<RawText> result = new MergeResult<>(
@@ -1320,8 +1327,12 @@ protected boolean processEntry(CanonicalTreeParser base,
addToCheckout(currentPath, null, attributes);
return true;
} catch (BinaryBlobException e) {
- // if the file is binary in either OURS, THEIRS or BASE
- // here, we don't have an option to ignore conflicts
+ // The file is binary in either OURS, THEIRS or BASE
+ if (ignoreConflicts) {
+ // When ignoreConflicts = true, we simply keep OURS to allow virtual commit to be built.
+ keep(ourDce);
+ return true;
+ }
}
}
switch (getContentMergeStrategy()) {
@@ -1362,6 +1373,8 @@ protected boolean processEntry(CanonicalTreeParser base,
}
}
} else {
+ // This is reachable if contentMerge() call above threw BinaryBlobException, so we don't
+ // need to check ignoreConflicts here, since it's already handled above.
result.setContainsConflicts(true);
addConflict(base, ours, theirs);
unmergedPaths.add(currentPath);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index 8373d68..863b794 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -50,7 +50,7 @@
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -995,7 +995,7 @@ public void setExecutor(Executor executor) {
*
* @return the index version, the special version 0 designates the oldest
* (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public int getIndexVersion() {
return indexVersion;
@@ -1009,7 +1009,7 @@ public int getIndexVersion() {
* @param version
* the version to write. The special version 0 designates the
* oldest (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public void setIndexVersion(int version) {
indexVersion = version;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
index a0194ea..8120df0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -11,8 +11,6 @@
package org.eclipse.jgit.transport;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.ServiceLoader;
@@ -99,9 +97,8 @@ public static void setInstance(SshSessionFactory newFactory) {
* @since 5.2
*/
public static String getLocalUserName() {
- return AccessController
- .doPrivileged((PrivilegedAction<String>) () -> SystemReader
- .getInstance().getProperty(Constants.OS_USER_NAME_KEY));
+ return SystemReader.getInstance()
+ .getProperty(Constants.OS_USER_NAME_KEY);
}
/**
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 5ba8270..d972067 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -2384,7 +2384,8 @@ else if (ref.getName().startsWith(Constants.R_HEADS))
: req.getDepth() - 1;
pw.setShallowPack(req.getDepth(), unshallowCommits);
- // Ownership is transferred below
+ // dw borrows the reader from walk which is closed by #close
+ @SuppressWarnings("resource")
DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
walk.getObjectReader(), walkDepth);
dw.setDeepenSince(req.getDeepenSince());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index 125ee6c..95b8221 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -36,29 +36,39 @@
*/
public interface HttpConnection {
/**
+ * HttpURLConnection#HTTP_OK
+ *
* @see HttpURLConnection#HTTP_OK
*/
int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
/**
+ * HttpURLConnection#HTTP_NOT_AUTHORITATIVE
+ *
* @see HttpURLConnection#HTTP_NOT_AUTHORITATIVE
* @since 5.8
*/
int HTTP_NOT_AUTHORITATIVE = java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
/**
+ * HttpURLConnection#HTTP_MOVED_PERM
+ *
* @see HttpURLConnection#HTTP_MOVED_PERM
* @since 4.7
*/
int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
/**
+ * HttpURLConnection#HTTP_MOVED_TEMP
+ *
* @see HttpURLConnection#HTTP_MOVED_TEMP
* @since 4.9
*/
int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
/**
+ * HttpURLConnection#HTTP_SEE_OTHER
+ *
* @see HttpURLConnection#HTTP_SEE_OTHER
* @since 4.9
*/
@@ -85,16 +95,22 @@ public interface HttpConnection {
int HTTP_11_MOVED_PERM = 308;
/**
+ * HttpURLConnection#HTTP_NOT_FOUND
+ *
* @see HttpURLConnection#HTTP_NOT_FOUND
*/
int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
/**
+ * HttpURLConnection#HTTP_UNAUTHORIZED
+ *
* @see HttpURLConnection#HTTP_UNAUTHORIZED
*/
int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
/**
+ * HttpURLConnection#HTTP_FORBIDDEN
+ *
* @see HttpURLConnection#HTTP_FORBIDDEN
*/
int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
index bcf79a2..33db6ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
@@ -13,12 +13,12 @@
package org.eclipse.jgit.treewalk.filter;
-import org.eclipse.jgit.util.RawParseUtils;
-
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
+import org.eclipse.jgit.util.RawParseUtils;
+
/**
* Specialized set for byte arrays, interpreted as strings for use in
* {@link PathFilterGroup.Group}. Most methods assume the hash is already know
@@ -141,13 +141,19 @@ boolean contains(byte[] toFind, int length, int hash) {
}
/**
+ * Returns number of arrays in the set
+ *
* @return number of arrays in the set
*/
int size() {
return size;
}
- /** @return true if {@link #size()} is 0. */
+ /**
+ * Returns true if {@link #size()} is 0
+ *
+ * @return true if {@link #size()} is 0
+ */
boolean isEmpty() {
return size == 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index 860c1c9..59bbacf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -30,7 +30,6 @@
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
-import java.security.AccessControlException;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
@@ -262,31 +261,6 @@ public static final class FileStoreAttributes {
private static final AtomicInteger threadNumber = new AtomicInteger(1);
/**
- * Don't use the default thread factory of the ForkJoinPool for the
- * CompletableFuture; it runs without any privileges, which causes
- * trouble if a SecurityManager is present.
- * <p>
- * Instead use normal daemon threads. They'll belong to the
- * SecurityManager's thread group, or use the one of the calling thread,
- * as appropriate.
- * </p>
- *
- * @see java.util.concurrent.Executors#newCachedThreadPool()
- */
- private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor(
- 5, 5, 30L, TimeUnit.SECONDS,
- new LinkedBlockingQueue<>(),
- runnable -> {
- Thread t = new Thread(runnable,
- "JGit-FileStoreAttributeReader-" //$NON-NLS-1$
- + threadNumber.getAndIncrement());
- // Make sure these threads don't prevent application/JVM
- // shutdown.
- t.setDaemon(true);
- return t;
- });
-
- /**
* Use a separate executor with at most one thread to synchronize
* writing to the config. We write asynchronously since the config
* itself might be on a different file system, which might otherwise
@@ -463,7 +437,7 @@ private static FileStoreAttributes getFileStoreAttributes(Path dir) {
locks.remove(s);
}
return attributes;
- }, FUTURE_RUNNER);
+ });
f = f.exceptionally(e -> {
LOG.error(e.getLocalizedMessage(), e);
return Optional.empty();
@@ -1391,13 +1365,6 @@ protected static String readPipe(File dir, String[] command,
}
} catch (IOException e) {
LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
- } catch (AccessControlException e) {
- LOG.warn(MessageFormat.format(
- JGitText.get().readPipeIsNotAllowedRequiredPermission,
- command, dir, e.getPermission()));
- } catch (SecurityException e) {
- LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
- command, dir));
}
if (debug) {
LOG.debug("readpipe returns null"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
index 635351a..2378791 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java
@@ -14,8 +14,6 @@
import java.io.File;
import java.io.OutputStream;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,10 +41,7 @@ public class FS_Win32_Cygwin extends FS_Win32 {
* @return true if cygwin is found
*/
public static boolean isCygwin() {
- final String path = AccessController
- .doPrivileged((PrivilegedAction<String>) () -> System
- .getProperty("java.library.path") //$NON-NLS-1$
- );
+ final String path = System.getProperty("java.library.path"); //$NON-NLS-1$
if (path == null)
return false;
File found = FS.searchPath(path, "cygpath.exe"); //$NON-NLS-1$
@@ -99,9 +94,7 @@ public File resolve(File dir, String pn) {
@Override
protected File userHomeImpl() {
- final String home = AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> System.getenv("HOME") //$NON-NLS-1$
- );
+ final String home = System.getenv("HOME"); //$NON-NLS-1$
if (home == null || home.length() == 0)
return super.userHomeImpl();
return resolve(new File("."), home); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
index 6a4b396..f080056 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
@@ -28,7 +28,10 @@
* used. One example is the parsing of the config parameter gc.pruneexpire. The
* parser can handle only subset of what native gits approxidate parser
* understands.
+ *
+ * @deprecated Use {@link GitTimeParser} instead.
*/
+@Deprecated(since = "7.1")
public class GitDateParser {
/**
* The Date representing never. Though this is a concrete value, most
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
new file mode 100644
index 0000000..7d00fcd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 Christian Halstrick 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.util;
+
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Parses strings with time and date specifications into
+ * {@link java.time.Instant}.
+ *
+ * When git needs to parse strings specified by the user this parser can be
+ * used. One example is the parsing of the config parameter gc.pruneexpire. The
+ * parser can handle only subset of what native gits approxidate parser
+ * understands.
+ *
+ * @since 7.1
+ */
+public class GitTimeParser {
+
+ private static final Map<ParseableSimpleDateFormat, DateTimeFormatter> formatCache = new EnumMap<>(
+ ParseableSimpleDateFormat.class);
+
+ // An enum of all those formats which this parser can parse with the help of
+ // a DateTimeFormatter. There are other formats (e.g. the relative formats
+ // like "yesterday" or "1 week ago") which this parser can parse but which
+ // are not listed here because they are parsed without the help of a
+ // DateTimeFormatter.
+ enum ParseableSimpleDateFormat {
+ ISO("yyyy-MM-dd HH:mm:ss Z"), // //$NON-NLS-1$
+ RFC("EEE, dd MMM yyyy HH:mm:ss Z"), // //$NON-NLS-1$
+ SHORT("yyyy-MM-dd"), // //$NON-NLS-1$
+ SHORT_WITH_DOTS_REVERSE("dd.MM.yyyy"), // //$NON-NLS-1$
+ SHORT_WITH_DOTS("yyyy.MM.dd"), // //$NON-NLS-1$
+ SHORT_WITH_SLASH("MM/dd/yyyy"), // //$NON-NLS-1$
+ DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$
+ LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$
+
+ private final String formatStr;
+
+ ParseableSimpleDateFormat(String formatStr) {
+ this.formatStr = formatStr;
+ }
+ }
+
+ private GitTimeParser() {
+ // This class is not supposed to be instantiated
+ }
+
+ /**
+ * Parses a string into a {@link java.time.LocalDateTime} 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.LocalDateTime}
+ * @throws java.text.ParseException
+ * if the given dateStr was not recognized
+ */
+ public static LocalDateTime parse(String dateStr) throws ParseException {
+ return parse(dateStr, SystemReader.getInstance().civilNow());
+ }
+
+ // Only tests seem to use this method
+ static LocalDateTime parse(String dateStr, LocalDateTime now)
+ throws ParseException {
+ dateStr = dateStr.trim();
+
+ if (dateStr.equalsIgnoreCase("never")) { //$NON-NLS-1$
+ return LocalDateTime.MAX;
+ }
+ LocalDateTime ret = parseRelative(dateStr, now);
+ if (ret != null) {
+ return ret;
+ }
+ for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) {
+ try {
+ return parseSimple(dateStr, f);
+ } catch (DateTimeParseException e) {
+ // simply proceed with the next parser
+ }
+ }
+ ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values();
+ StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$
+ .append(values[0].formatStr);
+ for (int i = 1; i < values.length; i++) {
+ allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$
+ }
+ allFormats.append("\""); //$NON-NLS-1$
+ throw new ParseException(
+ MessageFormat.format(JGitText.get().cannotParseDate, dateStr,
+ allFormats.toString()),
+ 0);
+ }
+
+ // tries to parse a string with the formats supported by DateTimeFormatter
+ private static LocalDateTime parseSimple(String dateStr,
+ ParseableSimpleDateFormat f) throws DateTimeParseException {
+ DateTimeFormatter dateFormat = formatCache.computeIfAbsent(f,
+ format -> DateTimeFormatter
+ .ofPattern(f.formatStr)
+ .withLocale(SystemReader.getInstance().getLocale()));
+ TemporalAccessor parsed = dateFormat.parse(dateStr);
+ return parsed.isSupported(ChronoField.HOUR_OF_DAY)
+ ? LocalDateTime.from(parsed)
+ : LocalDate.from(parsed).atStartOfDay();
+ }
+
+ // tries to parse a string with a relative time specification
+ @SuppressWarnings("nls")
+ @Nullable
+ private static LocalDateTime parseRelative(String dateStr,
+ LocalDateTime now) {
+ // check for the static words "yesterday" or "now"
+ if (dateStr.equals("now")) {
+ return now;
+ }
+
+ if (dateStr.equals("yesterday")) {
+ return now.minusDays(1);
+ }
+
+ // parse constructs like "3 days ago", "5.week.2.day.ago"
+ String[] parts = dateStr.split("\\.| ", -1);
+ int partsLength = parts.length;
+ // check we have an odd number of parts (at least 3) and that the last
+ // part is "ago"
+ if (partsLength < 3 || (partsLength & 1) == 0
+ || !parts[parts.length - 1].equals("ago")) {
+ return null;
+ }
+ int number;
+ for (int i = 0; i < parts.length - 2; i += 2) {
+ try {
+ number = Integer.parseInt(parts[i]);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ if (parts[i + 1] == null) {
+ return null;
+ }
+ switch (parts[i + 1]) {
+ case "year":
+ case "years":
+ now = now.minusYears(number);
+ break;
+ case "month":
+ case "months":
+ now = now.minusMonths(number);
+ break;
+ case "week":
+ case "weeks":
+ now = now.minusWeeks(number);
+ break;
+ case "day":
+ case "days":
+ now = now.minusDays(number);
+ break;
+ case "hour":
+ case "hours":
+ now = now.minusHours(number);
+ break;
+ case "minute":
+ case "minutes":
+ now = now.minusMinutes(number);
+ break;
+ case "second":
+ case "seconds":
+ now = now.minusSeconds(number);
+ break;
+ default:
+ return null;
+ }
+ }
+ return now;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
index d957deb..efa6e7dd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
@@ -43,14 +43,18 @@ public void add(double x) {
}
/**
- * @return number of the added values
+ * Returns the number of added values
+ *
+ * @return the number of added values
*/
public int count() {
return n;
}
/**
- * @return minimum of the added values
+ * Returns the smallest value added
+ *
+ * @return the smallest value added
*/
public double min() {
if (n < 1) {
@@ -60,7 +64,9 @@ public double min() {
}
/**
- * @return maximum of the added values
+ * Returns the biggest value added
+ *
+ * @return the biggest value added
*/
public double max() {
if (n < 1) {
@@ -70,9 +76,10 @@ public double max() {
}
/**
- * @return average of the added values
+ * Returns the average of the added values
+ *
+ * @return the average of the added values
*/
-
public double avg() {
if (n < 1) {
return Double.NaN;
@@ -81,7 +88,9 @@ public double avg() {
}
/**
- * @return variance of the added values
+ * Returns the variance of the added values
+ *
+ * @return the variance of the added values
*/
public double var() {
if (n < 2) {
@@ -91,7 +100,9 @@ public double var() {
}
/**
- * @return standard deviation of the added values
+ * Returns the standard deviation of the added values
+ *
+ * @return the standard deviation of the added values
*/
public double stddev() {
return Math.sqrt(this.var());
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 ed62c71..55cc878 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -23,10 +23,12 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
@@ -169,6 +171,11 @@ public long getCurrentTime() {
}
@Override
+ public Instant now() {
+ return Instant.now();
+ }
+
+ @Override
public int getTimezone(long when) {
return getTimeZone().getOffset(when) / (60 * 1000);
}
@@ -230,9 +237,19 @@ public long getCurrentTime() {
}
@Override
+ public Instant now() {
+ return delegate.now();
+ }
+
+ @Override
public int getTimezone(long when) {
return delegate.getTimezone(when);
}
+
+ @Override
+ public ZoneOffset getTimeZoneAt(Instant when) {
+ return delegate.getTimeZoneAt(when);
+ }
}
private static volatile SystemReader INSTANCE = DEFAULT;
@@ -503,10 +520,37 @@ private void updateAll(Config config)
* Get the current system time
*
* @return the current system time
+ *
+ * @deprecated Use {@link #now()}
*/
+ @Deprecated
public abstract long getCurrentTime();
/**
+ * Get the current system time
+ *
+ * @return the current system time
+ *
+ * @since 7.1
+ */
+ public Instant now() {
+ // Subclasses overriding getCurrentTime should keep working
+ // TODO(ifrade): Once we remove getCurrentTime, use Instant.now()
+ return Instant.ofEpochMilli(getCurrentTime());
+ }
+
+ /**
+ * Get "now" as civil time, in the System timezone
+ *
+ * @return the current system time
+ *
+ * @since 7.1
+ */
+ public LocalDateTime civilNow() {
+ return LocalDateTime.ofInstant(now(), getTimeZoneId());
+ }
+
+ /**
* Get clock instance preferred by this system.
*
* @return clock instance preferred by this system.
@@ -522,20 +566,48 @@ public MonotonicClock getClock() {
* @param when
* a system timestamp
* @return the local time zone
+ *
+ * @deprecated Use {@link #getTimeZoneAt(Instant)} instead.
*/
+ @Deprecated
public abstract int getTimezone(long when);
/**
+ * Get the local time zone offset at "when" time
+ *
+ * @param when
+ * a system timestamp
+ * @return the local time zone
+ * @since 7.1
+ */
+ public ZoneOffset getTimeZoneAt(Instant when) {
+ return getTimeZoneId().getRules().getOffset(when);
+ }
+
+ /**
* Get system time zone, possibly mocked for testing
*
* @return system time zone, possibly mocked for testing
* @since 1.2
+ *
+ * @deprecated Use {@link #getTimeZoneId()}
*/
+ @Deprecated
public TimeZone getTimeZone() {
return TimeZone.getDefault();
}
/**
+ * Get system time zone, possibly mocked for testing
+ *
+ * @return system time zone, possibly mocked for testing
+ * @since 7.1
+ */
+ public ZoneId getTimeZoneId() {
+ return ZoneId.systemDefault();
+ }
+
+ /**
* Get the locale to use
*
* @return the locale to use
@@ -670,9 +742,7 @@ public boolean isPerformanceTraceEnabled() {
}
private String getOsName() {
- return AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$
- );
+ return getProperty("os.name"); //$NON-NLS-1$
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
index 4764676..13982b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
@@ -11,8 +11,6 @@
import java.io.IOException;
import java.io.Writer;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import org.eclipse.jgit.util.SystemReader;
@@ -35,10 +33,7 @@ public class ThrowingPrintWriter extends Writer {
*/
public ThrowingPrintWriter(Writer out) {
this.out = out;
- LF = AccessController
- .doPrivileged((PrivilegedAction<String>) () -> SystemReader
- .getInstance().getProperty("line.separator") //$NON-NLS-1$
- );
+ LF = SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$
}
@Override
diff --git a/pom.xml b/pom.xml
index ef6018b..9a0770c 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.0.2-SNAPSHOT</version>
+ <version>7.1.2-SNAPSHOT</version>
<name>JGit - Parent</name>
<url>${jgit-url}</url>
@@ -118,8 +118,8 @@
<project.build.outputTimestamp>${commit.time.iso}</project.build.outputTimestamp>
- <jgit-last-release-version>6.10.0.202406032230-r</jgit-last-release-version>
- <ant-version>1.10.14</ant-version>
+ <jgit-last-release-version>7.0.0.202409031743-r</jgit-last-release-version>
+ <ant-version>1.10.15</ant-version>
<apache-sshd-version>2.14.0</apache-sshd-version>
<jsch-version>0.1.55</jsch-version>
<jzlib-version>1.1.3</jzlib-version>
@@ -130,14 +130,14 @@
<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.12</jetty-version>
+ <jetty-version>12.0.15</jetty-version>
<japicmp-version>0.21.2</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.78.1</bouncycastle-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>
@@ -147,8 +147,8 @@
<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.14.0</jna-version>
- <byte-buddy-version>1.15.0</byte-buddy-version>
+ <jna-version>5.15.0</jna-version>
+ <byte-buddy-version>1.15.10</byte-buddy-version>
<!-- Properties to enable jacoco code coverage analysis -->
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
@@ -911,7 +911,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
- <version>2.16.1</version>
+ <version>2.17.0</version>
</dependency>
<dependency>
@@ -1020,7 +1020,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>5.12.0</version>
+ <version>5.14.2</version>
</dependency>
<dependency>
diff --git a/tools/BUILD b/tools/BUILD
index 8c424b3..844f004 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -10,6 +10,7 @@
java_runtime = "@rules_java//toolchains:remotejdk_17",
package_configuration = [
":error_prone",
+ ":error_prone_tests",
],
source_version = "17",
target_version = "17",
@@ -22,6 +23,7 @@
java_runtime = "@rules_java//toolchains:remotejdk_21",
package_configuration = [
":error_prone",
+ ":error_prone_tests",
],
source_version = "21",
target_version = "21",
@@ -32,9 +34,7 @@
# enabled. This warnings list is originally based on:
# https://github.com/bazelbuild/BUILD_file_generator/blob/master/tools/bazel_defs/java.bzl
# However, feel free to add any additional errors. Thus far they have all been pretty useful.
-java_package_configuration(
- name = "error_prone",
- javacopts = [
+errorprone_checks = [
"-XepDisableWarningsInGeneratedCode",
# The XepDisableWarningsInGeneratedCode disables only warnings, but
# not errors. We should manually exclude all files generated by
@@ -422,37 +422,57 @@
"-Xep:WrongOneof:ERROR",
"-Xep:XorPower:ERROR",
"-Xep:ZoneIdOfZ:ERROR",
- ],
+]
+
+
+exclude_in_tests = ["-Xep:EmptyBlockTag:WARN",
+ "-Xep:MissingSummary:WARN"]
+
+java_package_configuration(
+ name = "error_prone",
+ javacopts = errorprone_checks,
packages = ["error_prone_packages"],
)
+java_package_configuration(
+ name = "error_prone_tests",
+ javacopts = [ check for check in errorprone_checks if check not in exclude_in_tests],
+ packages = ["error_prone_packages_test"],
+)
+
package_group(
name = "error_prone_packages",
packages = [
- "//org.eclipse.jgit.ant.test/...",
"//org.eclipse.jgit.ant/...",
"//org.eclipse.jgit.archive/...",
- "//org.eclipse.jgit.gpg.bc.test/...",
"//org.eclipse.jgit.gpg.bc/...",
"//org.eclipse.jgit.http.apache/...",
"//org.eclipse.jgit.http.server/...",
- "//org.eclipse.jgit.http.test/...",
"//org.eclipse.jgit.junit.ssh/...",
"//org.eclipse.jgit.junit/...",
"//org.eclipse.jgit.junit/http/...",
- "//org.eclipse.jgit.lfs.server.test/...",
"//org.eclipse.jgit.lfs.server/...",
- "//org.eclipse.jgit.lfs.test/...",
"//org.eclipse.jgit.lfs/...",
- "//org.eclipse.jgit.pgm.test/...",
"//org.eclipse.jgit.pgm/...",
"//org.eclipse.jgit.ssh.apache.agent/...",
- "//org.eclipse.jgit.ssh.apache.test/...",
"//org.eclipse.jgit.ssh.apache/...",
- "//org.eclipse.jgit.ssh.jsch.test/...",
"//org.eclipse.jgit.ssh.jsch/...",
- "//org.eclipse.jgit.test/...",
"//org.eclipse.jgit.ui/...",
"//org.eclipse.jgit/...",
],
)
+
+package_group(
+ name = "error_prone_packages_test",
+ packages = [
+ "//org.eclipse.jgit.ant.test/...",
+ "//org.eclipse.jgit.gpg.bc.test/...",
+ "//org.eclipse.jgit.http.test/...",
+ "//org.eclipse.jgit.lfs.server.test/...",
+ "//org.eclipse.jgit.lfs.test/...",
+ "//org.eclipse.jgit.pgm.test/...",
+ "//org.eclipse.jgit.ssh.apache.test/...",
+ "//org.eclipse.jgit.ssh.jsch.test/...",
+ "//org.eclipse.jgit.test/...",
+ ],
+)