From b832b068d2efdf09edf29d015748039b5be27e76 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Mon, 25 Jan 2021 16:54:52 +0100 Subject: [PATCH 01/35] reftable: add random suffix to table names In some circumstances (eg. compacting a stack that has deletions), the result may have a {min, max} range that already exists. In these cases, we would rename onto an already existing file, which does not work on Windows. By adding a random suffix, we disambiguate the files, and avoid this failure scenario. Change-Id: I0273f99bb845cfbdbd8cdd582b55d3c310505d29 Signed-off-by: Han-Wen Nienhuys --- .../jgit/internal/storage/file/FileReftableStack.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java index db454b92b..e422767f0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java @@ -21,6 +21,7 @@ import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -65,6 +66,8 @@ private static class StackEntry { private final Runnable onChange; + private final SecureRandom random = new SecureRandom(); + private final Supplier configSupplier; // Used for stats & testing. @@ -365,8 +368,9 @@ private long nextUpdateIndex() throws IOException { } private String filename(long low, long high) { - return String.format("%012x-%012x", //$NON-NLS-1$ - Long.valueOf(low), Long.valueOf(high)); + return String.format("%012x-%012x-%08x", //$NON-NLS-1$ + Long.valueOf(low), Long.valueOf(high), + random.nextInt()); } /** From 8ad53baa1486b0ae1b8b52a99bfe56240074ca7e Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 4 Feb 2021 01:14:30 +0100 Subject: [PATCH 02/35] Fix bazel tests broken by classes moved in dbd05433 Change-Id: I88a3547c4b52bcf28c0f0f548ba1bb41a7787704 Signed-off-by: Matthias Sohn --- org.eclipse.jgit.test/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD index f12646e85..26a1fbdfc 100644 --- a/org.eclipse.jgit.test/BUILD +++ b/org.eclipse.jgit.test/BUILD @@ -13,6 +13,8 @@ HELPERS = glob( ) + [PKG + c for c in [ "api/AbstractRemoteCommandTest.java", "diff/AbstractDiffTestCase.java", + "internal/revwalk/ObjectReachabilityTestCase.java", + "internal/revwalk/ReachabilityCheckerTestCase.java", "internal/storage/file/GcTestCase.java", "internal/storage/file/PackIndexTestCase.java", "internal/storage/file/XInputStream.java", @@ -20,8 +22,6 @@ HELPERS = glob( "nls/MissingPropertyBundle.java", "nls/NoPropertiesBundle.java", "nls/NonTranslatedBundle.java", - "revwalk/ObjectReachabilityTestCase.java", - "revwalk/ReachabilityCheckerTestCase.java", "revwalk/RevQueueTestCase.java", "revwalk/RevWalkTestCase.java", "transport/ObjectIdMatcher.java", From 083e6fd70955ed4961c7b009d8d77ce891b8e42a Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 4 Feb 2021 01:17:00 +0100 Subject: [PATCH 03/35] LFSPointerTest: suppress errorprone error [SelfComparison] The test #testCompareToSame tests comparing against self intentionally. Suppress the error raised by errorprone. Change-Id: If8d70a51ab34ffb6d7f0c9d409746aee8b031408 Signed-off-by: Matthias Sohn --- .../tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java index fd83ff19e..da78b28d9 100644 --- a/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java +++ b/org.eclipse.jgit.lfs.test/tst/org/eclipse/jgit/lfs/lib/LFSPointerTest.java @@ -241,6 +241,7 @@ public void testCompareToEquals() throws Exception { } @Test + @SuppressWarnings("SelfComparison") public void testCompareToSame() throws Exception { AnyLongObjectId id = LongObjectId.fromString(TEST_SHA256); LfsPointer lfs = new LfsPointer(id, 4); From 4560bdf7e2e3c16a7c7bb3f2fcf067bb1eee26fb Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sat, 25 Jul 2020 10:00:11 +0200 Subject: [PATCH 04/35] Migrate to Apache MINA sshd 2.6.0 and Orbit I20210203173513 Re-enable DSA, DSA_CERT, and RSA_CERT public key authentication. DSA is discouraged for a long time already, but it might still be way too disruptive to completely drop it. RSA is discouraged for far less long, and dropping that would be really disruptive. Adapt to the changed property handling. Remove work-arounds for shortcomings of earlier sshd versions. Use Orbit I20210203173513, which includes sshd 2.6.0. This also bumps apache.httpclient to 4.5.13 and apache.httpcore to 4.4.14. Change-Id: I2d24a1ce4cc9f616a94bb5c4bdaedbf20dc6638e Signed-off-by: David Ostrovsky Signed-off-by: Thomas Wolf Signed-off-by: Matthias Sohn --- BUILD | 2 + WORKSPACE | 16 +- .../META-INF/MANIFEST.MF | 47 ++--- .../jgit/junit/ssh/SshTestGitServer.java | 63 +++++-- .../org.eclipse.jgit.target/jgit-4.10.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.10.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.11.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.11.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.12.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.12.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.13.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.13.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.14.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.14.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.15.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.15.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.16.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.16.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.17.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.17.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.18.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.18.tpd | 2 +- .../jgit-4.19-staging.target | 20 +-- .../jgit-4.19-staging.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.6.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.6.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.7.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.7.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.8.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.8.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.9.target | 20 +-- .../org.eclipse.jgit.target/jgit-4.9.tpd | 2 +- .../orbit/I20210203173513.tpd | 66 +++++++ .../META-INF/MANIFEST.MF | 23 +-- .../jgit/transport/sshd/ApacheSshTest.java | 7 +- .../META-INF/MANIFEST.MF | 88 ++++----- .../transport/sshd/JGitClientSession.java | 167 ++---------------- .../sshd/JGitPasswordAuthentication.java | 7 +- .../transport/sshd/JGitSshClient.java | 13 +- .../transport/sshd/JGitSshConfig.java | 2 +- .../sshd/PasswordProviderWrapper.java | 14 +- .../sshd/proxy/HttpClientConnector.java | 2 +- .../sshd/proxy/Socks5ClientConnector.java | 8 +- .../jgit/transport/sshd/SshdSession.java | 24 +-- .../transport/sshd/SshdSessionFactory.java | 35 ++++ pom.xml | 6 +- 46 files changed, 445 insertions(+), 453 deletions(-) create mode 100644 org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd diff --git a/BUILD b/BUILD index be6dd767d..184ab272d 100644 --- a/BUILD +++ b/BUILD @@ -13,6 +13,8 @@ genrule( "//org.eclipse.jgit.lfs:jgit-lfs", "//org.eclipse.jgit.lfs.server:jgit-lfs-server", "//org.eclipse.jgit.junit:junit", + "//org.eclipse.jgit.ssh.apache:ssh-apache", + "//org.eclipse.jgit.ssh.jsch:ssh-jsch", ], outs = ["all.zip"], cmd = " && ".join([ diff --git a/WORKSPACE b/WORKSPACE index 768682d1c..5ce9dd7d0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -111,26 +111,26 @@ maven_jar( maven_jar( name = "httpclient", - artifact = "org.apache.httpcomponents:httpclient:4.5.10", - sha1 = "7ca2e4276f4ef95e4db725a8cd4a1d1e7585b9e5", + artifact = "org.apache.httpcomponents:httpclient:4.5.13", + sha1 = "e5f6cae5ca7ecaac1ec2827a9e2d65ae2869cada", ) maven_jar( name = "httpcore", - artifact = "org.apache.httpcomponents:httpcore:4.4.12", - sha1 = "21ebaf6d532bc350ba95bd81938fa5f0e511c132", + artifact = "org.apache.httpcomponents:httpcore:4.4.14", + sha1 = "9dd1a631c082d92ecd4bd8fd4cf55026c720a8c1", ) maven_jar( name = "sshd-osgi", - artifact = "org.apache.sshd:sshd-osgi:2.4.0", - sha1 = "fc4551c1eeda35e4671b263297d37d2bca81c4d4", + artifact = "org.apache.sshd:sshd-osgi:2.6.0", + sha1 = "40e365bb799e1bff3d31dc858b1e59a93c123f29", ) maven_jar( name = "sshd-sftp", - artifact = "org.apache.sshd:sshd-sftp:2.4.0", - sha1 = "92e1b7d1e19c715efb4a8871d34145da8f87cdb2", + artifact = "org.apache.sshd:sshd-sftp:2.6.0", + sha1 = "6eddfe8fdf59a3d9a49151e4177f8c1bebeb30c9", ) maven_jar( diff --git a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF index 46b59fe78..f7b9a2894 100644 --- a/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF @@ -8,28 +8,31 @@ Bundle-Localization: plugin Bundle-Vendor: %Bundle-Vendor Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.apache.sshd.common;version="[2.4.0,2.5.0)", - org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)", - org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)", - org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)", - org.apache.sshd.common.io;version="[2.4.0,2.5.0)", - org.apache.sshd.common.kex;version="[2.4.0,2.5.0)", - org.apache.sshd.common.keyprovider;version="[2.4.0,2.5.0)", - org.apache.sshd.common.session;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.buffer;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.logging;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.threads;version="[2.4.0,2.5.0)", - org.apache.sshd.server;version="[2.4.0,2.5.0)", - org.apache.sshd.server.auth;version="[2.4.0,2.5.0)", - org.apache.sshd.server.auth.gss;version="[2.4.0,2.5.0)", - org.apache.sshd.server.auth.keyboard;version="[2.4.0,2.5.0)", - org.apache.sshd.server.auth.password;version="[2.4.0,2.5.0)", - org.apache.sshd.server.command;version="[2.4.0,2.5.0)", - org.apache.sshd.server.session;version="[2.4.0,2.5.0)", - org.apache.sshd.server.shell;version="[2.4.0,2.5.0)", - org.apache.sshd.server.subsystem;version="[2.4.0,2.5.0)", - org.apache.sshd.server.subsystem.sftp;version="[2.4.0,2.5.0)", +Import-Package: org.apache.sshd.common;version="[2.6.0,2.7.0)", + org.apache.sshd.common.config.keys;version="[2.6.0,2.7.0)", + org.apache.sshd.common.file.virtualfs;version="[2.6.0,2.7.0)", + org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)", + org.apache.sshd.common.io;version="[2.6.0,2.7.0)", + org.apache.sshd.common.kex;version="[2.6.0,2.7.0)", + org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)", + org.apache.sshd.common.session;version="[2.6.0,2.7.0)", + org.apache.sshd.common.signature;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.buffer;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.logging;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.threads;version="[2.6.0,2.7.0)", + org.apache.sshd.core;version="[2.6.0,2.7.0)", + org.apache.sshd.server;version="[2.6.0,2.7.0)", + org.apache.sshd.server.auth;version="[2.6.0,2.7.0)", + org.apache.sshd.server.auth.gss;version="[2.6.0,2.7.0)", + org.apache.sshd.server.auth.keyboard;version="[2.6.0,2.7.0)", + org.apache.sshd.server.auth.password;version="[2.6.0,2.7.0)", + org.apache.sshd.server.command;version="[2.6.0,2.7.0)", + org.apache.sshd.server.session;version="[2.6.0,2.7.0)", + org.apache.sshd.server.shell;version="[2.6.0,2.7.0)", + org.apache.sshd.server.subsystem;version="[2.6.0,2.7.0)", + org.apache.sshd.sftp;version="[2.6.0,2.7.0)", + org.apache.sshd.sftp.server;version="[2.6.0,2.7.0)", org.eclipse.jgit.annotations;version="[5.11.0,5.12.0)", org.eclipse.jgit.api;version="[5.11.0,5.12.0)", org.eclipse.jgit.api.errors;version="[5.11.0,5.12.0)", diff --git a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java index 8494a2fb1..4fe98f868 100644 --- a/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java +++ b/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java @@ -9,6 +9,9 @@ */ package org.eclipse.jgit.junit.ssh; +import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES; +import static org.apache.sshd.core.CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -21,26 +24,28 @@ import java.security.PublicKey; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; +import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.PropertyResolver; -import org.apache.sshd.common.PropertyResolverUtils; import org.apache.sshd.common.SshConstants; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PublicKeyEntryResolver; import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory; -import org.apache.sshd.common.session.Session; +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.security.SecurityUtils; import org.apache.sshd.common.util.threads.CloseableExecutorService; import org.apache.sshd.common.util.threads.ThreadUtils; import org.apache.sshd.server.ServerAuthenticationManager; -import org.apache.sshd.server.ServerFactoryManager; +import org.apache.sshd.server.ServerBuilder; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.auth.UserAuth; import org.apache.sshd.server.auth.UserAuthFactory; @@ -52,7 +57,7 @@ import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.shell.UnknownCommand; import org.apache.sshd.server.subsystem.SubsystemFactory; -import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory; +import org.apache.sshd.sftp.server.SftpSubsystemFactory; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Repository; @@ -162,7 +167,9 @@ public SshTestGitServer(@NonNull String testUser, this.testUser = testUser; setTestUserPublicKey(testKey); this.repository = repository; - server = SshServer.setUpDefaultServer(); + ServerBuilder builder = ServerBuilder.builder() + .signatureFactories(getSignatureFactories()); + server = builder.build(); hostKeys.add(hostKey); server.setKeyPairProvider((session) -> hostKeys); @@ -187,6 +194,37 @@ public SshTestGitServer(@NonNull String testUser, }); } + /** + * Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to + * set it up explicitly to still allow users to connect with DSA keys. + * + * @return a list of supported signature factories + */ + @SuppressWarnings("deprecation") + private static List> getSignatureFactories() { + // @formatter:off + return Arrays.asList( + BuiltinSignatures.nistp256_cert, + BuiltinSignatures.nistp384_cert, + BuiltinSignatures.nistp521_cert, + BuiltinSignatures.ed25519_cert, + BuiltinSignatures.rsaSHA512_cert, + BuiltinSignatures.rsaSHA256_cert, + BuiltinSignatures.rsa_cert, + BuiltinSignatures.nistp256, + BuiltinSignatures.nistp384, + BuiltinSignatures.nistp521, + BuiltinSignatures.ed25519, + BuiltinSignatures.sk_ecdsa_sha2_nistp256, + BuiltinSignatures.sk_ssh_ed25519, + BuiltinSignatures.rsaSHA512, + BuiltinSignatures.rsaSHA256, + BuiltinSignatures.rsa, + BuiltinSignatures.dsa_cert, + BuiltinSignatures.dsa); + // @formatter:on + } + private static PublicKey readPublicKey(Path key) throws IOException, GeneralSecurityException { return AuthorizedKeyEntry.readAuthorizedKeys(key).get(0) @@ -278,14 +316,8 @@ public boolean validateInitialUser(ServerSession session, @NonNull protected List configureSubsystems() { // SFTP. - server.setFileSystemFactory(new VirtualFileSystemFactory() { - - @Override - protected Path computeRootDir(Session session) throws IOException { - return SshTestGitServer.this.repository.getDirectory() - .getParentFile().getAbsoluteFile().toPath(); - } - }); + server.setFileSystemFactory(new VirtualFileSystemFactory(repository + .getDirectory().getParentFile().getAbsoluteFile().toPath())); return Collections .singletonList((new SftpSubsystemFactory.Builder()).build()); } @@ -434,9 +466,8 @@ public void setTestUserPublicKey(@NonNull PublicKey key) { */ public void setPreamble(String... lines) { if (lines != null && lines.length > 0) { - PropertyResolverUtils.updateProperty(this.server, - ServerFactoryManager.SERVER_EXTRA_IDENTIFICATION_LINES, - String.join("|", lines)); + SERVER_EXTRA_IDENTIFICATION_LINES.set(server, String.join( + String.valueOf(SERVER_EXTRA_IDENT_LINES_SEPARATOR), lines)); } } diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target index 1629266d1..73c1b34df 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd index ac4da5c9b..206600afa 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd @@ -1,7 +1,7 @@ target "jgit-4.10" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2018-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target index afe7936c2..6b7896283 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd index c3ac5900c..937ccf57a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd @@ -1,7 +1,7 @@ target "jgit-4.11" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2019-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target index b7de53dfe..aa8c724e5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd index 7e6ad8ea0..a70f829a5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd @@ -1,7 +1,7 @@ target "jgit-4.12" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2019-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target index 3f9d17daf..fa8953781 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd index af0f84599..16c32b8e4 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd @@ -1,7 +1,7 @@ target "jgit-4.13" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2019-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target index 9b8490888..166b8d834 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd index 0c89f0ef4..afa10ef18 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd @@ -1,7 +1,7 @@ target "jgit-4.14" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2019-12/201912181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target index b533aa178..008001571 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd index c176a4b0d..700d38b33 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd @@ -1,7 +1,7 @@ target "jgit-4.15" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2020-03/202003181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target index 45c665a15..476a08f15 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd index 63520997c..24093d8e1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd @@ -1,7 +1,7 @@ target "jgit-4.16" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2020-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target index a3027bc17..3e619ed69 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd index 4c759348b..f26614afe 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd @@ -1,7 +1,7 @@ target "jgit-4.17" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2020-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target index 578fe234b..bf73bc995 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd index 77384a34b..716bd93f4 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd @@ -1,7 +1,7 @@ target "jgit-4.18" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2020-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target index 567767cee..58192257a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd index d01602850..a707e5b38 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd @@ -1,7 +1,7 @@ target "jgit-4.19-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/staging/2021-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index 9cc4290f6..b384956ce 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd index cbc1f30f0..42d02c30b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd @@ -1,7 +1,7 @@ target "jgit-4.6" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/neon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index b7a8c403e..d606a5ec5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd index 403ec0211..6bd9be5d5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd @@ -1,7 +1,7 @@ target "jgit-4.7" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/oxygen/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index 8084e343b..6c65c24a1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd index c120bc9b4..af5f4fb4a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd @@ -1,7 +1,7 @@ target "jgit-4.8" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/photon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index d51e0235a..9662b0577 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,7 +1,7 @@ - + @@ -49,16 +49,16 @@ - - - - + + + + - - - - + + + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd index 0c896b4c3..d9ff5b610 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd @@ -1,7 +1,7 @@ target "jgit-4.9" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210105214148.tpd" +include "orbit/I20210203173513.tpd" location "https://download.eclipse.org/releases/2018-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd new file mode 100644 index 000000000..a77e21d61 --- /dev/null +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd @@ -0,0 +1,66 @@ +target "I20210203173513" with source configurePhase +// see https://download.eclipse.org/tools/orbit/downloads/ + +location "https://download.eclipse.org/tools/orbit/downloads/drops/I20210203173513/repository" { + com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626] + com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626] + com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902] + com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902] + com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] + com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] + javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831] + javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831] + javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] + javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] + net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410] + net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534] + net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534] + net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410] + net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323] + net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323] + org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946] + org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946] + org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422] + org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422] + org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343] + org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343] + org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502] + org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502] + org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225] + org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225] + org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225] + org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225] + org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.sshd.osgi [2.6.0.v20210201-2003,2.6.0.v20210201-2003] + org.apache.sshd.osgi.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003] + org.apache.sshd.sftp [2.6.0.v20210201-2003,2.6.0.v20210201-2003] + org.apache.sshd.sftp.source [2.6.0.v20210201-2003,2.6.0.v20210201-2003] + org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926] + org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926] + org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955] + org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955] + org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955] + org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955] + org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514] + org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514] + org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000] + org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500] + org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500] + org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642] + org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642] + org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519] + org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519] + org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150] + org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150] + org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042] + org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042] + org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613] + org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613] +} diff --git a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF index e9592426a..de173f8fb 100644 --- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF @@ -7,17 +7,18 @@ Bundle-Version: 5.11.0.qualifier Bundle-Vendor: %Bundle-Vendor Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.apache.sshd.client.config.hosts;version="[2.4.0,2.5.0)", - org.apache.sshd.common;version="[2.4.0,2.5.0)", - org.apache.sshd.common.auth;version="[2.4.0,2.5.0)", - org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)", - org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)", - org.apache.sshd.common.keyprovider;version="[2.4.0,2.5.0)", - org.apache.sshd.common.session;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.net;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)", - org.apache.sshd.server;version="[2.4.0,2.5.0)", - org.apache.sshd.server.forward;version="[2.4.0,2.5.0)", +Import-Package: org.apache.sshd.client.config.hosts;version="[2.6.0,2.7.0)", + org.apache.sshd.common;version="[2.6.0,2.7.0)", + org.apache.sshd.common.auth;version="[2.6.0,2.7.0)", + org.apache.sshd.common.config.keys;version="[2.6.0,2.7.0)", + org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)", + org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)", + org.apache.sshd.common.session;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.net;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)", + org.apache.sshd.core;version="[2.6.0,2.7.0)", + org.apache.sshd.server;version="[2.6.0,2.7.0)", + org.apache.sshd.server.forward;version="[2.6.0,2.7.0)", org.eclipse.jgit.api;version="[5.11.0,5.12.0)", org.eclipse.jgit.api.errors;version="[5.11.0,5.12.0)", org.eclipse.jgit.internal.transport.sshd.proxy;version="[5.11.0,5.12.0)", diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java index 3427da667..97f97f902 100644 --- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java +++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshTest.java @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.transport.sshd; +import static org.apache.sshd.core.CoreModuleProperties.MAX_CONCURRENT_SESSIONS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -33,7 +34,6 @@ import org.apache.sshd.client.config.hosts.KnownHostEntry; import org.apache.sshd.client.config.hosts.KnownHostHashValue; -import org.apache.sshd.common.PropertyResolverUtils; import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PublicKeyEntry; @@ -41,7 +41,6 @@ import org.apache.sshd.common.session.Session; import org.apache.sshd.common.util.net.SshdSocketAddress; import org.apache.sshd.server.ServerAuthenticationManager; -import org.apache.sshd.server.ServerFactoryManager; import org.apache.sshd.server.SshServer; import org.apache.sshd.server.forward.StaticDecisionForwardingFilter; import org.eclipse.jgit.api.Git; @@ -216,8 +215,8 @@ public void testHugePreamble() throws Exception { */ @Test public void testCloneAndFetchWithSessionLimit() throws Exception { - PropertyResolverUtils.updateProperty(server.getPropertyResolver(), - ServerFactoryManager.MAX_CONCURRENT_SESSIONS, 2); + MAX_CONCURRENT_SESSIONS + .set(server.getPropertyResolver(), Integer.valueOf(2)); File localClone = cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // "Host localhost", // diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF index 59eafa443..6ff43ff27 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -33,49 +33,51 @@ Export-Package: org.eclipse.jgit.internal.transport.sshd;version="5.11.0";x-inte org.apache.sshd.client.session, org.apache.sshd.client.keyverifier" Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", - org.apache.sshd.agent;version="[2.4.0,2.5.0)", - org.apache.sshd.client;version="[2.4.0,2.5.0)", - org.apache.sshd.client.auth;version="[2.4.0,2.5.0)", - org.apache.sshd.client.auth.keyboard;version="[2.4.0,2.5.0)", - org.apache.sshd.client.auth.password;version="[2.4.0,2.5.0)", - org.apache.sshd.client.auth.pubkey;version="[2.4.0,2.5.0)", - org.apache.sshd.client.channel;version="[2.4.0,2.5.0)", - org.apache.sshd.client.config.hosts;version="[2.4.0,2.5.0)", - org.apache.sshd.client.config.keys;version="[2.4.0,2.5.0)", - org.apache.sshd.client.future;version="[2.4.0,2.5.0)", - org.apache.sshd.client.keyverifier;version="[2.4.0,2.5.0)", - org.apache.sshd.client.session;version="[2.4.0,2.5.0)", - org.apache.sshd.client.session.forward;version="[2.4.0,2.5.0)", - org.apache.sshd.client.subsystem.sftp;version="[2.4.0,2.5.0)", - org.apache.sshd.common;version="[2.4.0,2.5.0)", - org.apache.sshd.common.auth;version="[2.4.0,2.5.0)", - org.apache.sshd.common.channel;version="[2.4.0,2.5.0)", - org.apache.sshd.common.compression;version="[2.4.0,2.5.0)", - org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)", - org.apache.sshd.common.config.keys.loader;version="[2.4.0,2.5.0)", - org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.4.0,2.5.0)", - org.apache.sshd.common.digest;version="[2.4.0,2.5.0)", - org.apache.sshd.common.forward;version="[2.4.0,2.5.0)", - org.apache.sshd.common.future;version="[2.4.0,2.5.0)", - org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)", - org.apache.sshd.common.io;version="[2.4.0,2.5.0)", - org.apache.sshd.common.kex;version="[2.4.0,2.5.0)", - org.apache.sshd.common.keyprovider;version="[2.4.0,2.5.0)", - org.apache.sshd.common.mac;version="[2.4.0,2.5.0)", - org.apache.sshd.common.random;version="[2.4.0,2.5.0)", - org.apache.sshd.common.session;version="[2.4.0,2.5.0)", - org.apache.sshd.common.session.helpers;version="[2.4.0,2.5.0)", - org.apache.sshd.common.signature;version="[2.4.0,2.5.0)", - org.apache.sshd.common.subsystem.sftp;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.buffer;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.closeable;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.io;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.io.resource;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.logging;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.net;version="[2.4.0,2.5.0)", - org.apache.sshd.common.util.security;version="[2.4.0,2.5.0)", - org.apache.sshd.server.auth;version="[2.4.0,2.5.0)", + org.apache.sshd.agent;version="[2.6.0,2.7.0)", + org.apache.sshd.client;version="[2.6.0,2.7.0)", + org.apache.sshd.client.auth;version="[2.6.0,2.7.0)", + org.apache.sshd.client.auth.keyboard;version="[2.6.0,2.7.0)", + org.apache.sshd.client.auth.password;version="[2.6.0,2.7.0)", + org.apache.sshd.client.auth.pubkey;version="[2.6.0,2.7.0)", + org.apache.sshd.client.channel;version="[2.6.0,2.7.0)", + org.apache.sshd.client.config.hosts;version="[2.6.0,2.7.0)", + org.apache.sshd.client.config.keys;version="[2.6.0,2.7.0)", + org.apache.sshd.client.future;version="[2.6.0,2.7.0)", + org.apache.sshd.client.keyverifier;version="[2.6.0,2.7.0)", + org.apache.sshd.client.session;version="[2.6.0,2.7.0)", + org.apache.sshd.client.session.forward;version="[2.6.0,2.7.0)", + org.apache.sshd.common;version="[2.6.0,2.7.0)", + org.apache.sshd.common.auth;version="[2.6.0,2.7.0)", + org.apache.sshd.common.channel;version="[2.6.0,2.7.0)", + org.apache.sshd.common.compression;version="[2.6.0,2.7.0)", + org.apache.sshd.common.config.keys;version="[2.6.0,2.7.0)", + org.apache.sshd.common.config.keys.loader;version="[2.6.0,2.7.0)", + org.apache.sshd.common.config.keys.loader.openssh.kdf;version="[2.6.0,2.7.0)", + org.apache.sshd.common.digest;version="[2.6.0,2.7.0)", + org.apache.sshd.common.forward;version="[2.6.0,2.7.0)", + org.apache.sshd.common.future;version="[2.6.0,2.7.0)", + org.apache.sshd.common.helpers;version="[2.6.0,2.7.0)", + org.apache.sshd.common.io;version="[2.6.0,2.7.0)", + org.apache.sshd.common.kex;version="[2.6.0,2.7.0)", + org.apache.sshd.common.keyprovider;version="[2.6.0,2.7.0)", + org.apache.sshd.common.mac;version="[2.6.0,2.7.0)", + org.apache.sshd.common.random;version="[2.6.0,2.7.0)", + org.apache.sshd.common.session;version="[2.6.0,2.7.0)", + org.apache.sshd.common.session.helpers;version="[2.6.0,2.7.0)", + org.apache.sshd.common.signature;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.buffer;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.closeable;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.io;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.io.resource;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.logging;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.net;version="[2.6.0,2.7.0)", + org.apache.sshd.common.util.security;version="[2.6.0,2.7.0)", + org.apache.sshd.core;version="[2.6.0,2.7.0)", + org.apache.sshd.server.auth;version="[2.6.0,2.7.0)", + org.apache.sshd.sftp;version="[2.6.0,2.7.0)", + org.apache.sshd.sftp.client;version="[2.6.0,2.7.0)", + org.apache.sshd.sftp.common;version="[2.6.0,2.7.0)", org.eclipse.jgit.annotations;version="[5.11.0,5.12.0)", org.eclipse.jgit.errors;version="[5.11.0,5.12.0)", org.eclipse.jgit.fnmatch;version="[5.11.0,5.12.0)", diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java index 0d6f3027f..66713ba63 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitClientSession.java @@ -10,6 +10,7 @@ package org.eclipse.jgit.internal.transport.sshd; import static java.text.MessageFormat.format; +import static org.apache.sshd.core.CoreModuleProperties.MAX_IDENTIFICATION_SIZE; import java.io.IOException; import java.io.StreamCorruptedException; @@ -29,19 +30,14 @@ import org.apache.sshd.client.ClientFactoryManager; import org.apache.sshd.client.config.hosts.HostConfigEntry; -import org.apache.sshd.client.future.AuthFuture; import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.session.ClientSessionImpl; -import org.apache.sshd.client.session.ClientUserAuthService; import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.FactoryManager; import org.apache.sshd.common.PropertyResolver; -import org.apache.sshd.common.PropertyResolverUtils; -import org.apache.sshd.common.SshException; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.common.io.IoWriteFuture; -import org.apache.sshd.common.kex.KexState; import org.apache.sshd.common.util.Readable; import org.apache.sshd.common.util.buffer.Buffer; import org.eclipse.jgit.errors.InvalidPatternException; @@ -66,7 +62,8 @@ public class JGitClientSession extends ClientSessionImpl { * protocol version exchange. 64kb is what OpenSSH < 8.0 read; OpenSSH 8.0 * changed it to 8Mb, but that seems excessive for the purpose stated in RFC * 4253. The Apache MINA sshd default in - * {@link FactoryManager#DEFAULT_MAX_IDENTIFICATION_SIZE} is 16kb. + * {@link org.apache.sshd.core.CoreModuleProperties#MAX_IDENTIFICATION_SIZE} + * is 16kb. */ private static final int DEFAULT_MAX_IDENTIFICATION_SIZE = 64 * 1024; @@ -76,17 +73,6 @@ public class JGitClientSession extends ClientSessionImpl { private volatile StatefulProxyConnector proxyHandler; - /** - * Work-around for bug 565394 / SSHD-1050; remove when using sshd 2.6.0. - */ - private volatile AuthFuture authFuture; - - /** Records exceptions before there is an authFuture. */ - private List earlyErrors = new ArrayList<>(); - - /** Guards setting an earlyError and the authFuture together. */ - private final Object errorLock = new Object(); - /** * @param manager * @param session @@ -97,125 +83,6 @@ public JGitClientSession(ClientFactoryManager manager, IoSession session) super(manager, session); } - // BEGIN Work-around for bug 565394 / SSHD-1050 - // Remove when using sshd 2.6.0. - - @Override - public AuthFuture auth() throws IOException { - if (getUsername() == null) { - throw new IllegalStateException( - SshdText.get().sessionWithoutUsername); - } - ClientUserAuthService authService = getUserAuthService(); - String serviceName = nextServiceName(); - List errors = null; - AuthFuture future; - // Guard both getting early errors and setting authFuture - synchronized (errorLock) { - future = authService.auth(serviceName); - if (future == null) { - // Internal error; no translation. - throw new IllegalStateException( - "No auth future generated by service '" //$NON-NLS-1$ - + serviceName + '\''); - } - errors = earlyErrors; - earlyErrors = null; - authFuture = future; - } - if (errors != null && !errors.isEmpty()) { - Iterator iter = errors.iterator(); - Throwable first = iter.next(); - iter.forEachRemaining(t -> { - if (t != first && t != null) { - first.addSuppressed(t); - } - }); - // Mark the future as having had an exception; just to be on the - // safe side. Actually, there shouldn't be anyone waiting on this - // future yet. - future.setException(first); - if (log.isDebugEnabled()) { - log.debug("auth({}) early exception type={}: {}", //$NON-NLS-1$ - this, first.getClass().getSimpleName(), - first.getMessage()); - } - if (first instanceof SshException) { - throw new SshException( - ((SshException) first).getDisconnectCode(), - first.getMessage(), first); - } - throw new IOException(first.getMessage(), first); - } - return future; - } - - @Override - protected void signalAuthFailure(AuthFuture future, Throwable t) { - signalAuthFailure(t); - } - - private void signalAuthFailure(Throwable t) { - AuthFuture future = authFuture; - if (future == null) { - synchronized (errorLock) { - if (earlyErrors != null) { - earlyErrors.add(t); - } - future = authFuture; - } - } - if (future != null) { - future.setException(t); - } - if (log.isDebugEnabled()) { - boolean signalled = future != null && t == future.getException(); - log.debug("signalAuthFailure({}) type={}, signalled={}: {}", this, //$NON-NLS-1$ - t.getClass().getSimpleName(), Boolean.valueOf(signalled), - t.getMessage()); - } - } - - @Override - public void exceptionCaught(Throwable t) { - signalAuthFailure(t); - super.exceptionCaught(t); - } - - @Override - protected void preClose() { - signalAuthFailure( - new SshException(SshdText.get().authenticationOnClosedSession)); - super.preClose(); - } - - @Override - protected void handleDisconnect(int code, String msg, String lang, - Buffer buffer) throws Exception { - signalAuthFailure(new SshException(code, msg)); - super.handleDisconnect(code, msg, lang, buffer); - } - - @Override - protected > C updateCurrentSessionState( - C newState) { - if (closeFuture.isClosed()) { - newState.add(ClientSessionEvent.CLOSED); - } - if (isAuthenticated()) { // authFuture.isSuccess() - newState.add(ClientSessionEvent.AUTHED); - } - if (KexState.DONE.equals(getKexState())) { - AuthFuture future = authFuture; - if (future == null || future.isFailure()) { - newState.add(ClientSessionEvent.WAIT_AUTH); - } - } - return newState; - } - - // END Work-around for bug 565394 / SSHD-1050 - /** * Retrieves the {@link HostConfigEntry} this session was created for. * @@ -331,22 +198,6 @@ public void messageReceived(Readable buffer) throws Exception { } } - @Override - protected void checkKeys() throws SshException { - ServerKeyVerifier serverKeyVerifier = getServerKeyVerifier(); - // The super implementation always uses - // getIoSession().getRemoteAddress(). In case of a proxy connection, - // that would be the address of the proxy! - SocketAddress remoteAddress = getConnectAddress(); - PublicKey serverKey = getKex().getServerKey(); - if (!serverKeyVerifier.verifyServerKey(this, remoteAddress, - serverKey)) { - throw new SshException( - org.apache.sshd.common.SshConstants.SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, - SshdText.get().kexServerKeyInvalid); - } - } - @Override protected String resolveAvailableSignaturesProposal( FactoryManager manager) { @@ -477,9 +328,15 @@ protected List doReadIdentification(Buffer buffer, boolean server) throw new IllegalStateException( "doReadIdentification of client called with server=true"); //$NON-NLS-1$ } - int maxIdentSize = PropertyResolverUtils.getIntProperty(this, - FactoryManager.MAX_IDENTIFICATION_SIZE, - DEFAULT_MAX_IDENTIFICATION_SIZE); + Integer maxIdentLength = MAX_IDENTIFICATION_SIZE.get(this).orElse(null); + int maxIdentSize; + if (maxIdentLength == null || maxIdentLength + .intValue() < DEFAULT_MAX_IDENTIFICATION_SIZE) { + maxIdentSize = DEFAULT_MAX_IDENTIFICATION_SIZE; + MAX_IDENTIFICATION_SIZE.set(this, Integer.valueOf(maxIdentSize)); + } else { + maxIdentSize = maxIdentLength.intValue(); + } int current = buffer.rpos(); int end = current + buffer.available(); if (current >= end) { diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java index 4abd6e901..ff8caaacc 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPasswordAuthentication.java @@ -9,7 +9,8 @@ */ package org.eclipse.jgit.internal.transport.sshd; -import org.apache.sshd.client.ClientAuthenticationManager; +import static org.apache.sshd.core.CoreModuleProperties.PASSWORD_PROMPTS; + import org.apache.sshd.client.auth.keyboard.UserInteraction; import org.apache.sshd.client.auth.password.UserAuthPassword; import org.apache.sshd.client.session.ClientSession; @@ -29,9 +30,7 @@ public class JGitPasswordAuthentication extends UserAuthPassword { public void init(ClientSession session, String service) throws Exception { super.init(session, service); maxAttempts = Math.max(1, - session.getIntProperty( - ClientAuthenticationManager.PASSWORD_PROMPTS, - ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS)); + PASSWORD_PROMPTS.getRequired(session).intValue()); attempts = 0; } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java index beaaecaac..74455dc80 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshClient.java @@ -10,6 +10,8 @@ package org.eclipse.jgit.internal.transport.sshd; import static java.text.MessageFormat.format; +import static org.apache.sshd.core.CoreModuleProperties.PASSWORD_PROMPTS; +import static org.apache.sshd.core.CoreModuleProperties.PREFERRED_AUTHS; import static org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile.positive; import java.io.IOException; @@ -32,7 +34,6 @@ import java.util.Objects; import java.util.stream.Collectors; -import org.apache.sshd.client.ClientAuthenticationManager; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.future.ConnectFuture; @@ -169,12 +170,15 @@ private AttributeRepository sessionAttributes(AttributeRepository parent, Map, Object> data = new HashMap<>(); data.put(HOST_CONFIG_ENTRY, hostConfig); data.put(ORIGINAL_REMOTE_ADDRESS, originalAddress); + data.put(TARGET_SERVER, new SshdSocketAddress(originalAddress)); String preferredAuths = hostConfig.getProperty( SshConstants.PREFERRED_AUTHENTICATIONS, resolveAttribute(PREFERRED_AUTHENTICATIONS)); if (!StringUtils.isEmptyOrNull(preferredAuths)) { data.put(SessionAttributes.PROPERTIES, - Collections.singletonMap(PREFERRED_AUTHS, preferredAuths)); + Collections.singletonMap( + PREFERRED_AUTHS.getName(), + preferredAuths)); } return new SessionAttributes( AttributeRepository.ofAttributesMap(data), @@ -267,8 +271,7 @@ private JGitClientSession createSession(IoSession ioSession, session.setCredentialsProvider(getCredentialsProvider()); } int numberOfPasswordPrompts = getNumberOfPasswordPrompts(hostConfig); - session.getProperties().put(PASSWORD_PROMPTS, - Integer.valueOf(numberOfPasswordPrompts)); + PASSWORD_PROMPTS.set(session, Integer.valueOf(numberOfPasswordPrompts)); List identities = hostConfig.getIdentities().stream() .map(s -> { try { @@ -311,7 +314,7 @@ private int getNumberOfPasswordPrompts(HostConfigEntry hostConfig) { log.warn(format(SshdText.get().configInvalidPositive, SshConstants.NUMBER_OF_PASSWORD_PROMPTS, prompts)); } - return ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS; + return PASSWORD_PROMPTS.getRequiredDefault().intValue(); } /** diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java index 97e0fcc7d..6b0d9fb70 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitSshConfig.java @@ -46,7 +46,7 @@ public JGitSshConfig(SshConfigStore store) { @Override public HostConfigEntry resolveEffectiveHost(String host, int port, - SocketAddress localAddress, String username, + SocketAddress localAddress, String username, String proxyJump, AttributeRepository attributes) throws IOException { SshConfigStore.HostConfig entry = configFile == null ? SshConfigStore.EMPTY_CONFIG 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 078e411f2..2cd066984 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 @@ -9,6 +9,8 @@ */ package org.eclipse.jgit.internal.transport.sshd; +import static org.apache.sshd.core.CoreModuleProperties.PASSWORD_PROMPTS; + import java.io.IOException; import java.net.URISyntaxException; import java.security.GeneralSecurityException; @@ -18,7 +20,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; -import org.apache.sshd.client.ClientAuthenticationManager; import org.apache.sshd.common.AttributeRepository.AttributeKey; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; @@ -62,15 +63,8 @@ private PerSessionState getState(SessionContext context) { if (state == null) { state = new PerSessionState(); state.delegate = factory.get(); - Integer maxNumberOfAttempts = context - .getInteger(ClientAuthenticationManager.PASSWORD_PROMPTS); - if (maxNumberOfAttempts != null - && maxNumberOfAttempts.intValue() > 0) { - state.delegate.setAttempts(maxNumberOfAttempts.intValue()); - } else { - state.delegate.setAttempts( - ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS); - } + state.delegate.setAttempts( + PASSWORD_PROMPTS.getRequiredDefault().intValue()); context.setAttribute(STATE, state); } return state; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java index 8ac752bcc..e5d1e80f7 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpClientConnector.java @@ -135,7 +135,7 @@ private void send(StringBuilder msg, IoSession session) throws Exception { byte[] data = eol(msg).toString().getBytes(US_ASCII); Buffer buffer = new ByteArrayBuffer(data.length, false); buffer.putRawBytes(data); - session.writePacket(buffer).verify(getTimeout()); + session.writeBuffer(buffer).verify(getTimeout()); } private StringBuilder connect() { diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java index 78b8d456b..8844efa6b 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/Socks5ClientConnector.java @@ -235,7 +235,7 @@ public void sendClientProxyMetadata(ClientSession sshSession) buffer.putByte((byte) authenticationProposals.length); buffer.putRawBytes(authenticationProposals); state = ProtocolState.INIT; - session.writePacket(buffer).verify(getTimeout()); + session.writeBuffer(buffer).verify(getTimeout()); } private byte[] getAuthenticationProposals() { @@ -298,7 +298,7 @@ private void sendConnectInfo(IoSession session) throws Exception { buffer.putByte((byte) ((port >> 8) & 0xFF)); buffer.putByte((byte) (port & 0xFF)); state = ProtocolState.CONNECTING; - session.writePacket(buffer).verify(getTimeout()); + session.writeBuffer(buffer).verify(getTimeout()); } private void doPasswordAuth(IoSession session) throws Exception { @@ -335,7 +335,7 @@ private void startAuth(IoSession session) throws Exception { "No data for proxy authentication with " //$NON-NLS-1$ + proxyAddress); } - session.writePacket(buffer).verify(getTimeout()); + session.writeBuffer(buffer).verify(getTimeout()); } finally { if (buffer != null) { buffer.clear(true); @@ -350,7 +350,7 @@ private void authStep(IoSession session, Buffer input) throws Exception { authenticator.process(); buffer = authenticator.getToken(); if (buffer != null) { - session.writePacket(buffer).verify(getTimeout()); + session.writeBuffer(buffer).verify(getTimeout()); } } finally { if (buffer != null) { diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java index 5a50cc8f2..33b234b1f 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java @@ -11,6 +11,7 @@ import static java.text.MessageFormat.format; import static org.apache.sshd.common.SshConstants.SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE; +import static org.apache.sshd.sftp.SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT; import java.io.Closeable; import java.io.IOException; @@ -38,17 +39,17 @@ import org.apache.sshd.client.future.ConnectFuture; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.session.forward.PortForwardingTracker; -import org.apache.sshd.client.subsystem.sftp.SftpClient; -import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; -import org.apache.sshd.client.subsystem.sftp.SftpClient.CopyMode; -import org.apache.sshd.client.subsystem.sftp.SftpClientFactory; import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.SshException; import org.apache.sshd.common.future.CloseFuture; import org.apache.sshd.common.future.SshFutureListener; -import org.apache.sshd.common.subsystem.sftp.SftpException; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.apache.sshd.sftp.client.SftpClient; +import org.apache.sshd.sftp.client.SftpClient.CloseableHandle; +import org.apache.sshd.sftp.client.SftpClient.CopyMode; +import org.apache.sshd.sftp.client.SftpClientFactory; +import org.apache.sshd.sftp.common.SftpException; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.transport.sshd.JGitSshClient; @@ -205,7 +206,7 @@ private void close(Closeable toClose, Throwable error) { private HostConfigEntry getHostConfig(String username, String host, int port) throws IOException { HostConfigEntry entry = client.getHostConfigEntryResolver() - .resolveEffectiveHost(host, port, null, username, null); + .resolveEffectiveHost(host, port, null, username, null, null); if (entry == null) { if (SshdSocketAddress.isIPv6Address(host)) { return new HostConfigEntry("", host, port, username); //$NON-NLS-1$ @@ -439,13 +440,12 @@ private class SshdFtpChannel implements FtpChannel { @Override public void connect(int timeout, TimeUnit unit) throws IOException { if (timeout <= 0) { - session.getProperties().put( - SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT, - Long.valueOf(Long.MAX_VALUE)); + // This timeout must not be null! + SFTP_CHANNEL_OPEN_TIMEOUT.set(session, + Duration.ofMillis(Long.MAX_VALUE)); } else { - session.getProperties().put( - SftpClient.SFTP_CHANNEL_OPEN_TIMEOUT, - Long.valueOf(unit.toMillis(timeout))); + SFTP_CHANNEL_OPEN_TIMEOUT.set(session, + Duration.ofMillis(unit.toMillis(timeout))); } ftp = SftpClientFactory.instance().createSftpClient(session); try { 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 df0e1d28a..357994d43 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 @@ -35,10 +35,13 @@ import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; import org.apache.sshd.client.config.hosts.HostConfigEntryResolver; import org.apache.sshd.common.SshException; +import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.compression.BuiltinCompressions; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.loader.openssh.kdf.BCryptKdfOptions; import org.apache.sshd.common.keyprovider.KeyIdentityProvider; +import org.apache.sshd.common.signature.BuiltinSignatures; +import org.apache.sshd.common.signature.Signature; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.transport.ssh.OpenSshConfigFile; @@ -205,6 +208,7 @@ public SshdSession getSession(URIish uri, .hostConfigEntryResolver(configFile) .serverKeyVerifier(new JGitServerKeyVerifier( getServerKeyDatabase(home, sshDir))) + .signatureFactories(getSignatureFactories()) .compressionFactories( new ArrayList<>(BuiltinCompressions.VALUES)) .build(); @@ -590,4 +594,35 @@ private List getUserAuthFactories() { protected String getDefaultPreferredAuthentications() { return null; } + + /** + * Apache MINA sshd 2.6.0 has removed DSA, DSA_CERT and RSA_CERT. We have to + * set it up explicitly to still allow users to connect with DSA keys. + * + * @return a list of supported signature factories + */ + @SuppressWarnings("deprecation") + private static List> getSignatureFactories() { + // @formatter:off + return Arrays.asList( + BuiltinSignatures.nistp256_cert, + BuiltinSignatures.nistp384_cert, + BuiltinSignatures.nistp521_cert, + BuiltinSignatures.ed25519_cert, + BuiltinSignatures.rsaSHA512_cert, + BuiltinSignatures.rsaSHA256_cert, + BuiltinSignatures.rsa_cert, + BuiltinSignatures.nistp256, + BuiltinSignatures.nistp384, + BuiltinSignatures.nistp521, + BuiltinSignatures.ed25519, + BuiltinSignatures.sk_ecdsa_sha2_nistp256, + BuiltinSignatures.sk_ssh_ed25519, + BuiltinSignatures.rsaSHA512, + BuiltinSignatures.rsaSHA256, + BuiltinSignatures.rsa, + BuiltinSignatures.dsa_cert, + BuiltinSignatures.dsa); + // @formatter:on + } } diff --git a/pom.xml b/pom.xml index e2aa7cabd..8257f952d 100644 --- a/pom.xml +++ b/pom.xml @@ -152,7 +152,7 @@ ${project.build.directory}/META-INF/MANIFEST.MF 5.9.0.202009080501-r - 2.4.0 + 2.6.0 0.1.55 1.1.1 1.1.7 @@ -164,8 +164,8 @@ 3.1.0 9.4.35.v20201120 0.14.4 - 4.5.10 - 4.4.12 + 4.5.13 + 4.4.14 1.7.30 1.2.15 3.2.0 From f077158acf64dfee6fafba46d2db375a1c938809 Mon Sep 17 00:00:00 2001 From: Alina Djamankulova Date: Fri, 5 Feb 2021 10:12:25 -0800 Subject: [PATCH 05/35] TransportGitAnon: remove unnecessary socket bind to a local address before connecting. A socket gets bound on connect in the next line. Signed-off-by: Alina Djamankulova Change-Id: I69a423c592e2fdd582b3c40099137b4ef3d05b39 --- .../src/org/eclipse/jgit/transport/TransportGitAnon.java | 1 - 1 file changed, 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java index fa4392d5a..a1914b618 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java @@ -121,7 +121,6 @@ Socket openConnection() throws TransportException { final Socket s = new Socket(); try { final InetAddress host = InetAddress.getByName(uri.getHost()); - s.bind(null); s.connect(new InetSocketAddress(host, port), tms); } catch (IOException c) { try { From 9299df41cb941ab21b6c4bd835510305b7fd5382 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sat, 26 Dec 2020 02:28:03 +0100 Subject: [PATCH 06/35] Fix SeparateClassloaderTestRunner on Java 9 or higher Since Java 9 the SystemClassLoader is no longer a URLClassLoader. Change-Id: I3aa834f1075e611c86fc4684fda6a50c684b3729 Signed-off-by: Matthias Sohn --- .../jgit/junit/SeparateClassloaderTestRunner.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 b982787e7..4a4dc92c4 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 @@ -9,10 +9,10 @@ */ package org.eclipse.jgit.junit; -import static java.lang.ClassLoader.getSystemClassLoader; - +import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Paths; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; @@ -40,7 +40,13 @@ public SeparateClassloaderTestRunner(Class klass) private static Class loadNewClass(Class klass) throws InitializationError { try { - URL[] urls = ((URLClassLoader) getSystemClassLoader()).getURLs(); + String pathSeparator = System.getProperty("path.separator"); + String[] classPathEntries = System.getProperty("java.class.path") + .split(pathSeparator); + URL[] urls = new URL[classPathEntries.length]; + for (int i = 0; i < classPathEntries.length; i++) { + urls[i] = Paths.get(classPathEntries[i]).toUri().toURL(); + } ClassLoader testClassLoader = new URLClassLoader(urls) { @Override @@ -54,7 +60,7 @@ public Class loadClass(String name) } }; return Class.forName(klass.getName(), true, testClassLoader); - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException | MalformedURLException e) { throw new InitializationError(e); } } From 58f2e23fded26f8f4a1bfe436b1cbe72bcc11aa4 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sun, 3 Jan 2021 02:10:58 +0100 Subject: [PATCH 07/35] Fix FileRepository#convertToReftable which failed if no reflog existed Deleting non-existing files when converting to reftable without backup caused convertToReftable to fail. Observed this on a mirrored repository which had no reflogs. Fix this by skipping missing files during deletion. Change-Id: I3bb913d5bfddccc6813677b873006efb849a6ebc Signed-off-by: Matthias Sohn --- .../jgit/internal/storage/file/FileRepository.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 b939d37ca..51ee9e9d1 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 @@ -759,12 +759,14 @@ void convertToReftable(boolean writeLogs, boolean backup) } } else { FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING); - FileUtils.delete(headFile); - FileUtils.delete(logsDir, FileUtils.RECURSIVE); - FileUtils.delete(refsFile, FileUtils.RECURSIVE); + FileUtils.delete(headFile, FileUtils.SKIP_MISSING); + FileUtils.delete(logsDir, + FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + FileUtils.delete(refsFile, + FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); for (String r : additional) { new File(getDirectory(), r).delete(); - } + } } FileUtils.mkdir(refsFile, true); From 3259a960217f1dd44c46007a545f473bd575f951 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Wed, 3 Feb 2021 11:55:39 +0100 Subject: [PATCH 08/35] Field updateHead can be a local variable in RefDirectoryRename Keeping the field updateDate is unecessary, as it is set and used only in the doRename method. Change-Id: I1cdd1adf759b75c103480db7a74cec8c2d78b794 Signed-off-by: Lars Vogel --- .../jgit/internal/storage/file/RefDirectoryRename.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java index 9dbdbc73f..2c0ade681 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java @@ -51,9 +51,6 @@ class RefDirectoryRename extends RefRename { */ private ObjectId objId; - /** True if HEAD must be moved to the destination reference. */ - private boolean updateHEAD; - /** A reference we backup {@link #objId} into during the rename. */ private RefDirectoryUpdate tmp; @@ -69,7 +66,7 @@ protected Result doRename() throws IOException { return Result.IO_FAILURE; // not supported objId = source.getOldObjectId(); - updateHEAD = needToUpdateHEAD(); + boolean updateHEAD = needToUpdateHEAD(); tmp = refdb.newTemporaryUpdate(); try (RevWalk rw = new RevWalk(refdb.getRepository())) { // First backup the source so its never unreachable. From c7685003d8fb11e39234866fd43b82b7ba883a2f Mon Sep 17 00:00:00 2001 From: Adithya Chakilam Date: Fri, 29 Jan 2021 19:35:04 -0600 Subject: [PATCH 09/35] Fix DateRevQueue tie breaks with more than 2 elements DateRevQueue is expected to give out the commits that have higher commit time. But in case of tie(same commit time), it should give the commit that is inserted first. This is inferred from the testInsertTie test case written for DateRevQueue. Also that test case, right now uses just two commits which caused it not to fail with the current implementation, so added another commit to make the test more robust. By fixing the DateRevQueue, we would also match the behaviour of LogCommand.addRange(c1,c2) with git log c1..c2. A test case for the same is added to show that current behaviour is not the expected one. By fixing addRange(), the order in which commits are applied during a rebase is altered. Rebase logic should have never depended upon LogCommand.addRange() since the intended order of addRange() is not the order a rebase should use. So, modify the RebaseCommand to use RevWalk directly with TopoNonIntermixSortGenerator. Add a new LogCommandTest.addRangeWithMerge() test case which creates commits in the following order: A - B - C - M \ / -D- Using git 2.30.0, git log B..M outputs: M C D LogCommand.addRange(B, M) without this fix outputs: M D C LogCommand.addRange(B, M) with this fix outputs: M C D Change-Id: I30cc3ba6c97f0960f64e9e021df96ff276f63db7 Signed-off-by: Adithya Chakilam --- .../org/eclipse/jgit/api/LogCommandTest.java | 47 +++++++++++++++++++ .../jgit/revwalk/DateRevQueueTest.java | 6 +++ .../org/eclipse/jgit/api/RebaseCommand.java | 21 +++++---- .../eclipse/jgit/revwalk/DateRevQueue.java | 2 +- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java index 6460c7988..c563d5a47 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogCommandTest.java @@ -232,6 +232,53 @@ public void logNoMergeCommits() throws Exception { assertFalse(i.hasNext()); } + /** + *
+	 * A - B - C - M
+	 *      \     /
+	 *        -D(side)
+	 * 
+ */ + @Test + public void addRangeWithMerge() throws Exception{ + String fileA = "fileA"; + String fileB = "fileB"; + Git git = Git.wrap(db); + + writeTrashFile(fileA, fileA); + git.add().addFilepattern(fileA).call(); + git.commit().setMessage("commit a").call(); + + writeTrashFile(fileA, fileA); + git.add().addFilepattern(fileA).call(); + RevCommit b = git.commit().setMessage("commit b").call(); + + writeTrashFile(fileA, fileA); + git.add().addFilepattern(fileA).call(); + RevCommit c = git.commit().setMessage("commit c").call(); + + createBranch(b, "refs/heads/side"); + checkoutBranch("refs/heads/side"); + + writeTrashFile(fileB, fileB); + git.add().addFilepattern(fileB).call(); + RevCommit d = git.commit().setMessage("commit d").call(); + + checkoutBranch("refs/heads/master"); + MergeResult m = git.merge().include(d.getId()).call(); + assertEquals(MergeResult.MergeStatus.MERGED, m.getMergeStatus()); + + Iterator rangeLog = git.log().addRange(b.getId(), m.getNewHead()).call().iterator(); + + RevCommit commit = rangeLog.next(); + assertEquals(m.getNewHead(), commit.getId()); + commit = rangeLog.next(); + assertEquals(c.getId(), commit.getId()); + commit = rangeLog.next(); + assertEquals(d.getId(), commit.getId()); + assertFalse(rangeLog.hasNext()); + } + private void setCommitsAndMerge() throws Exception { Git git = Git.wrap(db); writeTrashFile("file1", "1\n2\n3\n4\n"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java index a9dfe15c9..852d18c35 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java @@ -59,20 +59,26 @@ public void testInsertOutOfOrder() throws Exception { public void testInsertTie() throws Exception { final RevCommit a = parseBody(commit()); final RevCommit b = parseBody(commit(0, a)); + final RevCommit c = parseBody(commit(0, b)); + { q = create(); q.add(a); q.add(b); + q.add(c); assertCommit(a, q.next()); assertCommit(b, q.next()); + assertCommit(c, q.next()); assertNull(q.next()); } { q = create(); + q.add(c); q.add(b); q.add(a); + assertCommit(c, q.next()); assertCommit(b, q.next()); assertCommit(a, q.next()); assertNull(q.next()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 6678af163..836175dce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -67,6 +67,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode; @@ -1137,15 +1138,19 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) { private List calculatePickList(RevCommit headCommit) throws GitAPIException, NoHeadException, IOException { - Iterable commitsToUse; - try (Git git = new Git(repo)) { - LogCommand cmd = git.log().addRange(upstreamCommit, headCommit); - commitsToUse = cmd.call(); - } List cherryPickList = new ArrayList<>(); - for (RevCommit commit : commitsToUse) { - if (preserveMerges || commit.getParentCount() == 1) - cherryPickList.add(commit); + try (RevWalk r = new RevWalk(repo)) { + r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); + r.sort(RevSort.COMMIT_TIME_DESC, true); + r.markUninteresting(r.lookupCommit(upstreamCommit)); + r.markStart(r.lookupCommit(headCommit)); + Iterator commitsToUse = r.iterator(); + while (commitsToUse.hasNext()) { + RevCommit commit = commitsToUse.next(); + if (preserveMerges || commit.getParentCount() == 1) { + cherryPickList.add(commit); + } + } } Collections.reverse(cherryPickList); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java index b875be927..0cabf0705 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java @@ -93,7 +93,7 @@ else if (t > when) head = n; } else { Entry p = q.next; - while (p != null && p.commit.commitTime > when) { + while (p != null && p.commit.commitTime >= when) { q = p; p = q.next; } From 7755645777d89cfbd9f553bc394a52bb892b6c59 Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sun, 7 Feb 2021 15:36:35 +0100 Subject: [PATCH 10/35] Bazel: Remove unused resources variable Change-Id: Iac2e547791929c26027ab4730ceac6177899ccf1 Signed-off-by: David Ostrovsky --- org.eclipse.jgit.test/BUILD | 2 -- 1 file changed, 2 deletions(-) diff --git a/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD index 26a1fbdfc..c9b5d3726 100644 --- a/org.eclipse.jgit.test/BUILD +++ b/org.eclipse.jgit.test/BUILD @@ -44,8 +44,6 @@ EXCLUDED = [ PKG + "api/SecurityManagerMissingPermissionsTest.java", ] -RESOURCES = glob(["resources/**"]) - tests(tests = glob( ["tst/**/*.java"], exclude = HELPERS + DATA + EXCLUDED, From 19bed3399de12654481f8072cab60cb4b1796d8a Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Sun, 7 Feb 2021 15:38:20 +0100 Subject: [PATCH 11/35] Bump bazel version to 4.0.0 Change-Id: I2faa67d5083f23b29f7a434e54c5e17360b1c0fe Signed-off-by: David Ostrovsky --- .bazelversion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 8faff82c7..fcdb2e109 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.0.0rc2 +4.0.0 From 6d462e5fe95e20245656aaac4ead6f4c48e319d5 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 24 Jan 2021 01:57:09 +0100 Subject: [PATCH 12/35] GPG: support git config gpg.program Add it to the GpgConfig. Change GpgConfig to load the values once only. Add a parameter to the GpgObjectSigner interface's operations to pass in a GpgConfig. Update CommitCommand and TagCommand to pass the value to the signer. Let the signer decide whether it can actually produce the wanted signature type (openpgp or x509). No behavior change. But this makes it possible to implement different signers that might support x509 signatures, or use gpg.program and shell out to an external GPG executable for signing. Change-Id: I427f83eb1ece81c310e1cddd85315f6f88cc99ea Signed-off-by: Thomas Wolf --- .../bc/internal/BouncyCastleGpgSigner.java | 36 +++++++- .../org/eclipse/jgit/api/CommitCommand.java | 55 ++++++++++-- .../src/org/eclipse/jgit/api/TagCommand.java | 56 +++++++++--- .../org/eclipse/jgit/lib/ConfigConstants.java | 8 ++ .../src/org/eclipse/jgit/lib/GpgConfig.java | 85 +++++++++++++++---- .../org/eclipse/jgit/lib/GpgObjectSigner.java | 38 ++++++++- 6 files changed, 241 insertions(+), 37 deletions(-) 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 449c4a487..f448d5e9e 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 @@ -34,13 +34,17 @@ import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgSignature; import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.ObjectBuilder; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.util.StringUtils; @@ -70,6 +74,24 @@ public BouncyCastleGpgSigner() { public boolean canLocateSigningKey(@Nullable String gpgSigningKey, PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException { + try { + return canLocateSigningKey(gpgSigningKey, committer, + credentialsProvider, null); + } catch (UnsupportedSigningFormatException e) { + // Cannot occur with a null config + return false; + } + } + + @Override + public boolean canLocateSigningKey(@Nullable String gpgSigningKey, + PersonIdent committer, CredentialsProvider credentialsProvider, + GpgConfig config) + throws CanceledException, UnsupportedSigningFormatException { + if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { + throw new UnsupportedSigningFormatException( + JGitText.get().onlyOpenPgpSupportedForSigning); + } try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( credentialsProvider)) { BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, @@ -101,13 +123,23 @@ private BouncyCastleGpgKey locateSigningKey(@Nullable String gpgSigningKey, public void sign(@NonNull CommitBuilder commit, @Nullable String gpgSigningKey, @NonNull PersonIdent committer, CredentialsProvider credentialsProvider) throws CanceledException { - signObject(commit, gpgSigningKey, committer, credentialsProvider); + try { + signObject(commit, gpgSigningKey, committer, credentialsProvider, + null); + } catch (UnsupportedSigningFormatException e) { + // Cannot occur with a null config + } } @Override public void signObject(@NonNull ObjectBuilder object, @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException { + CredentialsProvider credentialsProvider, GpgConfig config) + throws CanceledException, UnsupportedSigningFormatException { + if (config != null && config.getKeyFormat() != GpgFormat.OPENPGP) { + throw new UnsupportedSigningFormatException( + JGitText.get().onlyOpenPgpSupportedForSigning); + } try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( credentialsProvider)) { BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index b4f717503..31f6a31c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -47,6 +47,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.GpgConfig; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; +import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -120,6 +121,8 @@ public class CommitCommand extends GitCommand { private GpgSigner gpgSigner; + private GpgConfig gpgConfig; + private CredentialsProvider credentialsProvider; /** @@ -247,8 +250,18 @@ public RevCommit call() throws GitAPIException, AbortedByHookException, throw new ServiceUnavailableException( JGitText.get().signingServiceUnavailable); } - gpgSigner.sign(commit, signingKey, committer, - credentialsProvider); + if (gpgSigner instanceof GpgObjectSigner) { + ((GpgObjectSigner) gpgSigner).signObject(commit, + signingKey, committer, credentialsProvider, + gpgConfig); + } else { + if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { + throw new UnsupportedSigningFormatException(JGitText + .get().onlyOpenPgpSupportedForSigning); + } + gpgSigner.sign(commit, signingKey, committer, + credentialsProvider); + } } ObjectId commitId = odi.insert(commit); @@ -576,7 +589,9 @@ private void processOptions(RepositoryState state, RevWalk rw) // an explicit message throw new NoMessageException(JGitText.get().commitMessageNotSpecified); - GpgConfig gpgConfig = new GpgConfig(repo.getConfig()); + if (gpgConfig == null) { + gpgConfig = new GpgConfig(repo.getConfig()); + } if (signCommit == null) { signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE : Boolean.FALSE; @@ -585,10 +600,6 @@ private void processOptions(RepositoryState state, RevWalk rw) signingKey = gpgConfig.getSigningKey(); } if (gpgSigner == null) { - if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException( - JGitText.get().onlyOpenPgpSupportedForSigning); - } gpgSigner = GpgSigner.getDefault(); } } @@ -972,6 +983,36 @@ public CommitCommand setSign(Boolean sign) { return this; } + /** + * Sets the {@link GpgSigner} to use if the commit is to be signed. + * + * @param signer + * to use; if {@code null}, the default signer will be used + * @return {@code this} + * @since 5.11 + */ + public CommitCommand setGpgSigner(GpgSigner signer) { + checkCallable(); + this.gpgSigner = signer; + return this; + } + + /** + * Sets an external {@link GpgConfig} to use. Whether it will be used is at + * the discretion of the {@link #setGpgSigner(GpgSigner)}. + * + * @param config + * to set; if {@code null}, the config will be loaded from the + * git config of the repository + * @return {@code this} + * @since 5.11 + */ + public CommitCommand setGpgConfig(GpgConfig config) { + checkCallable(); + this.gpgConfig = config; + return this; + } + /** * Sets a {@link CredentialsProvider} * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java index 75f942d2e..58c18b38d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java @@ -23,6 +23,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.GpgSigner; import org.eclipse.jgit.lib.ObjectId; @@ -34,7 +35,6 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.TagBuilder; -import org.eclipse.jgit.lib.GpgConfig.GpgFormat; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.CredentialsProvider; @@ -80,6 +80,8 @@ public class TagCommand extends GitCommand { private String signingKey; + private GpgConfig gpgConfig; + private GpgObjectSigner gpgSigner; private CredentialsProvider credentialsProvider; @@ -138,7 +140,7 @@ public Ref call() throws GitAPIException, ConcurrentRefUpdateException, if (gpgSigner != null) { gpgSigner.signObject(newTag, signingKey, tagger, - credentialsProvider); + credentialsProvider, gpgConfig); } // write the tag object @@ -228,7 +230,9 @@ private void processOptions(RepositoryState state) } // Figure out whether to sign. if (!(Boolean.FALSE.equals(signed) && signingKey == null)) { - GpgConfig gpgConfig = new GpgConfig(repo.getConfig()); + if (gpgConfig == null) { + gpgConfig = new GpgConfig(repo.getConfig()); + } boolean doSign = isSigned() || gpgConfig.isSignAllTags(); if (!Boolean.TRUE.equals(annotated) && !doSign) { doSign = gpgConfig.isSignAnnotated(); @@ -237,16 +241,14 @@ private void processOptions(RepositoryState state) if (signingKey == null) { signingKey = gpgConfig.getSigningKey(); } - if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) { - throw new UnsupportedSigningFormatException( - JGitText.get().onlyOpenPgpSupportedForSigning); + if (gpgSigner == null) { + GpgSigner signer = GpgSigner.getDefault(); + if (!(signer instanceof GpgObjectSigner)) { + throw new ServiceUnavailableException( + JGitText.get().signingServiceUnavailable); + } + gpgSigner = (GpgObjectSigner) signer; } - GpgSigner signer = GpgSigner.getDefault(); - if (!(signer instanceof GpgObjectSigner)) { - throw new ServiceUnavailableException( - JGitText.get().signingServiceUnavailable); - } - gpgSigner = (GpgObjectSigner) signer; // The message of a signed tag must end in a newline because // the signature will be appended. if (message != null && !message.isEmpty() @@ -331,6 +333,36 @@ public TagCommand setSigned(boolean signed) { return this; } + /** + * Sets the {@link GpgSigner} to use if the commit is to be signed. + * + * @param signer + * to use; if {@code null}, the default signer will be used + * @return {@code this} + * @since 5.11 + */ + public TagCommand setGpgSigner(GpgObjectSigner signer) { + checkCallable(); + this.gpgSigner = signer; + return this; + } + + /** + * Sets an external {@link GpgConfig} to use. Whether it will be used is at + * the discretion of the {@link #setGpgSigner(GpgObjectSigner)}. + * + * @param config + * to set; if {@code null}, the config will be loaded from the + * git config of the repository + * @return {@code this} + * @since 5.11 + */ + public TagCommand setGpgConfig(GpgConfig config) { + checkCallable(); + this.gpgConfig = config; + return this; + } + /** * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be * created from the info in the repository. 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 954a75cb2..7381c905b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -104,8 +104,16 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_FORMAT = "format"; + /** + * The "program" key + * + * @since 5.11 + */ + public static final String CONFIG_KEY_PROGRAM = "program"; + /** * The "signingKey" key + * * @since 5.2 */ public static final String CONFIG_KEY_SIGNINGKEY = "signingKey"; 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 5b4372973..427a235f3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Salesforce. and others + * Copyright (C) 2018, 2021 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 @@ -43,16 +43,65 @@ public String toConfigValue() { } } - private final Config config; + private final GpgFormat keyFormat; + + private final String signingKey; + + private final String program; + + private final boolean signCommits; + + private final boolean signAllTags; + + private final boolean forceAnnotated; /** - * Create a new GPG config, which will read configuration from config. + * Create a {@link GpgConfig} with the given parameters and default + * {@code true} for signing commits and {@code false} for tags. + * + * @param keySpec + * to use + * @param format + * to use + * @param gpgProgram + * to use + * @since 5.11 + */ + public GpgConfig(String keySpec, GpgFormat format, String gpgProgram) { + keyFormat = format; + signingKey = keySpec; + program = gpgProgram; + signCommits = true; + signAllTags = false; + forceAnnotated = false; + } + + /** + * Create a new GPG config that reads the configuration from config. * * @param config * the config to read from */ public GpgConfig(Config config) { - this.config = config; + keyFormat = config.getEnum(GpgFormat.values(), + ConfigConstants.CONFIG_GPG_SECTION, null, + ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP); + signingKey = config.getString(ConfigConstants.CONFIG_USER_SECTION, null, + ConfigConstants.CONFIG_KEY_SIGNINGKEY); + + String exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, + keyFormat.toConfigValue(), ConfigConstants.CONFIG_KEY_PROGRAM); + if (exe == null) { + exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, null, + ConfigConstants.CONFIG_KEY_PROGRAM); + } + program = exe; + signCommits = config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION, + ConfigConstants.CONFIG_KEY_GPGSIGN, false); + signAllTags = config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION, + ConfigConstants.CONFIG_KEY_GPGSIGN, false); + forceAnnotated = config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION, + ConfigConstants.CONFIG_KEY_FORCE_SIGN_ANNOTATED, false); } /** @@ -61,9 +110,19 @@ public GpgConfig(Config config) { * @return the {@link org.eclipse.jgit.lib.GpgConfig.GpgFormat} */ public GpgFormat getKeyFormat() { - return config.getEnum(GpgFormat.values(), - ConfigConstants.CONFIG_GPG_SECTION, null, - ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP); + return keyFormat; + } + + /** + * Retrieves the value of the configured GPG program to use, as defined by + * gpg.openpgp.program, gpg.x509.program (depending on the defined + * {@link #getKeyFormat() format}), or gpg.program. + * + * @return the program string configured, or {@code null} if none + * @since 5.11 + */ + public String getProgram() { + return program; } /** @@ -72,8 +131,7 @@ public GpgFormat getKeyFormat() { * @return the value of user.signingKey (may be null) */ public String getSigningKey() { - return config.getString(ConfigConstants.CONFIG_USER_SECTION, null, - ConfigConstants.CONFIG_KEY_SIGNINGKEY); + return signingKey; } /** @@ -82,8 +140,7 @@ public String getSigningKey() { * @return the value of commit.gpgSign (defaults to false) */ public boolean isSignCommits() { - return config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION, - ConfigConstants.CONFIG_KEY_GPGSIGN, false); + return signCommits; } /** @@ -94,8 +151,7 @@ public boolean isSignCommits() { * @since 5.11 */ public boolean isSignAllTags() { - return config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION, - ConfigConstants.CONFIG_KEY_GPGSIGN, false); + return signAllTags; } /** @@ -107,7 +163,6 @@ public boolean isSignAllTags() { * @since 5.11 */ public boolean isSignAnnotated() { - return config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION, - ConfigConstants.CONFIG_KEY_FORCE_SIGN_ANNOTATED, false); + return forceAnnotated; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java index 6fb767774..074f46567 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java @@ -12,6 +12,7 @@ import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.CanceledException; +import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; import org.eclipse.jgit.transport.CredentialsProvider; /** @@ -48,12 +49,47 @@ public interface GpgObjectSigner { * @param credentialsProvider * provider to use when querying for signing key credentials (eg. * passphrase) + * @param config + * GPG settings from the git config * @throws CanceledException * when signing was canceled (eg., user aborted when entering * passphrase) + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported */ void signObject(@NonNull ObjectBuilder object, @Nullable String gpgSigningKey, @NonNull PersonIdent committer, - CredentialsProvider credentialsProvider) throws CanceledException; + CredentialsProvider credentialsProvider, GpgConfig config) + throws CanceledException, UnsupportedSigningFormatException; + + /** + * Indicates if a signing key is available for the specified committer + * and/or signing key. + * + * @param gpgSigningKey + * the signing key to locate (passed as is to the GPG signing + * tool as is; eg., value of user.signingkey) + * @param committer + * the signing identity (to help with key lookup in case signing + * key is not specified) + * @param credentialsProvider + * provider to use when querying for signing key credentials (eg. + * passphrase) + * @param config + * GPG settings from the git config + * @return true if a signing key is available, + * false otherwise + * @throws CanceledException + * when signing was canceled (eg., user aborted when entering + * passphrase) + * @throws UnsupportedSigningFormatException + * if a config is given and the wanted key format is not + * supported + */ + public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey, + @NonNull PersonIdent committer, + CredentialsProvider credentialsProvider, GpgConfig config) + throws CanceledException, UnsupportedSigningFormatException; } From 686565f416752db9b89902aa20609c0b99d1a40d Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Tue, 9 Feb 2021 02:17:14 +0100 Subject: [PATCH 13/35] GitHook: make fields outputStream and errorStream private Subclasses can use the corresponding getter methods. Change-Id: Iaa9ab01f5a9731a264b28608d2418a9405b601d7 Signed-off-by: Matthias Sohn --- org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index 4059b16b3..53f4819d3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -44,12 +44,12 @@ abstract class GitHook implements Callable { /** * The output stream to be used by the hook. */ - protected final PrintStream outputStream; + private final PrintStream outputStream; /** * The error stream to be used by the hook. */ - protected final PrintStream errorStream; + private final PrintStream errorStream; /** * Constructor for GitHook. From 1b9911d9ae23f14a7e7a5330c47e63a99fe1469d Mon Sep 17 00:00:00 2001 From: Marija Savtchouk Date: Thu, 21 Jan 2021 15:08:57 +0000 Subject: [PATCH 14/35] Allow dir/file conflicts in virtual base commit on recursive merge. If RecursiveMerger finds multiple base commits, it tries to compute the virtual ancestor to use as a base for the three way merge. Currently, the content conflicts between ancestors are ignored (file staged with the conflict markers). If the path is a file in one ancestor and a dir in the other, it results in NoMergeBaseException (CONFLICTS_DURING_MERGE_BASE_CALCULATION). Allow these conflicts by ignoring this unmerged path in the virtual base. The merger will compute diff in the children instead and it can be further fixed manually if needed. Change-Id: Id59648ae1d6bdf300b26fff513c3204317b755ab Signed-off-by: Marija Savtchouk --- .../org/eclipse/jgit/merge/MergerTest.java | 264 ++++++++++++++++++ .../org/eclipse/jgit/merge/ResolveMerger.java | 23 +- 2 files changed, 277 insertions(+), 10 deletions(-) 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 e2ac89be9..eecf25be9 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 @@ -1384,6 +1384,270 @@ public void checkMergeConflictInVirtualAncestor( git.merge().include(commitB).call(); } + /** + * Merging two commits with a file/dir conflict in the virtual ancestor. + * + *

+ * Those conflicts should be ignored, otherwise the found base can not be used by the + * RecursiveMerger. + *

+	 *  --------------
+	 * |              \
+	 * |         C1 - C4 --- ?     master
+	 * |        /          /
+	 * |  I - A1 - C2 - C3         second-branch
+	 * |   \            /
+	 * \    \          /
+	 *  ----A2--------             branch-to-merge
+	 *  
+ *

+ *

+ * Path "a" is initially a file in I and A1. It is changed to a directory in A2 + * ("branch-to-merge"). + *

+ * A2 is merged into "master" and "second-branch". The dir/file merge conflict is resolved + * manually, results in C4 and C3. + *

+ * While merging C3 and C4, A1 and A2 are the base commits found by the recursive merge that + * have the dir/file conflict. + */ + @Theory + public void checkFileDirMergeConflictInVirtualAncestor_NoConflictInChildren( + MergeStrategy strategy) + throws Exception { + if (!strategy.equals(MergeStrategy.RECURSIVE)) { + return; + } + + Git git = Git.wrap(db); + + // master + writeTrashFile("a", "initial content"); + git.add().addFilepattern("a").call(); + RevCommit commitI = git.commit().setMessage("Initial commit").call(); + + writeTrashFile("a", "content in Ancestor 1"); + git.add().addFilepattern("a").call(); + RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call(); + + writeTrashFile("a", "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(); + // "a" becomes a directory in A2 + git.rm().addFilepattern("a").call(); + writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)"); + git.add().addFilepattern("a/content").call(); + RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call(); + + // second branch + git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call(); + writeTrashFile("a", "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, merge "a" as a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + writeTrashFile("a", "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 - merge "a" as a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + writeTrashFile("a", "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); + + } + + @Theory + public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_FileDir(MergeStrategy strategy) + throws Exception { + if (!strategy.equals(MergeStrategy.RECURSIVE)) { + return; + } + + Git git = Git.wrap(db); + + // master + writeTrashFile("a", "initial content"); + git.add().addFilepattern("a").call(); + RevCommit commitI = git.commit().setMessage("Initial commit").call(); + + writeTrashFile("a", "content in Ancestor 1"); + git.add().addFilepattern("a").call(); + RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call(); + + writeTrashFile("a", "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(); + + // "a" becomes a directory in A2 + git.rm().addFilepattern("a").call(); + writeTrashFile("a/content", "content in Ancestor 2 (commited on branch-to-merge)"); + git.add().addFilepattern("a/content").call(); + RevCommit commitA2 = git.commit().setMessage("Ancestor 2").call(); + + // second branch + git.checkout().setCreateBranch(true).setStartPoint(commitA1).setName("second-branch").call(); + writeTrashFile("a", "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 - write a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + writeTrashFile("a", + "content in Child 3 (commited on second-branch) - 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 - write a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + writeTrashFile("a", "content in Child 4 (commited on master) - 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 + mergeResult = git.merge().include(commitC3S).call(); + assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING); + String expected = + "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n" + + "=======\n" + + "content in Child 3 (commited on second-branch) - merge conflict resolution\n" + + ">>>>>>> " + commitC3S.name() + "\n"; + assertEquals(expected, read("a")); + // Nothing was populated from the ancestors. + assertEquals( + "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]", + indexState(CONTENT)); + } + + /** + * Same test as above, but "a" is a dir in A1 and a file in A2 + */ + @Theory + public void checkFileDirMergeConflictInVirtualAncestor_ConflictInChildren_DirFile(MergeStrategy strategy) + throws Exception { + if (!strategy.equals(MergeStrategy.RECURSIVE)) { + return; + } + + Git git = Git.wrap(db); + + // master + writeTrashFile("a/content", "initial content"); + git.add().addFilepattern("a/content").call(); + RevCommit commitI = git.commit().setMessage("Initial commit").call(); + + writeTrashFile("a/content", "content in Ancestor 1"); + git.add().addFilepattern("a/content").call(); + RevCommit commitA1 = git.commit().setMessage("Ancestor 1").call(); + + writeTrashFile("a/content", "content in Child 1 (commited on master)"); + git.add().addFilepattern("a/content").call(); + // commit C1M + git.commit().setMessage("Child 1 on master").call(); + + git.checkout().setCreateBranch(true).setStartPoint(commitI).setName("branch-to-merge").call(); + + // "a" becomes a file in A2 + git.rm().addFilepattern("a/content").call(); + writeTrashFile("a", "content in Ancestor 2 (commited on branch-to-merge)"); + 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/content", "content in Child 2 (commited on second-branch)"); + git.add().addFilepattern("a/content").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 - write a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + deleteTrashFile("a/content"); + deleteTrashFile("a"); + writeTrashFile("a", "content in Child 3 (commited on second-branch) - 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 - write a file + git.rm().addFilepattern("a").call(); + git.rm().addFilepattern("a/content").call(); + deleteTrashFile("a/content"); + deleteTrashFile("a"); + writeTrashFile("a", "content in Child 4 (commited on master) - 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 + mergeResult = git.merge().include(commitC3S).call(); + assertEquals(mergeResult.getMergeStatus(), MergeStatus.CONFLICTING); + String expected = "<<<<<<< HEAD\n" + "content in Child 4 (commited on master) - merge conflict resolution\n" + + "=======\n" + "content in Child 3 (commited on second-branch) - merge conflict resolution\n" + + ">>>>>>> " + commitC3S.name() + "\n"; + assertEquals(expected, read("a")); + // Nothing was populated from the ancestors. + assertEquals( + "[a, mode:100644, stage:2, content:content in Child 4 (commited on master) - merge conflict resolution][a, mode:100644, stage:3, content:content in Child 3 (commited on second-branch) - merge conflict resolution]", + indexState(CONTENT)); + } + private void writeSubmodule(String path, ObjectId commit) throws IOException, ConfigInvalidException { addSubmoduleToIndex(path, commit); 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 6c217fdf2..4bfb38d28 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -703,18 +703,21 @@ protected boolean processEntry(CanonicalTreeParser base, // conflict between ours and theirs. file/folder conflicts between // base/index/workingTree and something else are not relevant or // detected later - if (nonTree(modeO) && !nonTree(modeT)) { + if (nonTree(modeO) != nonTree(modeT)) { + if (ignoreConflicts) { + // In case of merge failures, ignore this path instead of reporting unmerged, so + // a caller can use virtual commit. This will not result in files with conflict + // markers in the index/working tree. The actual diff on the path will be + // computed directly on children. + enterSubtree = false; + return true; + } if (nonTree(modeB)) add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); - add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); - unmergedPaths.add(tw.getPathString()); - enterSubtree = false; - return true; - } - if (nonTree(modeT) && !nonTree(modeO)) { - if (nonTree(modeB)) - add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); - add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + if (nonTree(modeO)) + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); + if (nonTree(modeT)) + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); unmergedPaths.add(tw.getPathString()); enterSubtree = false; return true; From efb154fc24fbf416ae3513942fa720128358b31b Mon Sep 17 00:00:00 2001 From: Nasser Grainawi Date: Wed, 10 Feb 2021 22:42:41 -0700 Subject: [PATCH 15/35] Rename PackFile to Pack Pack better represents the purpose of the object and paves the way to add a PackFile object that extends File. Change-Id: I39b4f697902d395e9b6df5e8ce53078ce72fcea3 Signed-off-by: Nasser Grainawi --- .../jgit/http/server/InfoPacksServlet.java | 4 +- .../eclipse/jgit/junit/TestRepository.java | 6 +- .../storage/file/GcBasicPackingTest.java | 4 +- .../storage/file/GcConcurrentTest.java | 8 +- .../storage/file/GcKeepFilesTest.java | 6 +- .../storage/file/PackFileSnapshotTest.java | 38 +++++----- .../storage/file/PackInserterTest.java | 28 +++---- .../file/{PackFileTest.java => PackTest.java} | 30 ++++---- .../internal/storage/file/PackWriterTest.java | 4 +- .../storage/file/T0004_PackReaderTest.java | 4 +- .../jgit/transport/PackParserTest.java | 46 ++++++------ .../jgit/errors/NoPackSignatureException.java | 3 +- .../jgit/errors/PackInvalidException.java | 2 +- .../jgit/errors/PackMismatchException.java | 2 +- .../UnsupportedPackVersionException.java | 2 +- .../storage/file/ByteArrayWindow.java | 2 +- .../storage/file/ByteBufferWindow.java | 2 +- .../internal/storage/file/ByteWindow.java | 10 +-- .../storage/file/CachedObjectDirectory.java | 4 +- .../internal/storage/file/DeltaBaseCache.java | 6 +- .../storage/file/FileObjectDatabase.java | 4 +- .../jgit/internal/storage/file/GC.java | 62 +++++++-------- .../storage/file/LargePackedWholeObject.java | 4 +- .../storage/file/LocalCachedPack.java | 20 ++--- .../file/LocalObjectRepresentation.java | 24 +++--- .../storage/file/LocalObjectToPack.java | 2 +- .../storage/file/ObjectDirectory.java | 10 +-- .../file/ObjectDirectoryPackParser.java | 6 +- .../storage/file/{PackFile.java => Pack.java} | 10 +-- .../internal/storage/file/PackDirectory.java | 75 ++++++++++--------- .../jgit/internal/storage/file/PackIndex.java | 2 +- .../storage/file/PackIndexWriter.java | 2 +- .../storage/file/PackInputStream.java | 4 +- .../jgit/internal/storage/file/PackLock.java | 2 +- .../storage/file/PackReverseIndex.java | 2 +- .../internal/storage/file/WindowCache.java | 73 +++++++++--------- .../internal/storage/file/WindowCursor.java | 14 ++-- 37 files changed, 265 insertions(+), 262 deletions(-) rename org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/{PackFileTest.java => PackTest.java} (94%) rename org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/{PackFile.java => Pack.java} (99%) diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java index c3d72552a..e90580b75 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java @@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.internal.storage.file.ObjectDirectory; -import org.eclipse.jgit.internal.storage.file.PackFile; +import org.eclipse.jgit.internal.storage.file.Pack; import org.eclipse.jgit.lib.ObjectDatabase; /** Sends the current list of pack files, sorted most recent first. */ @@ -38,7 +38,7 @@ private static String packList(HttpServletRequest req) { final StringBuilder out = new StringBuilder(); final ObjectDatabase db = getRepository(req).getObjectDatabase(); if (db instanceof ObjectDirectory) { - for (PackFile pack : ((ObjectDirectory) db).getPacks()) { + for (Pack pack : ((ObjectDirectory) db).getPacks()) { out.append("P "); out.append(pack.getPackFile().getName()); out.append('\n'); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index a5b3b1f3a..e3eb2c536 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -43,7 +43,7 @@ import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.internal.storage.file.LockFile; import org.eclipse.jgit.internal.storage.file.ObjectDirectory; -import org.eclipse.jgit.internal.storage.file.PackFile; +import org.eclipse.jgit.internal.storage.file.Pack; import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; @@ -773,7 +773,7 @@ protected void writeFile(String name, byte[] bin) rw.writeInfoRefs(); final StringBuilder w = new StringBuilder(); - for (PackFile p : fr.getObjectDatabase().getPacks()) { + for (Pack p : fr.getObjectDatabase().getPacks()) { w.append("P "); w.append(p.getPackFile().getName()); w.append('\n'); @@ -954,7 +954,7 @@ public void close() { } private static void prunePacked(ObjectDirectory odb) throws IOException { - for (PackFile p : odb.getPacks()) { + for (Pack p : odb.getPacks()) { for (MutableEntry e : p) FileUtils.delete(odb.fileFor(e.toObjectId())); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java index d007dd451..42e423845 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java @@ -157,7 +157,7 @@ public void testNotPackTwice(boolean aggressive) throws Exception { .create(); tr.update("refs/tags/t1", second); - Collection oldPacks = tr.getRepository().getObjectDatabase() + Collection oldPacks = tr.getRepository().getObjectDatabase() .getPacks(); assertEquals(0, oldPacks.size()); stats = gc.getStatistics(); @@ -171,7 +171,7 @@ public void testNotPackTwice(boolean aggressive) throws Exception { stats = gc.getStatistics(); assertEquals(0, stats.numberOfLooseObjects); - List packs = new ArrayList<>( + List packs = new ArrayList<>( repo.getObjectDatabase().getPacks()); assertEquals(11, packs.get(0).getObjectCount()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java index bb8455f51..5cac1e342 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java @@ -156,8 +156,8 @@ public void repackAndUploadPack() throws Exception { } } - PackFile getSinglePack(FileRepository r) { - Collection packs = r.getObjectDatabase().getPacks(); + Pack getSinglePack(FileRepository r) { + Collection packs = r.getObjectDatabase().getPacks(); assertEquals(1, packs.size()); return packs.iterator().next(); } @@ -206,11 +206,11 @@ public void testInterruptGc() throws Exception { SampleDataRepositoryTestCase.copyCGitTestPacks(repo); ExecutorService executor = Executors.newSingleThreadExecutor(); final CountDownLatch latch = new CountDownLatch(1); - Future> result = executor.submit(() -> { + Future> result = executor.submit(() -> { long start = System.currentTimeMillis(); System.out.println("starting gc"); latch.countDown(); - Collection r = gc.gc(); + Collection r = gc.gc(); System.out.println( "gc took " + (System.currentTimeMillis() - start) + " ms"); return r; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java index e1559584f..8472983d5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java @@ -36,9 +36,9 @@ public void testKeepFiles() throws Exception { assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); - Iterator packIt = repo.getObjectDatabase().getPacks() + Iterator packIt = repo.getObjectDatabase().getPacks() .iterator(); - PackFile singlePack = packIt.next(); + Pack singlePack = packIt.next(); assertFalse(packIt.hasNext()); String packFileName = singlePack.getPackFile().getPath(); String keepFileName = packFileName.substring(0, @@ -58,7 +58,7 @@ public void testKeepFiles() throws Exception { assertEquals(2, stats.numberOfPackFiles); // check that no object is packed twice - Iterator packs = repo.getObjectDatabase().getPacks() + Iterator packs = repo.getObjectDatabase().getPacks() .iterator(); PackIndex ind1 = packs.next().getIndex(); assertEquals(4, ind1.getObjectCount()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java index 1f1e09438..7c32ce7ce 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileSnapshotTest.java @@ -72,14 +72,14 @@ public void testSamePackDifferentCompressionDetectChecksumChanged() c.setInt(ConfigConstants.CONFIG_GC_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT, 1); c.save(); - Collection packs = gc(Deflater.NO_COMPRESSION); + Collection packs = gc(Deflater.NO_COMPRESSION); assertEquals("expected 1 packfile after gc", 1, packs.size()); - PackFile p1 = packs.iterator().next(); + Pack p1 = packs.iterator().next(); PackFileSnapshot snapshot = p1.getFileSnapshot(); packs = gc(Deflater.BEST_COMPRESSION); assertEquals("expected 1 packfile after gc", 1, packs.size()); - PackFile p2 = packs.iterator().next(); + Pack p2 = packs.iterator().next(); File pf = p2.getPackFile(); // changing compression level with aggressive gc may change size, @@ -153,11 +153,11 @@ public void testDetectModificationAlthoughSameSizeAndModificationtime() createTestRepo(testDataSeed, testDataLength); // repack to create initial packfile - PackFile pf = repackAndCheck(5, null, null, null); - Path packFilePath = pf.getPackFile().toPath(); - AnyObjectId chk1 = pf.getPackChecksum(); - String name = pf.getPackName(); - Long length = Long.valueOf(pf.getPackFile().length()); + Pack p = repackAndCheck(5, null, null, null); + Path packFilePath = p.getPackFile().toPath(); + AnyObjectId chk1 = p.getPackChecksum(); + String name = p.getPackName(); + Long length = Long.valueOf(p.getPackFile().length()); FS fs = db.getFS(); Instant m1 = fs.lastModifiedInstant(packFilePath); @@ -207,16 +207,16 @@ public void testDetectModificationAlthoughSameSizeAndModificationtimeAndFileKey( createTestRepo(testDataSeed, testDataLength); // Repack to create initial packfile. Make a copy of it - PackFile pf = repackAndCheck(5, null, null, null); - Path packFilePath = pf.getPackFile().toPath(); + Pack p = repackAndCheck(5, null, null, null); + Path packFilePath = p.getPackFile().toPath(); Path fn = packFilePath.getFileName(); assertNotNull(fn); String packFileName = fn.toString(); Path packFileBasePath = packFilePath .resolveSibling(packFileName.replaceAll(".pack", "")); - AnyObjectId chk1 = pf.getPackChecksum(); - String name = pf.getPackName(); - Long length = Long.valueOf(pf.getPackFile().length()); + AnyObjectId chk1 = p.getPackChecksum(); + String name = p.getPackName(); + Long length = Long.valueOf(p.getPackFile().length()); copyPack(packFileBasePath, "", ".copy1"); // Repack to create second packfile. Make a copy of it @@ -280,10 +280,10 @@ private Path copyPack(Path base, String srcSuffix, String dstSuffix) Paths.get(base + ".pack" + dstSuffix)); } - private PackFile repackAndCheck(int compressionLevel, String oldName, + private Pack repackAndCheck(int compressionLevel, String oldName, Long oldLength, AnyObjectId oldChkSum) throws IOException, ParseException { - PackFile p = getSinglePack(gc(compressionLevel)); + Pack p = getSinglePack(gc(compressionLevel)); File pf = p.getPackFile(); // The following two assumptions should not cause the test to fail. If // on a certain platform we get packfiles (containing the same git @@ -298,14 +298,14 @@ private PackFile repackAndCheck(int compressionLevel, String oldName, return p; } - private PackFile getSinglePack(Collection packs) { - Iterator pIt = packs.iterator(); - PackFile p = pIt.next(); + private Pack getSinglePack(Collection packs) { + Iterator pIt = packs.iterator(); + Pack p = pIt.next(); assertFalse(pIt.hasNext()); return p; } - private Collection gc(int compressionLevel) + private Collection gc(int compressionLevel) throws IOException, ParseException { GC gc = new GC(db); PackConfig pc = new PackConfig(db.getConfig()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java index 8c56480fe..85043034a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackInserterTest.java @@ -160,7 +160,7 @@ public void singlePack() throws Exception { } assertPacksOnly(); - List packs = listPacks(); + List packs = listPacks(); assertEquals(1, packs.size()); assertEquals(3, packs.get(0).getObjectCount()); @@ -193,7 +193,7 @@ public void multiplePacks() throws Exception { } assertPacksOnly(); - List packs = listPacks(); + List packs = listPacks(); assertEquals(2, packs.size()); assertEquals(1, packs.get(0).getObjectCount()); assertEquals(1, packs.get(1).getObjectCount()); @@ -216,9 +216,9 @@ public void largeBlob() throws Exception { } assertPacksOnly(); - Collection packs = listPacks(); + Collection packs = listPacks(); assertEquals(1, packs.size()); - PackFile p = packs.iterator().next(); + Pack p = packs.iterator().next(); assertEquals(1, p.getObjectCount()); try (ObjectReader reader = db.newObjectReader()) { @@ -237,9 +237,9 @@ public void overwriteExistingPack() throws Exception { } assertPacksOnly(); - List packs = listPacks(); + List packs = listPacks(); assertEquals(1, packs.size()); - PackFile pack = packs.get(0); + Pack pack = packs.get(0); assertEquals(1, pack.getObjectCount()); String inode = getInode(pack.getPackFile()); @@ -372,7 +372,7 @@ public void readBackSmallFiles() throws Exception { } assertPacksOnly(); - List packs = listPacks(); + List packs = listPacks(); assertEquals(1, packs.size()); assertEquals(2, packs.get(0).getObjectCount()); @@ -489,16 +489,16 @@ public void readBackSmallObjectBeforeLargeObject() throws Exception { } } - private List listPacks() throws Exception { - List fromOpenDb = listPacks(db); - List reopened; + private List listPacks() throws Exception { + List fromOpenDb = listPacks(db); + List reopened; try (FileRepository db2 = new FileRepository(db.getDirectory())) { reopened = listPacks(db2); } assertEquals(fromOpenDb.size(), reopened.size()); for (int i = 0 ; i < fromOpenDb.size(); i++) { - PackFile a = fromOpenDb.get(i); - PackFile b = reopened.get(i); + Pack a = fromOpenDb.get(i); + Pack b = reopened.get(i); assertEquals(a.getPackName(), b.getPackName()); assertEquals( a.getPackFile().getAbsolutePath(), b.getPackFile().getAbsolutePath()); @@ -508,9 +508,9 @@ private List listPacks() throws Exception { return fromOpenDb; } - private static List listPacks(FileRepository db) throws Exception { + private static List listPacks(FileRepository db) throws Exception { return db.getObjectDatabase().getPacks().stream() - .sorted(comparing(PackFile::getPackName)).collect(toList()); + .sorted(comparing(Pack::getPackName)).collect(toList()); } private PackInserter newInserter() { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java similarity index 94% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java index 97a86e249..182e42265 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackFileTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackTest.java @@ -57,7 +57,7 @@ import org.junit.Before; import org.junit.Test; -public class PackFileTest extends LocalDiskRepositoryTestCase { +public class PackTest extends LocalDiskRepositoryTestCase { private int streamThreshold = 16 * 1024; private TestRng rng; @@ -228,21 +228,21 @@ public void testDelta_FailsOver2GiB() throws Exception { PackedObjectInfo a = new PackedObjectInfo(idA); PackedObjectInfo b = new PackedObjectInfo(idB); - TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(64 * 1024); - packHeader(pack, 2); - a.setOffset(pack.length()); - objectHeader(pack, Constants.OBJ_BLOB, base.length); - deflate(pack, base); + TemporaryBuffer.Heap packContents = new TemporaryBuffer.Heap(64 * 1024); + packHeader(packContents, 2); + a.setOffset(packContents.length()); + objectHeader(packContents, Constants.OBJ_BLOB, base.length); + deflate(packContents, base); ByteArrayOutputStream tmp = new ByteArrayOutputStream(); DeltaEncoder de = new DeltaEncoder(tmp, base.length, 3L << 30); de.copy(0, 1); byte[] delta = tmp.toByteArray(); - b.setOffset(pack.length()); - objectHeader(pack, Constants.OBJ_REF_DELTA, delta.length); - idA.copyRawTo(pack); - deflate(pack, delta); - byte[] footer = digest(pack); + b.setOffset(packContents.length()); + objectHeader(packContents, Constants.OBJ_REF_DELTA, delta.length); + idA.copyRawTo(packContents); + deflate(packContents, delta); + byte[] footer = digest(packContents); File dir = new File(repo.getObjectDatabase().getDirectory(), "pack"); @@ -250,7 +250,7 @@ public void testDelta_FailsOver2GiB() throws Exception { File idxName = new File(dir, idA.name() + ".idx"); try (FileOutputStream f = new FileOutputStream(packName)) { - f.write(pack.toByteArray()); + f.write(packContents.toByteArray()); } try (FileOutputStream f = new FileOutputStream(idxName)) { @@ -261,14 +261,14 @@ public void testDelta_FailsOver2GiB() throws Exception { new PackIndexWriterV1(f).write(list, footer); } - PackFile packFile = new PackFile(packName, PackExt.INDEX.getBit()); + Pack pack = new Pack(packName, PackExt.INDEX.getBit()); try { - packFile.get(wc, b); + pack.get(wc, b); fail("expected LargeObjectException.ExceedsByteArrayLimit"); } catch (LargeObjectException.ExceedsByteArrayLimit bad) { assertNull(bad.getObjectId()); } finally { - packFile.close(); + pack.close(); } } } 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/PackWriterTest.java index c90310e07..214ddb989 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/PackWriterTest.java @@ -72,7 +72,7 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { private ByteArrayOutputStream os; - private PackFile pack; + private Pack pack; private ObjectInserter inserter; @@ -840,7 +840,7 @@ private void verifyOpenPack(boolean thin) throws IOException { p.setAllowThin(thin); p.setIndexVersion(2); p.parse(NullProgressMonitor.INSTANCE); - pack = p.getPackFile(); + pack = p.getPack(); assertNotNull("have PackFile after parsing", pack); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java index ee4c9b1dc..8f1371e09 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java @@ -32,8 +32,8 @@ public void test003_lookupCompressedObject() throws IOException { final ObjectId id; final ObjectLoader or; - PackFile pr = null; - for (PackFile p : db.getObjectDatabase().getPacks()) { + Pack pr = null; + for (Pack p : db.getObjectDatabase().getPacks()) { if (PACK_NAME.equals(p.getPackName())) { pr = p; break; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java index 07c236dab..60b8098b3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PackParserTest.java @@ -29,7 +29,7 @@ import org.eclipse.jgit.errors.TooLargeObjectInPackException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.ObjectDirectoryPackParser; -import org.eclipse.jgit.internal.storage.file.PackFile; +import org.eclipse.jgit.internal.storage.file.Pack; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; @@ -63,16 +63,16 @@ public void test1() throws IOException { try (InputStream is = new FileInputStream(packFile)) { ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(is); p.parse(NullProgressMonitor.INSTANCE); - PackFile file = p.getPackFile(); + Pack pack = p.getPack(); - assertTrue(file.hasObject(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); - assertTrue(file.hasObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); - assertTrue(file.hasObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); - assertTrue(file.hasObject(ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); - assertTrue(file.hasObject(ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); - assertTrue(file.hasObject(ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); - assertTrue(file.hasObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); - assertTrue(file.hasObject(ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); + assertTrue(pack.hasObject(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); + assertTrue(pack.hasObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); + assertTrue(pack.hasObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); + assertTrue(pack.hasObject(ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); + assertTrue(pack.hasObject(ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + assertTrue(pack.hasObject(ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); + assertTrue(pack.hasObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); + assertTrue(pack.hasObject(ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); } } @@ -88,20 +88,20 @@ public void test2() throws IOException { try (InputStream is = new FileInputStream(packFile)) { ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(is); p.parse(NullProgressMonitor.INSTANCE); - PackFile file = p.getPackFile(); + Pack pack = p.getPack(); - assertTrue(file.hasObject(ObjectId.fromString("02ba32d3649e510002c21651936b7077aa75ffa9"))); - assertTrue(file.hasObject(ObjectId.fromString("0966a434eb1a025db6b71485ab63a3bfbea520b6"))); - assertTrue(file.hasObject(ObjectId.fromString("09efc7e59a839528ac7bda9fa020dc9101278680"))); - assertTrue(file.hasObject(ObjectId.fromString("0a3d7772488b6b106fb62813c4d6d627918d9181"))); - assertTrue(file.hasObject(ObjectId.fromString("1004d0d7ac26fbf63050a234c9b88a46075719d3"))); - assertTrue(file.hasObject(ObjectId.fromString("10da5895682013006950e7da534b705252b03be6"))); - assertTrue(file.hasObject(ObjectId.fromString("1203b03dc816ccbb67773f28b3c19318654b0bc8"))); - assertTrue(file.hasObject(ObjectId.fromString("15fae9e651043de0fd1deef588aa3fbf5a7a41c6"))); - assertTrue(file.hasObject(ObjectId.fromString("16f9ec009e5568c435f473ba3a1df732d49ce8c3"))); - assertTrue(file.hasObject(ObjectId.fromString("1fd7d579fb6ae3fe942dc09c2c783443d04cf21e"))); - assertTrue(file.hasObject(ObjectId.fromString("20a8ade77639491ea0bd667bf95de8abf3a434c8"))); - assertTrue(file.hasObject(ObjectId.fromString("2675188fd86978d5bc4d7211698b2118ae3bf658"))); + assertTrue(pack.hasObject(ObjectId.fromString("02ba32d3649e510002c21651936b7077aa75ffa9"))); + assertTrue(pack.hasObject(ObjectId.fromString("0966a434eb1a025db6b71485ab63a3bfbea520b6"))); + assertTrue(pack.hasObject(ObjectId.fromString("09efc7e59a839528ac7bda9fa020dc9101278680"))); + assertTrue(pack.hasObject(ObjectId.fromString("0a3d7772488b6b106fb62813c4d6d627918d9181"))); + assertTrue(pack.hasObject(ObjectId.fromString("1004d0d7ac26fbf63050a234c9b88a46075719d3"))); + assertTrue(pack.hasObject(ObjectId.fromString("10da5895682013006950e7da534b705252b03be6"))); + assertTrue(pack.hasObject(ObjectId.fromString("1203b03dc816ccbb67773f28b3c19318654b0bc8"))); + assertTrue(pack.hasObject(ObjectId.fromString("15fae9e651043de0fd1deef588aa3fbf5a7a41c6"))); + assertTrue(pack.hasObject(ObjectId.fromString("16f9ec009e5568c435f473ba3a1df732d49ce8c3"))); + assertTrue(pack.hasObject(ObjectId.fromString("1fd7d579fb6ae3fe942dc09c2c783443d04cf21e"))); + assertTrue(pack.hasObject(ObjectId.fromString("20a8ade77639491ea0bd667bf95de8abf3a434c8"))); + assertTrue(pack.hasObject(ObjectId.fromString("2675188fd86978d5bc4d7211698b2118ae3bf658"))); // and lots more... } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java index c3b1df992..a37b8bee2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoPackSignatureException.java @@ -13,8 +13,7 @@ import java.io.IOException; /** - * Thrown when a PackFile is found not to contain the pack signature defined by - * git. + * Thrown when a Pack is found not to contain the pack signature defined by git. * * @since 4.5 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java index c484984f8..1fd80867b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java @@ -17,7 +17,7 @@ import org.eclipse.jgit.internal.JGitText; /** - * Thrown when a PackFile previously failed and is known to be unusable + * Thrown when a Pack previously failed and is known to be unusable */ public class PackInvalidException extends IOException { private static final long serialVersionUID = 1L; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java index ad5664ceb..44b8e0193 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java @@ -13,7 +13,7 @@ import java.io.IOException; /** - * Thrown when a PackFile no longer matches the PackIndex. + * Thrown when a Pack no longer matches the PackIndex. */ public class PackMismatchException extends IOException { private static final long serialVersionUID = 1L; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java index 753822995..07aa7564d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/UnsupportedPackVersionException.java @@ -16,7 +16,7 @@ import org.eclipse.jgit.internal.JGitText; /** - * Thrown when a PackFile uses a pack version not supported by JGit. + * Thrown when a Pack uses a pack version not supported by JGit. * * @since 4.5 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java index 45d9c85c8..103653542 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java @@ -25,7 +25,7 @@ final class ByteArrayWindow extends ByteWindow { private final byte[] array; - ByteArrayWindow(PackFile pack, long o, byte[] b) { + ByteArrayWindow(Pack pack, long o, byte[] b) { super(pack, o, b.length); array = b; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java index 870321632..b6877578c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java @@ -27,7 +27,7 @@ final class ByteBufferWindow extends ByteWindow { private final ByteBuffer buffer; - ByteBufferWindow(PackFile pack, long o, ByteBuffer b) { + ByteBufferWindow(Pack pack, long o, ByteBuffer b) { super(pack, o, b.capacity()); buffer = b; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java index 159f31c97..31e7eadd8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java @@ -27,7 +27,7 @@ *

*/ abstract class ByteWindow { - protected final PackFile pack; + protected final Pack pack; protected final long start; @@ -37,13 +37,13 @@ abstract class ByteWindow { * Constructor for ByteWindow. * * @param p - * a {@link org.eclipse.jgit.internal.storage.file.PackFile}. + * a {@link org.eclipse.jgit.internal.storage.file.Pack}. * @param s * where the byte window starts in the pack file * @param n * size of the byte window */ - protected ByteWindow(PackFile p, long s, int n) { + protected ByteWindow(Pack p, long s, int n) { pack = p; start = s; end = start + n; @@ -53,8 +53,8 @@ final int size() { return (int) (end - start); } - final boolean contains(PackFile neededFile, long neededPos) { - return pack == neededFile && start <= neededPos && neededPos < end; + final boolean contains(Pack neededPack, long neededPos) { + return pack == neededPack && start <= neededPos && neededPos < end; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java index 9c7a2e711..7dedeb57a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java @@ -239,7 +239,7 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId objectId, } @Override - PackFile openPack(File pack) throws IOException { + Pack openPack(File pack) throws IOException { return wrapped.openPack(pack); } @@ -250,7 +250,7 @@ void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, } @Override - Collection getPacks() { + Collection getPacks() { return wrapped.getPacks(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java index cef5a330f..69cebadf1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java @@ -49,7 +49,7 @@ static void reconfigure(WindowCacheConfig cfg) { cache = new Slot[CACHE_SZ]; } - Entry get(PackFile pack, long position) { + Entry get(Pack pack, long position) { Slot e = cache[hash(position)]; if (e == null) return null; @@ -63,7 +63,7 @@ Entry get(PackFile pack, long position) { return null; } - void store(final PackFile pack, final long position, + void store(final Pack pack, final long position, final byte[] data, final int objectType) { if (data.length > maxByteCount) return; // Too large to cache. @@ -146,7 +146,7 @@ private static class Slot { Slot lruNext; - PackFile provider; + Pack provider; long position; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java index 11ed10c90..01dd27d9f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java @@ -71,7 +71,7 @@ abstract ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id) abstract InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id, boolean createDuplicate) throws IOException; - abstract PackFile openPack(File pack) throws IOException; + abstract Pack openPack(File pack) throws IOException; - abstract Collection getPacks(); + abstract Collection getPacks(); } 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 324075269..75de3be89 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 @@ -205,16 +205,16 @@ public GC(FileRepository repo) { * gc.log. * * @return the collection of - * {@link org.eclipse.jgit.internal.storage.file.PackFile}'s which + * {@link org.eclipse.jgit.internal.storage.file.Pack}'s which * are newly created * @throws java.io.IOException * @throws java.text.ParseException * If the configuration parameter "gc.pruneexpire" couldn't be * parsed */ - // TODO(ms): change signature and return Future> + // TODO(ms): change signature and return Future> @SuppressWarnings("FutureReturnValueIgnored") - public Collection gc() throws IOException, ParseException { + public Collection gc() throws IOException, ParseException { if (!background) { return doGc(); } @@ -224,9 +224,9 @@ public Collection gc() throws IOException, ParseException { return Collections.emptyList(); } - Callable> gcTask = () -> { + Callable> gcTask = () -> { try { - Collection newPacks = doGc(); + Collection newPacks = doGc(); if (automatic && tooManyLooseObjects()) { String message = JGitText.get().gcTooManyUnpruned; gcLog.write(message); @@ -258,14 +258,14 @@ private ExecutorService executor() { return (executor != null) ? executor : WorkQueue.getExecutor(); } - private Collection doGc() throws IOException, ParseException { + private Collection doGc() throws IOException, ParseException { if (automatic && !needGc()) { return Collections.emptyList(); } pm.start(6 /* tasks */); packRefs(); // TODO: implement reflog_expire(pm, repo); - Collection newPacks = repack(); + Collection newPacks = repack(); prune(Collections.emptySet()); // TODO: implement rerere_gc(pm); return newPacks; @@ -281,7 +281,7 @@ private Collection doGc() throws IOException, ParseException { * @param existing * @throws IOException */ - private void loosen(ObjectDirectoryInserter inserter, ObjectReader reader, PackFile pack, HashSet existing) + private void loosen(ObjectDirectoryInserter inserter, ObjectReader reader, Pack pack, HashSet existing) throws IOException { for (PackIndex.MutableEntry entry : pack) { ObjectId oid = entry.toObjectId(); @@ -313,10 +313,10 @@ private void loosen(ObjectDirectoryInserter inserter, ObjectReader reader, PackF * @throws ParseException * @throws IOException */ - private void deleteOldPacks(Collection oldPacks, - Collection newPacks) throws ParseException, IOException { + private void deleteOldPacks(Collection oldPacks, + Collection newPacks) throws ParseException, IOException { HashSet ids = new HashSet<>(); - for (PackFile pack : newPacks) { + for (Pack pack : newPacks) { for (PackIndex.MutableEntry entry : pack) { ids.add(entry.toObjectId()); } @@ -329,12 +329,12 @@ private void deleteOldPacks(Collection oldPacks, prunePreserved(); long packExpireDate = getPackExpireDate(); - oldPackLoop: for (PackFile oldPack : oldPacks) { + oldPackLoop: for (Pack oldPack : oldPacks) { checkCancelled(); String oldName = oldPack.getPackName(); // check whether an old pack file is also among the list of new // pack files. Then we must not delete it. - for (PackFile newPack : newPacks) + for (Pack newPack : newPacks) if (oldName.equals(newPack.getPackName())) continue oldPackLoop; @@ -438,7 +438,7 @@ private void prunePack(String packName) { */ public void prunePacked() throws IOException { ObjectDirectory objdb = repo.getObjectDatabase(); - Collection packs = objdb.getPacks(); + Collection packs = objdb.getPacks(); File objects = repo.getObjectsDirectory(); String[] fanout = objects.list(); @@ -466,7 +466,7 @@ public void prunePacked() throws IOException { continue; } boolean found = false; - for (PackFile p : packs) { + for (Pack p : packs) { checkCancelled(); if (p.hasObject(id)) { found = true; @@ -788,8 +788,8 @@ public void packRefs() throws IOException { * reflog-entries or during writing to the packfiles * {@link java.io.IOException} occurs */ - public Collection repack() throws IOException { - Collection toBeDeleted = repo.getObjectDatabase().getPacks(); + public Collection repack() throws IOException { + Collection toBeDeleted = repo.getObjectDatabase().getPacks(); long time = System.currentTimeMillis(); Collection refsBefore = getAllRefs(); @@ -821,10 +821,10 @@ public Collection repack() throws IOException { } List excluded = new LinkedList<>(); - for (PackFile f : repo.getObjectDatabase().getPacks()) { + for (Pack p : repo.getObjectDatabase().getPacks()) { checkCancelled(); - if (f.shouldBeKept()) - excluded.add(f.getIndex()); + if (p.shouldBeKept()) + excluded.add(p.getIndex()); } // Don't exclude tags that are also branch tips @@ -842,8 +842,8 @@ public Collection repack() throws IOException { nonHeads.clear(); } - List ret = new ArrayList<>(2); - PackFile heads = null; + List ret = new ArrayList<>(2); + Pack heads = null; if (!allHeadsAndTags.isEmpty()) { heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags, tagTargets, excluded); @@ -853,13 +853,13 @@ public Collection repack() throws IOException { } } if (!nonHeads.isEmpty()) { - PackFile rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE, + Pack rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE, tagTargets, excluded); if (rest != null) ret.add(rest); } if (!txnHeads.isEmpty()) { - PackFile txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE, + Pack txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE, null, excluded); if (txn != null) ret.add(txn); @@ -1129,7 +1129,7 @@ private Set listNonHEADIndexObjects() } } - private PackFile writePack(@NonNull Set want, + private Pack writePack(@NonNull Set want, @NonNull Set have, @NonNull Set tags, Set tagTargets, List excludeObjects) throws IOException { @@ -1356,13 +1356,13 @@ public String toString() { */ public RepoStatistics getStatistics() throws IOException { RepoStatistics ret = new RepoStatistics(); - Collection packs = repo.getObjectDatabase().getPacks(); - for (PackFile f : packs) { - ret.numberOfPackedObjects += f.getIndex().getObjectCount(); + Collection packs = repo.getObjectDatabase().getPacks(); + for (Pack p : packs) { + ret.numberOfPackedObjects += p.getIndex().getObjectCount(); ret.numberOfPackFiles++; - ret.sizeOfPackedObjects += f.getPackFile().length(); - if (f.getBitmapIndex() != null) - ret.numberOfBitmaps += f.getBitmapIndex().getBitmapCount(); + ret.sizeOfPackedObjects += p.getPackFile().length(); + if (p.getBitmapIndex() != null) + ret.numberOfBitmaps += p.getBitmapIndex().getBitmapCount(); } File objDir = repo.getObjectsDirectory(); String[] fanout = objDir.list(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java index ee4bbc196..e2fbd7a0b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java @@ -30,12 +30,12 @@ class LargePackedWholeObject extends ObjectLoader { private final int headerLength; - private final PackFile pack; + private final Pack pack; private final FileObjectDatabase db; LargePackedWholeObject(int type, long size, long objectOffset, - int headerLength, PackFile pack, FileObjectDatabase db) { + int headerLength, Pack pack, FileObjectDatabase db) { this.type = type; this.size = size; this.objectOffset = objectOffset; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java index 9d04062e3..ae5bce698 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java @@ -25,31 +25,31 @@ class LocalCachedPack extends CachedPack { private final String[] packNames; - private PackFile[] packs; + private Pack[] packs; LocalCachedPack(ObjectDirectory odb, List packNames) { this.odb = odb; this.packNames = packNames.toArray(new String[0]); } - LocalCachedPack(List packs) { + LocalCachedPack(List packs) { odb = null; packNames = null; - this.packs = packs.toArray(new PackFile[0]); + this.packs = packs.toArray(new Pack[0]); } /** {@inheritDoc} */ @Override public long getObjectCount() throws IOException { long cnt = 0; - for (PackFile pack : getPacks()) + for (Pack pack : getPacks()) cnt += pack.getObjectCount(); return cnt; } void copyAsIs(PackOutputStream out, WindowCursor wc) throws IOException { - for (PackFile pack : getPacks()) + for (Pack pack : getPacks()) pack.copyPackAsIs(out, wc); } @@ -58,7 +58,7 @@ void copyAsIs(PackOutputStream out, WindowCursor wc) public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) { try { LocalObjectRepresentation local = (LocalObjectRepresentation) rep; - for (PackFile pack : getPacks()) { + for (Pack pack : getPacks()) { if (local.pack == pack) return true; } @@ -68,9 +68,9 @@ public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) { } } - private PackFile[] getPacks() throws FileNotFoundException { + private Pack[] getPacks() throws FileNotFoundException { if (packs == null) { - PackFile[] p = new PackFile[packNames.length]; + Pack[] p = new Pack[packNames.length]; for (int i = 0; i < packNames.length; i++) p[i] = getPackFile(packNames[i]); packs = p; @@ -78,8 +78,8 @@ private PackFile[] getPacks() throws FileNotFoundException { return packs; } - private PackFile getPackFile(String packName) throws FileNotFoundException { - for (PackFile pack : odb.getPacks()) { + private Pack getPackFile(String packName) throws FileNotFoundException { + for (Pack pack : odb.getPacks()) { if (packName.equals(pack.getPackName())) return pack; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java index 3950dde4a..559718af3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java @@ -16,40 +16,40 @@ import org.eclipse.jgit.lib.ObjectId; class LocalObjectRepresentation extends StoredObjectRepresentation { - static LocalObjectRepresentation newWhole(PackFile f, long p, long length) { + static LocalObjectRepresentation newWhole(Pack pack, long offset, long length) { LocalObjectRepresentation r = new LocalObjectRepresentation() { @Override public int getFormat() { return PACK_WHOLE; } }; - r.pack = f; - r.offset = p; + r.pack = pack; + r.offset = offset; r.length = length; return r; } - static LocalObjectRepresentation newDelta(PackFile f, long p, long n, + static LocalObjectRepresentation newDelta(Pack pack, long offset, long length, ObjectId base) { LocalObjectRepresentation r = new Delta(); - r.pack = f; - r.offset = p; - r.length = n; + r.pack = pack; + r.offset = offset; + r.length = length; r.baseId = base; return r; } - static LocalObjectRepresentation newDelta(PackFile f, long p, long n, + static LocalObjectRepresentation newDelta(Pack pack, long offset, long length, long base) { LocalObjectRepresentation r = new Delta(); - r.pack = f; - r.offset = p; - r.length = n; + r.pack = pack; + r.offset = offset; + r.length = length; r.baseOffset = base; return r; } - PackFile pack; + Pack pack; long offset; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java index 4a0ac1fd8..ac6cd212d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java @@ -17,7 +17,7 @@ /** {@link ObjectToPack} for {@link ObjectDirectory}. */ class LocalObjectToPack extends ObjectToPack { /** Pack to reuse compressed data from, otherwise null. */ - PackFile pack; + Pack pack; /** Offset of the object's header in {@link #pack}. */ long offset; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java index 4a40db68d..e71a96060 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java @@ -51,7 +51,7 @@ * This is the classical object database representation for a Git repository, * where objects are stored loose by hashing them into directories by their * {@link org.eclipse.jgit.lib.ObjectId}, or are stored in compressed containers - * known as {@link org.eclipse.jgit.internal.storage.file.PackFile}s. + * known as {@link org.eclipse.jgit.internal.storage.file.Pack}s. *

* Optionally an object database can reference one or more alternates; other * ObjectDatabase instances that are searched in addition to the current @@ -206,7 +206,7 @@ public void close() { /** {@inheritDoc} */ @Override - public Collection getPacks() { + public Collection getPacks() { return packed.getPacks(); } @@ -216,7 +216,7 @@ public Collection getPacks() { * Add a single existing pack to the list of available pack files. */ @Override - public PackFile openPack(File pack) + public Pack openPack(File pack) throws IOException { final String p = pack.getName(); if (p.length() != 50 || !p.startsWith("pack-") || !p.endsWith(".pack")) //$NON-NLS-1$ //$NON-NLS-2$ @@ -235,7 +235,7 @@ public PackFile openPack(File pack) } } - PackFile res = new PackFile(pack, extensions); + Pack res = new Pack(pack, extensions); packed.insert(res); return res; } @@ -509,7 +509,7 @@ void closeAllPackHandles(File packFile) { // PackConfig) then make sure we get rid of all handles on the file. // Windows will not allow for rename otherwise. if (packFile.exists()) { - for (PackFile p : packed.getPacks()) { + for (Pack p : packed.getPacks()) { if (packFile.getPath().equals(p.getPackFile().getPath())) { p.close(); break; 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 e27518690..04d2ff8ab 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 @@ -88,7 +88,7 @@ public class ObjectDirectoryPackParser extends PackParser { private Deflater def; /** The pack that was created, if parsing was successful. */ - private PackFile newPack; + private Pack newPack; private PackConfig pconfig; @@ -129,14 +129,14 @@ public void setKeepEmpty(boolean empty) { } /** - * Get the imported {@link org.eclipse.jgit.internal.storage.file.PackFile}. + * Get the imported {@link org.eclipse.jgit.internal.storage.file.Pack}. *

* This method is supplied only to support testing; applications shouldn't * be using it directly to access the imported data. * * @return the imported PackFile, if parsing was successful. */ - public PackFile getPackFile() { + public Pack getPack() { return newPack; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java similarity index 99% rename from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java rename to org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java index e112fe744..d928633a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java @@ -69,13 +69,13 @@ * delta packed format yielding high compression of lots of object where some * objects are similar. */ -public class PackFile implements Iterable { - private static final Logger LOG = LoggerFactory.getLogger(PackFile.class); +public class Pack implements Iterable { + private static final Logger LOG = LoggerFactory.getLogger(Pack.class); /** * Sorts PackFiles to be most recently created to least recently created. */ - public static final Comparator SORT = (a, b) -> b.packLastModified + public static final Comparator SORT = (a, b) -> b.packLastModified .compareTo(a.packLastModified); private final File packFile; @@ -136,7 +136,7 @@ public class PackFile implements Iterable { * @param extensions * additional pack file extensions with the same base as the pack */ - public PackFile(File packFile, int extensions) { + public Pack(File packFile, int extensions) { this.packFile = packFile; this.fileSnapshot = PackFileSnapshot.save(packFile); this.packLastModified = fileSnapshot.lastModifiedInstant(); @@ -1201,7 +1201,7 @@ private boolean hasExt(PackExt ext) { @SuppressWarnings("nls") @Override public String toString() { - return "PackFile [packFileName=" + packFile.getName() + ", length=" + return "Pack [packFileName=" + packFile.getName() + ", length=" + packFile.length() + ", packChecksum=" + ObjectId.fromRaw(packChecksum).name() + "]"; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java index fd9da7c6c..b2ba36bf9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java @@ -47,16 +47,17 @@ /** * Traditional file system packed objects directory handler. *

- * This is the {@code PackFile}s object representation for a Git object - * database, where objects are stored in compressed containers known as - * {@link org.eclipse.jgit.internal.storage.file.PackFile}s. + * This is the {@link org.eclipse.jgit.internal.storage.file.Pack}s object + * representation for a Git object database, where objects are stored in + * compressed containers known as + * {@link org.eclipse.jgit.internal.storage.file.Pack}s. */ class PackDirectory { private final static Logger LOG = LoggerFactory .getLogger(PackDirectory.class); private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY, - new PackFile[0]); + new Pack[0]); private final Config config; @@ -94,18 +95,18 @@ void create() throws IOException { void close() { PackList packs = packList.get(); if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) { - for (PackFile p : packs.packs) { + for (Pack p : packs.packs) { p.close(); } } } - Collection getPacks() { + Collection getPacks() { PackList list = packList.get(); if (list == NO_PACKS) { list = scanPacks(list); } - PackFile[] packs = list.packs; + Pack[] packs = list.packs; return Collections.unmodifiableCollection(Arrays.asList(packs)); } @@ -126,7 +127,7 @@ boolean has(AnyObjectId objectId) { PackList pList; do { pList = packList.get(); - for (PackFile p : pList.packs) { + for (Pack p : pList.packs) { try { if (p.hasObject(objectId)) { return true; @@ -167,7 +168,7 @@ boolean resolve(Set matches, AbbreviatedObjectId id, PackList pList; do { pList = packList.get(); - for (PackFile p : pList.packs) { + for (Pack p : pList.packs) { try { p.resolve(matches, id, matchLimit); p.resetTransientErrorCount(); @@ -187,7 +188,7 @@ ObjectLoader open(WindowCursor curs, AnyObjectId objectId) { do { SEARCH: for (;;) { pList = packList.get(); - for (PackFile p : pList.packs) { + for (Pack p : pList.packs) { try { ObjectLoader ldr = p.get(curs, objectId); p.resetTransientErrorCount(); @@ -213,7 +214,7 @@ long getSize(WindowCursor curs, AnyObjectId id) { do { SEARCH: for (;;) { pList = packList.get(); - for (PackFile p : pList.packs) { + for (Pack p : pList.packs) { try { long len = p.getObjectSize(curs, id); p.resetTransientErrorCount(); @@ -239,7 +240,7 @@ void selectRepresentation(PackWriter packer, ObjectToPack otp, WindowCursor curs) { PackList pList = packList.get(); SEARCH: for (;;) { - for (PackFile p : pList.packs) { + for (Pack p : pList.packs) { try { LocalObjectRepresentation rep = p.representation(curs, otp); p.resetTransientErrorCount(); @@ -259,7 +260,7 @@ void selectRepresentation(PackWriter packer, ObjectToPack otp, } } - private void handlePackError(IOException e, PackFile p) { + private void handlePackError(IOException e, Pack p) { String warnTmpl = null; int transientErrorCount = 0; String errTmpl = JGitText.get().exceptionWhileReadingPack; @@ -322,7 +323,7 @@ boolean searchPacksAgain(PackList old) { && old != scanPacks(old); } - void insert(PackFile pf) { + void insert(Pack pack) { PackList o, n; do { o = packList.get(); @@ -331,33 +332,33 @@ void insert(PackFile pf) { // (picked up by a concurrent thread that did a scan?) we // do not want to insert it a second time. // - final PackFile[] oldList = o.packs; - final String name = pf.getPackFile().getName(); - for (PackFile p : oldList) { + final Pack[] oldList = o.packs; + final String name = pack.getPackFile().getName(); + for (Pack p : oldList) { if (name.equals(p.getPackFile().getName())) { return; } } - final PackFile[] newList = new PackFile[1 + oldList.length]; - newList[0] = pf; + final Pack[] newList = new Pack[1 + oldList.length]; + newList[0] = pack; System.arraycopy(oldList, 0, newList, 1, oldList.length); n = new PackList(o.snapshot, newList); } while (!packList.compareAndSet(o, n)); } - private void remove(PackFile deadPack) { + private void remove(Pack deadPack) { PackList o, n; do { o = packList.get(); - final PackFile[] oldList = o.packs; + final Pack[] oldList = o.packs; final int j = indexOf(oldList, deadPack); if (j < 0) { break; } - final PackFile[] newList = new PackFile[oldList.length - 1]; + final Pack[] newList = new Pack[oldList.length - 1]; System.arraycopy(oldList, 0, newList, 0, j); System.arraycopy(oldList, j + 1, newList, j, newList.length - j); n = new PackList(o.snapshot, newList); @@ -365,7 +366,7 @@ private void remove(PackFile deadPack) { deadPack.close(); } - private static int indexOf(PackFile[] list, PackFile pack) { + private static int indexOf(Pack[] list, Pack pack) { for (int i = 0; i < list.length; i++) { if (list[i] == pack) { return i; @@ -395,10 +396,10 @@ private PackList scanPacks(PackList original) { } private PackList scanPacksImpl(PackList old) { - final Map forReuse = reuseMap(old); + final Map forReuse = reuseMap(old); final FileSnapshot snapshot = FileSnapshot.save(directory); final Set names = listPackDirectory(); - final List list = new ArrayList<>(names.size() >> 2); + final List list = new ArrayList<>(names.size() >> 2); boolean foundNew = false; for (String indexName : names) { // Must match "pack-[0-9a-f]{40}.idx" to be an index. @@ -425,7 +426,7 @@ private PackList scanPacksImpl(PackList old) { final String packName = base + PACK.getExtension(); final File packFile = new File(directory, packName); - final PackFile oldPack = forReuse.get(packName); + final Pack oldPack = forReuse.get(packName); if (oldPack != null && !oldPack.getFileSnapshot().isModified(packFile)) { forReuse.remove(packName); @@ -433,7 +434,7 @@ private PackList scanPacksImpl(PackList old) { continue; } - list.add(new PackFile(packFile, extensions)); + list.add(new Pack(packFile, extensions)); foundNew = true; } @@ -447,7 +448,7 @@ private PackList scanPacksImpl(PackList old) { return old; } - for (PackFile p : forReuse.values()) { + for (Pack p : forReuse.values()) { p.close(); } @@ -455,14 +456,14 @@ private PackList scanPacksImpl(PackList old) { return new PackList(snapshot, NO_PACKS.packs); } - final PackFile[] r = list.toArray(new PackFile[0]); - Arrays.sort(r, PackFile.SORT); + final Pack[] r = list.toArray(new Pack[0]); + Arrays.sort(r, Pack.SORT); return new PackList(snapshot, r); } - private static Map reuseMap(PackList old) { - final Map forReuse = new HashMap<>(); - for (PackFile p : old.packs) { + private static Map reuseMap(PackList old) { + final Map forReuse = new HashMap<>(); + for (Pack p : old.packs) { if (p.invalid()) { // The pack instance is corrupted, and cannot be safely used // again. Do not include it in our reuse map. @@ -471,7 +472,7 @@ private static Map reuseMap(PackList old) { continue; } - final PackFile prior = forReuse.put(p.getPackFile().getName(), p); + final Pack prior = forReuse.put(p.getPackFile().getName(), p); if (prior != null) { // This should never occur. It should be impossible for us // to have two pack files with the same name, as all of them @@ -504,10 +505,10 @@ static final class PackList { /** State just before reading the pack directory. */ final FileSnapshot snapshot; - /** All known packs, sorted by {@link PackFile#SORT}. */ - final PackFile[] packs; + /** All known packs, sorted by {@link Pack#SORT}. */ + final Pack[] packs; - PackList(FileSnapshot monitor, PackFile[] packs) { + PackList(FileSnapshot monitor, Pack[] packs) { this.snapshot = monitor; this.packs = packs; } 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 31686befc..942cc9674 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 @@ -34,7 +34,7 @@ /** * Access path to locate objects by {@link org.eclipse.jgit.lib.ObjectId} in a - * {@link org.eclipse.jgit.internal.storage.file.PackFile}. + * {@link org.eclipse.jgit.internal.storage.file.Pack}. *

* Indexes are strictly redundant information in that we can rebuild all of the * data held in the index file from the on disk representation of the pack file 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/PackIndexWriter.java index 612b12366..87e0b44d4 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/PackIndexWriter.java @@ -25,7 +25,7 @@ /** * Creates a table of contents to support random access by - * {@link org.eclipse.jgit.internal.storage.file.PackFile}. + * {@link org.eclipse.jgit.internal.storage.file.Pack}. *

* Pack index files (the .idx suffix in a pack file pair) provides * random access to any object in the pack by associating an ObjectId to the diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java index a9e058888..0bceca72e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java @@ -16,11 +16,11 @@ class PackInputStream extends InputStream { private final WindowCursor wc; - private final PackFile pack; + private final Pack pack; private long pos; - PackInputStream(PackFile pack, long pos, WindowCursor wc) + PackInputStream(Pack pack, long pos, WindowCursor wc) throws IOException { this.pack = pack; this.pos = pos; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java index 2c2f7911a..482b143e3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackLock.java @@ -18,7 +18,7 @@ import org.eclipse.jgit.util.FileUtils; /** - * Keeps track of a {@link org.eclipse.jgit.internal.storage.file.PackFile}'s + * Keeps track of a {@link org.eclipse.jgit.internal.storage.file.Pack}'s * associated .keep file. */ public class PackLock { 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 4d80a0312..ee458e27b 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 @@ -25,7 +25,7 @@ *

* * @see PackIndex - * @see PackFile + * @see Pack */ public class PackReverseIndex { /** Index we were created from, and that has our ObjectId data. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java index 3e8cb3a3f..25653b3ce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java @@ -33,7 +33,7 @@ import org.eclipse.jgit.util.Monitoring; /** - * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.PackFile} in + * Caches slices of a {@link org.eclipse.jgit.internal.storage.file.Pack} in * memory for faster read access. *

* The WindowCache serves as a Java based "buffer cache", loading segments of a @@ -41,7 +41,7 @@ * only tiny slices of a file, the WindowCache tries to smooth out these tiny * reads into larger block-sized IO operations. *

- * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by + * Whenever a cache miss occurs, {@link #load(Pack, long)} is invoked by * exactly one thread for the given (PackFile,position) key tuple. * This is ensured by an array of locks, with the tuple hashed to a lock * instance. @@ -80,10 +80,10 @@ *

* This cache has an implementation rule such that: *

    - *
  • {@link #load(PackFile, long)} is invoked by at most one thread at a time + *
  • {@link #load(Pack, long)} is invoked by at most one thread at a time * for a given (PackFile,position) tuple.
  • *
  • For every load() invocation there is exactly one - * {@link #createRef(PackFile, long, ByteWindow)} invocation to wrap a + * {@link #createRef(Pack, long, ByteWindow)} invocation to wrap a * SoftReference or a StrongReference around the cached entity.
  • *
  • For every Reference created by createRef() there will be * exactly one call to {@link #clear(PageRef)} to cleanup any resources associated @@ -91,10 +91,10 @@ *
*

* Therefore, it is safe to perform resource accounting increments during the - * {@link #load(PackFile, long)} or - * {@link #createRef(PackFile, long, ByteWindow)} methods, and matching + * {@link #load(Pack, long)} or + * {@link #createRef(Pack, long, ByteWindow)} methods, and matching * decrements during {@link #clear(PageRef)}. Implementors may need to override - * {@link #createRef(PackFile, long, ByteWindow)} in order to embed additional + * {@link #createRef(Pack, long, ByteWindow)} in order to embed additional * accounting information into an implementation specific * {@link org.eclipse.jgit.internal.storage.file.WindowCache.PageRef} subclass, as * the cached entity may have already been evicted by the JRE's garbage @@ -170,7 +170,7 @@ static interface StatsRecorder { * @param delta * delta of cached bytes */ - void recordOpenBytes(PackFile pack, int delta); + void recordOpenBytes(Pack pack, int delta); /** * Returns a snapshot of this recorder's stats. Note that this may be an @@ -242,7 +242,7 @@ public void recordOpenFiles(int delta) { } @Override - public void recordOpenBytes(PackFile pack, int delta) { + public void recordOpenBytes(Pack pack, int delta) { openByteCount.add(delta); String repositoryId = repositoryId(pack); LongAdder la = openByteCountPerRepository @@ -254,9 +254,8 @@ public void recordOpenBytes(PackFile pack, int delta) { } } - private static String repositoryId(PackFile pack) { - // use repository's gitdir since packfile doesn't know its - // repository + private static String repositoryId(Pack pack) { + // use repository's gitdir since Pack doesn't know its repository return pack.getPackFile().getParentFile().getParentFile() .getParent(); } @@ -380,7 +379,7 @@ public static WindowCache getInstance() { return cache.publishMBeanIfNeeded(); } - static final ByteWindow get(PackFile pack, long offset) + static final ByteWindow get(Pack pack, long offset) throws IOException { final WindowCache c = cache; final ByteWindow r = c.getOrLoad(pack, c.toStart(offset)); @@ -395,7 +394,7 @@ static final ByteWindow get(PackFile pack, long offset) return r; } - static final void purge(PackFile pack) { + static final void purge(Pack pack) { cache.removeAll(pack); } @@ -506,7 +505,7 @@ private int hash(int packHash, long off) { return packHash + (int) (off >>> windowSizeShift); } - private ByteWindow load(PackFile pack, long offset) throws IOException { + private ByteWindow load(Pack pack, long offset) throws IOException { long startTime = System.nanoTime(); if (pack.beginWindowCache()) statsRecorder.recordOpenFiles(1); @@ -525,7 +524,7 @@ private ByteWindow load(PackFile pack, long offset) throws IOException { } } - private PageRef createRef(PackFile p, long o, ByteWindow v) { + private PageRef createRef(Pack p, long o, ByteWindow v) { final PageRef ref = useStrongRefs ? new StrongRef(p, o, v, queue) : new SoftRef(p, o, v, (SoftCleanupQueue) queue); @@ -539,7 +538,7 @@ private void clear(PageRef ref) { close(ref.getPack()); } - private void close(PackFile pack) { + private void close(Pack pack) { if (pack.endWindowCache()) { statsRecorder.recordOpenFiles(-1); } @@ -578,9 +577,9 @@ private static int lockCount(WindowCacheConfig cfg) { * @return the object reference. * @throws IOException * the object reference was not in the cache and could not be - * obtained by {@link #load(PackFile, long)}. + * obtained by {@link #load(Pack, long)}. */ - private ByteWindow getOrLoad(PackFile pack, long position) + private ByteWindow getOrLoad(Pack pack, long position) throws IOException { final int slot = slot(pack, position); final Entry e1 = table.get(slot); @@ -623,7 +622,7 @@ private ByteWindow getOrLoad(PackFile pack, long position) return v; } - private ByteWindow scan(Entry n, PackFile pack, long position) { + private ByteWindow scan(Entry n, Pack pack, long position) { for (; n != null; n = n.next) { final PageRef r = n.ref; if (r.getPack() == pack && r.getPosition() == position) { @@ -704,7 +703,7 @@ private void removeAll() { /** * Clear all entries related to a single file. *

- * Typically this method is invoked during {@link PackFile#close()}, when we + * Typically this method is invoked during {@link Pack#close()}, when we * know the pack is never going to be useful to us again (for example, it no * longer exists on disk). A concurrent reader loading an entry from this * same pack may cause the pack to become stuck in the cache anyway. @@ -712,7 +711,7 @@ private void removeAll() { * @param pack * the file to purge all entries of. */ - private void removeAll(PackFile pack) { + private void removeAll(Pack pack) { for (int s = 0; s < tableSize; s++) { final Entry e1 = table.get(s); boolean hasDead = false; @@ -733,11 +732,11 @@ private void gc() { queue.gc(); } - private int slot(PackFile pack, long position) { + private int slot(Pack pack, long position) { return (hash(pack.hash, position) >>> 1) % tableSize; } - private Lock lock(PackFile pack, long position) { + private Lock lock(Pack pack, long position) { return locks[(hash(pack.hash, position) >>> 1) % locks.length]; } @@ -799,16 +798,20 @@ private static interface PageRef { boolean kill(); /** - * Get the packfile the referenced cache page is allocated for + * Get the {@link org.eclipse.jgit.internal.storage.file.Pack} the + * referenced cache page is allocated for * - * @return the packfile the referenced cache page is allocated for + * @return the {@link org.eclipse.jgit.internal.storage.file.Pack} the + * referenced cache page is allocated for */ - PackFile getPack(); + Pack getPack(); /** - * Get the position of the referenced cache page in the packfile + * Get the position of the referenced cache page in the + * {@link org.eclipse.jgit.internal.storage.file.Pack} * - * @return the position of the referenced cache page in the packfile + * @return the position of the referenced cache page in the + * {@link org.eclipse.jgit.internal.storage.file.Pack} */ long getPosition(); @@ -844,7 +847,7 @@ private static interface PageRef { /** A soft reference wrapped around a cached object. */ private static class SoftRef extends SoftReference implements PageRef { - private final PackFile pack; + private final Pack pack; private final long position; @@ -852,7 +855,7 @@ private static class SoftRef extends SoftReference private long lastAccess; - protected SoftRef(final PackFile pack, final long position, + protected SoftRef(final Pack pack, final long position, final ByteWindow v, final SoftCleanupQueue queue) { super(v, queue); this.pack = pack; @@ -861,7 +864,7 @@ protected SoftRef(final PackFile pack, final long position, } @Override - public PackFile getPack() { + public Pack getPack() { return pack; } @@ -900,7 +903,7 @@ public boolean isStrongRef() { private static class StrongRef implements PageRef { private ByteWindow referent; - private final PackFile pack; + private final Pack pack; private final long position; @@ -910,7 +913,7 @@ private static class StrongRef implements PageRef { private CleanupQueue queue; - protected StrongRef(final PackFile pack, final long position, + protected StrongRef(final Pack pack, final long position, final ByteWindow v, final CleanupQueue queue) { this.pack = pack; this.position = position; @@ -920,7 +923,7 @@ protected StrongRef(final PackFile pack, final long position, } @Override - public PackFile getPack() { + public Pack getPack() { return pack; } 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 6c975708a..e7fd7b9e7 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 @@ -86,7 +86,7 @@ public ObjectReader newReader() { /** {@inheritDoc} */ @Override public BitmapIndex getBitmapIndex() throws IOException { - for (PackFile pack : db.getPacks()) { + for (Pack pack : db.getPacks()) { PackBitmapIndex index = pack.getBitmapIndex(); if (index != null) return new BitmapIndexImpl(index); @@ -98,7 +98,7 @@ public BitmapIndex getBitmapIndex() throws IOException { @Override public Collection getCachedPacksAndUpdate( BitmapBuilder needBitmap) throws IOException { - for (PackFile pack : db.getPacks()) { + for (Pack pack : db.getPacks()) { PackBitmapIndex index = pack.getBitmapIndex(); if (needBitmap.removeAllOrNone(index)) return Collections. singletonList( @@ -218,7 +218,7 @@ public void writeObjects(PackOutputStream out, List list) * this cursor does not match the provider or id and the proper * window could not be acquired through the provider's cache. */ - int copy(final PackFile pack, long position, final byte[] dstbuf, + int copy(final Pack pack, long position, final byte[] dstbuf, int dstoff, final int cnt) throws IOException { final long length = pack.length; int need = cnt; @@ -239,7 +239,7 @@ public void copyPackAsIs(PackOutputStream out, CachedPack pack) ((LocalCachedPack) pack).copyAsIs(out, this); } - void copyPackAsIs(final PackFile pack, final long length, + void copyPackAsIs(final Pack pack, final long length, final PackOutputStream out) throws IOException { long position = 12; long remaining = length - (12 + 20); @@ -275,7 +275,7 @@ void copyPackAsIs(final PackFile pack, final long length, * the inflater encountered an invalid chunk of data. Data * stream corruption is likely. */ - int inflate(final PackFile pack, long position, final byte[] dstbuf, + int inflate(final Pack pack, long position, final byte[] dstbuf, boolean headerOnly) throws IOException, DataFormatException { prepareInflater(); pin(pack, position); @@ -293,7 +293,7 @@ int inflate(final PackFile pack, long position, final byte[] dstbuf, } } - ByteArrayWindow quickCopy(PackFile p, long pos, long cnt) + ByteArrayWindow quickCopy(Pack p, long pos, long cnt) throws IOException { pin(p, pos); if (window instanceof ByteArrayWindow @@ -314,7 +314,7 @@ private void prepareInflater() { inf.reset(); } - void pin(PackFile pack, long position) + void pin(Pack pack, long position) throws IOException { final ByteWindow w = window; if (w == null || !w.contains(pack, position)) { From 7cc1a52c66077e8b46c10f77ebc8219333a56858 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sat, 13 Feb 2021 21:56:03 +0100 Subject: [PATCH 16/35] Update jetty to 9.4.36.v20210114 Change-Id: Iea57f0fddb0f10dbd1c9be886bfa5ad8c3ff5cb5 Signed-off-by: Matthias Sohn --- WORKSPACE | 30 +++++++------- .../org.eclipse.jgit.target/jgit-4.10.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.11.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.12.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.13.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.14.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.15.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.16.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.17.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.18.target | 40 +++++++++---------- .../jgit-4.19-staging.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.6.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.7.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.8.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.9.target | 40 +++++++++---------- .../projects/jetty-9.4.x.tpd | 38 +++++++++--------- pom.xml | 2 +- 17 files changed, 315 insertions(+), 315 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 5ce9dd7d0..224968a26 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -237,55 +237,55 @@ maven_jar( sha1 = "9180733b7df8542621dc12e21e87557e8c99b8cb", ) -JETTY_VER = "9.4.35.v20201120" +JETTY_VER = "9.4.36.v20210114" maven_jar( name = "jetty-servlet", artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER, - sha1 = "3e61bcb471e1bfc545ce866cbbe33c3aedeec9b1", - src_sha1 = "e237af9dd6556756736fcdc7da00194fa00d3c2b", + sha1 = "b189e52a5ee55ae172e4e99e29c5c314f5daf4b9", + src_sha1 = "3a0fa449772ab0d76625f6afb81f60c32a490613", ) maven_jar( name = "jetty-security", artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER, - sha1 = "80dc2f422789c78315de76d289b7a5b36c3232d5", - src_sha1 = "3c0d03191fdffd9cf1e46fa206d79eeb9e3adb90", + sha1 = "42030d6ed7dfc0f75818cde0adcf738efc477574", + src_sha1 = "612220a97d45fad3983ccc56b0cb9a271f3fd003", ) maven_jar( name = "jetty-server", artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER, - sha1 = "513502352fd689d4730b2935421b990ada8cc818", - src_sha1 = "760d3574ebc7f9b9b1ba51242b9c1dda6fe5505b", + sha1 = "88a7d342974aadca658e7386e8d0fcc5c0788f41", + src_sha1 = "4552c0c6db2948e8557db477b6b48d291006e481", ) maven_jar( name = "jetty-http", artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER, - sha1 = "45d35131a35a1e76991682174421e8cdf765fb9f", - src_sha1 = "491cd5b8abe8fe6f3db0086907a3f12440188e73", + sha1 = "1eee89a55e04ff94df0f85d95200fc48acb43d86", + src_sha1 = "552a784ec789c7ba581c5341ae6d8b6353ed5ace", ) maven_jar( name = "jetty-io", artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER, - sha1 = "eb9460700b99b71ecd82a53697f5ff99f69b9e1c", - src_sha1 = "fed96a4559a49e7173dbe2d9d8e100d72aee2a10", + sha1 = "84a8faf9031eb45a5a2ddb7681e22c483d81ab3a", + src_sha1 = "72d5fc6d909e28f8720394b25babda80805a46b9", ) maven_jar( name = "jetty-util", artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER, - sha1 = "ef61b83f9715c3b5355b633d9f01d2834f908ece", - src_sha1 = "8f178bebeb34c8365a06d854daa9b22da1b301d7", + sha1 = "925257fbcca6b501a25252c7447dbedb021f7404", + src_sha1 = "532e8b66044f4e58ca5da3aec19f02a2f3c16ddd", ) maven_jar( name = "jetty-util-ajax", artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER, - sha1 = "ebbb43912c6423bedb3458e44aee28eeb4d66f27", - src_sha1 = "b3acea974a17493afb125a9dfbe783870ce1d2f9", + sha1 = "2f478130c21787073facb64d7242e06f94980c60", + src_sha1 = "7153d7ca38878d971fd90992c303bb7719ba7a21", ) BOUNCYCASTLE_VER = "1.65" diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target index 73c1b34df..016d250bf 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target index 6b7896283..1153b2964 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target index aa8c724e5..7d7cdc046 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target index fa8953781..559c9eea8 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target index 166b8d834..0da5f51cf 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target index 008001571..b93d67e44 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target index 476a08f15..28d7be258 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target index 3e619ed69..34b88e5f0 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target index bf73bc995..dc1086298 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target index 58192257a..3f0b6c5ea 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index b384956ce..6526d2e69 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index d606a5ec5..e62a31099 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index 6c65c24a1..a5d9229f3 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index 9662b0577..69201e9de 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd index 72f20c4b7..4eec8aa35 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd @@ -1,22 +1,22 @@ target "jetty-9.4.x" with source configurePhase -location jetty-9.4.30 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.35.v20201120/" { - org.eclipse.jetty.client [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.client.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.continuation [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.continuation.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.http [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.http.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.io [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.io.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.security [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.security.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.server [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.server.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.servlet [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.servlet.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.util [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.util.source [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.util.ajax [9.4.35.v20201120,9.4.35.v20201120] - org.eclipse.jetty.util.ajax.source [9.4.35.v20201120,9.4.35.v20201120] +location jetty-9.4.36 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.36.v20210114/" { + org.eclipse.jetty.client [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.client.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.continuation [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.continuation.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.http [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.http.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.io [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.io.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.security [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.security.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.server [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.server.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.servlet [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.servlet.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.util [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.util.source [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.util.ajax [9.4.36.v20210114,9.4.36.v20210114] + org.eclipse.jetty.util.ajax.source [9.4.36.v20210114,9.4.36.v20210114] } diff --git a/pom.xml b/pom.xml index 8257f952d..7e3acbcf3 100644 --- a/pom.xml +++ b/pom.xml @@ -162,7 +162,7 @@ 1.19 4.3.1 3.1.0 - 9.4.35.v20201120 + 9.4.36.v20210114 0.14.4 4.5.13 4.4.14 From 5b528474f590a2abfc6fdc8300e6acfa3d81d355 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Tue, 9 Feb 2021 02:34:10 +0100 Subject: [PATCH 17/35] GitHook: use generic OutputStream instead of PrintStream Change-Id: I15e64dc963c9d27dc9c8de4976dd63f74b918b15 Signed-off-by: Matthias Sohn --- org.eclipse.jgit/.settings/.api_filters | 30 +++++++++++++++++++ .../src/org/eclipse/jgit/hooks/GitHook.java | 26 ++++++---------- .../src/org/eclipse/jgit/util/FS.java | 13 ++++---- .../src/org/eclipse/jgit/util/FS_POSIX.java | 4 +-- .../eclipse/jgit/util/FS_Win32_Cygwin.java | 4 +-- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index b74e703a0..d389ac588 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -24,4 +24,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index 53f4819d3..49e145088 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -13,8 +13,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; +import java.io.OutputStream; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.AbortedByHookException; @@ -44,12 +43,12 @@ abstract class GitHook implements Callable { /** * The output stream to be used by the hook. */ - private final PrintStream outputStream; + private final OutputStream outputStream; /** * The error stream to be used by the hook. */ - private final PrintStream errorStream; + private final OutputStream errorStream; /** * Constructor for GitHook. @@ -63,7 +62,7 @@ abstract class GitHook implements Callable { * The output stream the hook must use. {@code null} is allowed, * in which case the hook will use {@code System.out}. */ - protected GitHook(Repository repo, PrintStream outputStream) { + protected GitHook(Repository repo, OutputStream outputStream) { this(repo, outputStream, null); } @@ -79,8 +78,8 @@ protected GitHook(Repository repo, PrintStream outputStream) { * The error stream the hook must use. {@code null} is allowed, * in which case the hook will use {@code System.err}. */ - protected GitHook(Repository repo, PrintStream outputStream, - PrintStream errorStream) { + protected GitHook(Repository repo, OutputStream outputStream, + OutputStream errorStream) { this.repo = repo; this.outputStream = outputStream; this.errorStream = errorStream; @@ -137,7 +136,7 @@ protected String getStdinArgs() { * @return The output stream the hook must use. Never {@code null}, * {@code System.out} is returned by default. */ - protected PrintStream getOutputStream() { + protected OutputStream getOutputStream() { return outputStream == null ? System.out : outputStream; } @@ -147,7 +146,7 @@ protected PrintStream getOutputStream() { * @return The error stream the hook must use. Never {@code null}, * {@code System.err} is returned by default. */ - protected PrintStream getErrorStream() { + protected OutputStream getErrorStream() { return errorStream == null ? System.err : errorStream; } @@ -161,20 +160,13 @@ protected void doRun() throws AbortedByHookException { final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray, getErrorStream()); - PrintStream hookErrRedirect = null; - try { - hookErrRedirect = new PrintStream(stderrStream, false, - UTF_8.name()); - } catch (UnsupportedEncodingException e) { - // UTF-8 is guaranteed to be available - } Repository repository = getRepository(); FS fs = repository.getFS(); if (fs == null) { fs = FS.DETECTED; } ProcessResult result = fs.runHookIfPresent(repository, getHookName(), - getParameters(), getOutputStream(), hookErrRedirect, + getParameters(), getOutputStream(), stderrStream, getStdinArgs()); if (result.isExecutedWithError()) { throw new AbortedByHookException( 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 c80a3a4b7..0946f645f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -22,7 +22,6 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.io.PrintStream; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.AccessDeniedException; @@ -1873,18 +1872,18 @@ public ProcessResult runHookIfPresent(Repository repository, * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. - * @since 4.0 + * @since 5.11 */ public ProcessResult runHookIfPresent(Repository repository, final String hookName, - String[] args, PrintStream outRedirect, PrintStream errRedirect, + String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return new ProcessResult(Status.NOT_SUPPORTED); } /** * See - * {@link #runHookIfPresent(Repository, String, String[], PrintStream, PrintStream, String)} + * {@link #runHookIfPresent(Repository, String, String[], OutputStream, OutputStream, String)} * . Should only be called by FS supporting shell scripts execution. * * @param repository @@ -1909,11 +1908,11 @@ public ProcessResult runHookIfPresent(Repository repository, * @throws org.eclipse.jgit.api.errors.JGitInternalException * if we fail to run the hook somehow. Causes may include an * interrupted process or I/O errors. - * @since 4.0 + * @since 5.11 */ protected ProcessResult internalRunHookIfPresent(Repository repository, - final String hookName, String[] args, PrintStream outRedirect, - PrintStream errRedirect, String stdinArgs) + final String hookName, String[] args, OutputStream outRedirect, + OutputStream errRedirect, String stdinArgs) throws JGitInternalException { File hookFile = findHook(repository, hookName); if (hookFile == null || hookName == null) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java index fb63dc02b..946d81c73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java @@ -16,7 +16,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import java.io.PrintStream; +import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; @@ -268,7 +268,7 @@ String shellQuote(String cmd) { /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, - String[] args, PrintStream outRedirect, PrintStream errRedirect, + String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return internalRunHookIfPresent(repository, hookName, args, outRedirect, errRedirect, stdinArgs); 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 d53bff78e..add549817 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 @@ -13,7 +13,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; -import java.io.PrintStream; +import java.io.OutputStream; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; @@ -139,7 +139,7 @@ public String relativize(String base, String other) { /** {@inheritDoc} */ @Override public ProcessResult runHookIfPresent(Repository repository, String hookName, - String[] args, PrintStream outRedirect, PrintStream errRedirect, + String[] args, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws JGitInternalException { return internalRunHookIfPresent(repository, hookName, args, outRedirect, errRedirect, stdinArgs); From f17f8e8ba9cc5bed87ef5637b7ba4bed20b761b5 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Tue, 9 Feb 2021 02:42:29 +0100 Subject: [PATCH 18/35] GitHook: use default charset for output and error streams External scripts most probably expect the default charset. Change-Id: I318a5e1d9f536a95e70c06ffb5b6f408cd40f73a Signed-off-by: Matthias Sohn --- .../src/org/eclipse/jgit/hooks/GitHook.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index 49e145088..e19ebcc37 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -9,11 +9,10 @@ */ package org.eclipse.jgit.hooks; -import static java.nio.charset.StandardCharsets.UTF_8; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.AbortedByHookException; @@ -155,8 +154,10 @@ protected OutputStream getErrorStream() { * * @throws org.eclipse.jgit.api.errors.AbortedByHookException * If the underlying hook script exited with non-zero. + * @throws IOException + * if an IO error occurred */ - protected void doRun() throws AbortedByHookException { + protected void doRun() throws AbortedByHookException, IOException { final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray, getErrorStream()); @@ -170,7 +171,8 @@ protected void doRun() throws AbortedByHookException { getStdinArgs()); if (result.isExecutedWithError()) { throw new AbortedByHookException( - new String(errorByteArray.toByteArray(), UTF_8), + new String(errorByteArray.toByteArray(), + Charset.defaultCharset().name()), getHookName(), result.getExitCode()); } } From 935c8b752b05f989c9bb734dd7a47533ecec6d37 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sun, 7 Feb 2021 23:48:57 +0100 Subject: [PATCH 19/35] Allow to define additional Hook classes outside JGit EGit wants to add gitflow specific hooks in org.eclipse.egit.gitflow. Make GitHook public to allow sub-classing outside of the org.eclipse.jgit.hooks package. Change-Id: I439575ec901e3610b5cf9d66f7641c8324faa865 Signed-off-by: Matthias Sohn --- org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index e19ebcc37..4cbd6c2eb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -33,9 +33,9 @@ * the return type which is expected from {@link #call()} * @see Git * Hooks on the git-scm official site - * @since 4.0 + * @since 5.11 */ -abstract class GitHook implements Callable { +public abstract class GitHook implements Callable { private final Repository repo; From 15a38e5b4f79792c8ce85c8eddd567c32350de74 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Fri, 13 Dec 2019 15:20:02 +0100 Subject: [PATCH 20/35] Post commit hook failure should not cause commit failure As the post commit hook is run after a commit is finished, it can not abort the commit and the exit code of this hook should not have any effect. This can be achieved by not throwing a AbortedByHookException exception. The stderr output is not lost thanks to contributions for bug 553471. Bug: 553428 Change-Id: I451a76e04103e632ff44e045561c5a41f7b7d558 Signed-off-by: Tim Neumann Signed-off-by: Fabian Pfaff Signed-off-by: Matthias Sohn --- .../src/org/eclipse/jgit/hooks/GitHook.java | 26 ++++++++++++++++--- .../eclipse/jgit/hooks/PostCommitHook.java | 13 ++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index 4cbd6c2eb..ce3ad2239 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -170,13 +170,31 @@ protected void doRun() throws AbortedByHookException, IOException { getParameters(), getOutputStream(), stderrStream, getStdinArgs()); if (result.isExecutedWithError()) { - throw new AbortedByHookException( - new String(errorByteArray.toByteArray(), - Charset.defaultCharset().name()), - getHookName(), result.getExitCode()); + handleError(new String(errorByteArray.toByteArray(), + Charset.defaultCharset().name()), result); } } + /** + * Process that the hook exited with an error. This default implementation + * throws an {@link AbortedByHookException }. Hooks which need a different + * behavior can overwrite this method. + * + * @param message + * error message + * @param result + * The process result of the hook + * @throws AbortedByHookException + * When the hook should be aborted + * @since 5.11 + */ + protected void handleError(String message, + final ProcessResult result) + throws AbortedByHookException { + throw new AbortedByHookException(message, getHookName(), + result.getExitCode()); + } + /** * Check whether a 'native' (i.e. script) hook is installed in the * repository. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java index 0b61ebea3..b9dafcca3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java @@ -14,6 +14,7 @@ import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.ProcessResult; /** * The post-commit hook implementation. This hook is run after the @@ -73,4 +74,16 @@ public String getHookName() { return NAME; } + + /** + * Overwrites the default implementation to never throw an + * {@link AbortedByHookException}, as the commit has already been done and + * the exit code of the post-commit hook has no effect. + */ + @Override + protected void handleError(String message, ProcessResult result) + throws AbortedByHookException { + // Do nothing as the exit code of the post-commit hook has no effect. + } + } From 3774fcc848da7526ffa74211cbb2781df5731125 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Thu, 7 Jan 2021 17:11:57 +0100 Subject: [PATCH 21/35] GPG signature verification via BouncyCastle Add a GpgSignatureVerifier interface, plus a factory to create instances thereof that is provided via the ServiceLoader mechanism. Implement the new interface for BouncyCastle. A verifier maintains an internal LRU cache of previously found public keys to speed up verifying multiple objects (tag or commits). Mergetags are not handled. Provide a new VerifySignatureCommand in org.eclipse.jgit.api together with a factory method Git.verifySignature(). The command can verify signatures on tags or commits, and can be limited to accept only tags or commits. Provide a new public WrongObjectTypeException thrown when the command is limited to either tags or commits and a name resolves to some other object kind. In jgit.pgm, implement "git tag -v", "git log --show-signature", and "git show --show-signature". The output is similar to command-line gpg invoked via git, but not identical. In particular, lines are not prefixed by "gpg:" but by "bc:". Trust levels for public keys are read from the keys' trust packets, not from GPG's internal trust database. A trust packet may or may not be set. Command-line GPG produces more warning lines depending on the trust level, warning about keys with a trust level below "full". There are no unit tests because JGit still doesn't have any setup to do signing unit tests; this would require at least a faked .gpg directory with pre-created key rings and keys, and a way to make the BouncyCastle classes use that directory instead of the default. See bug 547538 and also bug 544847. Tested manually with a small test repository containing signed and unsigned commits and tags, with signatures made with different keys and made by command-line git using GPG 2.2.25 and by JGit using BouncyCastle 1.65. Bug: 547751 Change-Id: If7e34aeed6ca6636a92bf774d893d98f6d459181 Signed-off-by: Thomas Wolf --- org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF | 2 + ...lipse.jgit.lib.GpgSignatureVerifierFactory | 1 + .../jgit/gpg/bc/internal/BCText.properties | 7 + .../eclipse/jgit/gpg/bc/internal/BCText.java | 16 + .../internal/BouncyCastleGpgKeyLocator.java | 76 +++- .../BouncyCastleGpgSignatureVerifier.java | 388 ++++++++++++++++++ ...uncyCastleGpgSignatureVerifierFactory.java | 28 ++ .../bc/internal/BouncyCastleGpgSigner.java | 6 +- .../jgit/pgm/internal/CLIText.properties | 7 +- .../src/org/eclipse/jgit/pgm/Log.java | 44 +- .../src/org/eclipse/jgit/pgm/Show.java | 47 ++- .../src/org/eclipse/jgit/pgm/Tag.java | 85 +++- .../eclipse/jgit/pgm/internal/CLIText.java | 3 +- .../jgit/pgm/internal/VerificationUtils.java | 56 +++ .../eclipse/jgit/internal/JGitText.properties | 9 + .../src/org/eclipse/jgit/api/Git.java | 12 +- .../eclipse/jgit/api/VerificationResult.java | 46 +++ .../jgit/api/VerifySignatureCommand.java | 307 ++++++++++++++ .../api/errors/WrongObjectTypeException.java | 65 +++ .../org/eclipse/jgit/internal/JGitText.java | 11 +- .../jgit/lib/GpgSignatureVerifier.java | 158 +++++++ .../jgit/lib/GpgSignatureVerifierFactory.java | 71 ++++ .../src/org/eclipse/jgit/revwalk/RevTag.java | 20 +- .../org/eclipse/jgit/util/SignatureUtils.java | 86 ++++ 24 files changed, 1507 insertions(+), 44 deletions(-) create mode 100644 org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongObjectTypeException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF index 655dcca80..e5f432dc5 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -9,11 +9,13 @@ Bundle-Localization: plugin Bundle-Version: 5.11.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", + org.bouncycastle.bcpg.sig;version="[1.65.0,2.0.0)", org.bouncycastle.gpg;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)", org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)", org.bouncycastle.openpgp;version="[1.65.0,2.0.0)", + org.bouncycastle.openpgp.jcajce;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)", diff --git a/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory new file mode 100644 index 000000000..17ab30fba --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/resources/META-INF/services/org.eclipse.jgit.lib.GpgSignatureVerifierFactory @@ -0,0 +1 @@ +org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSignatureVerifierFactory \ No newline at end of file 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 1441c63e8..83ed9059e 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 @@ -8,4 +8,11 @@ gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: gpgNotASigningKey=Secret key ({0}) is not suitable for signing gpgKeyInfo=GPG Key (fingerprint {0}) gpgSigningCancelled=Signing was cancelled +nonSignatureError=Signature does not decode into a signature object +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 +signatureNoPublicKey=No public key found to verify the signature +signatureParseError=Signature cannot be parsed +signatureVerificationError=Signature verification failed unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available. 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 1a00b0fc7..9403ba235 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,3 +1,12 @@ +/* + * Copyright (C) 2018, 2021 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 + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ package org.eclipse.jgit.gpg.bc.internal; import org.eclipse.jgit.nls.NLS; @@ -28,6 +37,13 @@ public static BCText get() { /***/ public String gpgNotASigningKey; /***/ public String gpgKeyInfo; /***/ public String gpgSigningCancelled; + /***/ public String nonSignatureError; + /***/ public String signatureInconsistent; + /***/ public String signatureKeyLookupError; + /***/ public String signatureNoKeyInfo; + /***/ public String signatureNoPublicKey; + /***/ public String signatureParseError; + /***/ public String signatureVerificationError; /***/ public String unableToSignCommitNoSecretKey; } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java index 1389f8bd1..13655c0d5 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Salesforce and others + * Copyright (C) 2018, 2021 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 @@ -14,12 +14,14 @@ import java.io.BufferedInputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; @@ -247,16 +249,32 @@ private static boolean containsIgnoreCase(String a, String b) { return false; } - private String toFingerprint(String keyId) { + private static String toFingerprint(String keyId) { if (keyId.startsWith("0x")) { //$NON-NLS-1$ return keyId.substring(2); } return keyId; } - private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) + static PGPPublicKey findPublicKey(String fingerprint, String keySpec) + throws IOException, PGPException { + PGPPublicKey result = findPublicKeyInPubring(USER_PGP_PUBRING_FILE, + fingerprint, keySpec); + if (result == null && exists(USER_KEYBOX_PATH)) { + try { + result = findPublicKeyInKeyBox(USER_KEYBOX_PATH, fingerprint, + keySpec); + } catch (NoSuchAlgorithmException | NoSuchProviderException + | IOException | NoOpenPgpKeyException e) { + log.error(e.getMessage(), e); + } + } + return result; + } + + private static PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob, + String keyId) throws IOException { - String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT); if (keyId.isEmpty()) { return null; } @@ -270,10 +288,11 @@ private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob) return null; } - private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) + private static PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob, + String keySpec) throws IOException { for (UserID userID : keyBlob.getUserIds()) { - if (containsSigningKey(userID.getUserIDAsString(), signingKey)) { + if (containsSigningKey(userID.getUserIDAsString(), keySpec)) { return getSigningPublicKey(keyBlob); } } @@ -285,6 +304,10 @@ private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) * * @param keyboxFile * the KeyBox file + * @param keyId + * to look for, may be null + * @param keySpec + * to look for * @return publicKey the public key (maybe null) * @throws IOException * in case of problems reading the file @@ -293,19 +316,22 @@ private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob) * @throws NoOpenPgpKeyException * if the file does not contain any OpenPGP key */ - private PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile) + private static PGPPublicKey findPublicKeyInKeyBox(Path keyboxFile, + String keyId, String keySpec) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, NoOpenPgpKeyException { KeyBox keyBox = readKeyBoxFile(keyboxFile); + String id = keyId != null ? keyId + : toFingerprint(keySpec).toLowerCase(Locale.ROOT); boolean hasOpenPgpKey = false; for (KeyBlob keyBlob : keyBox.getKeyBlobs()) { if (keyBlob.getType() == BlobType.OPEN_PGP_BLOB) { hasOpenPgpKey = true; - PGPPublicKey key = findPublicKeyByKeyId(keyBlob); + PGPPublicKey key = findPublicKeyByKeyId(keyBlob, id); if (key != null) { return key; } - key = findPublicKeyByUserId(keyBlob); + key = findPublicKeyByUserId(keyBlob, keySpec); if (key != null) { return key; } @@ -349,7 +375,8 @@ public BouncyCastleGpgKey findSecretKey() throws IOException, // pubring.gpg also try secring.gpg to find the secret key. if (exists(USER_KEYBOX_PATH)) { try { - publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH); + publicKey = findPublicKeyInKeyBox(USER_KEYBOX_PATH, null, + signingKey); if (publicKey != null) { key = findSecretKeyForKeyBoxPublicKey(publicKey, USER_KEYBOX_PATH); @@ -372,7 +399,8 @@ public BouncyCastleGpgKey findSecretKey() throws IOException, } } if (exists(USER_PGP_PUBRING_FILE)) { - publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE); + publicKey = findPublicKeyInPubring(USER_PGP_PUBRING_FILE, null, + signingKey); if (publicKey != null) { // GPG < 2.1 may have both; the agent using the directory // and gpg using secring.gpg. GPG >= 2.1 delegates all @@ -562,6 +590,11 @@ private PGPSecretKey findSecretKeyInLegacySecring(String signingkey, * Return the first public key matching the key id ({@link #signingKey}. * * @param pubringFile + * to search + * @param keyId + * to look for, may be null + * @param keySpec + * to look for * * @return the PGP public key, or {@code null} if none found * @throws IOException @@ -569,14 +602,16 @@ private PGPSecretKey findSecretKeyInLegacySecring(String signingkey, * @throws PGPException * on BouncyCastle errors */ - private PGPPublicKey findPublicKeyInPubring(Path pubringFile) + private static PGPPublicKey findPublicKeyInPubring(Path pubringFile, + String keyId, String keySpec) throws IOException, PGPException { try (InputStream in = newInputStream(pubringFile)) { PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( new BufferedInputStream(in), new JcaKeyFingerprintCalculator()); - String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT); + String id = keyId != null ? keyId + : toFingerprint(keySpec).toLowerCase(Locale.ROOT); Iterator keyrings = pgpPub.getKeyRings(); while (keyrings.hasNext()) { PGPPublicKeyRing keyRing = keyrings.next(); @@ -586,30 +621,33 @@ private PGPPublicKey findPublicKeyInPubring(Path pubringFile) // try key id String fingerprint = Hex.toHexString(key.getFingerprint()) .toLowerCase(Locale.ROOT); - if (fingerprint.endsWith(keyId)) { + if (fingerprint.endsWith(id)) { return key; } // try user id Iterator userIDs = key.getUserIDs(); while (userIDs.hasNext()) { String userId = userIDs.next(); - if (containsSigningKey(userId, signingKey)) { + if (containsSigningKey(userId, keySpec)) { return key; } } } } + } catch (FileNotFoundException | NoSuchFileException e) { + // Ignore and return null } return null; } - private PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) + private static PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint) throws IOException { return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing() .getPublicKey(fingerprint); } - private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException { + private static PGPPublicKey getSigningPublicKey(KeyBlob blob) + throws IOException { PGPPublicKey masterKey = null; Iterator keys = ((PublicKeyRingBlob) blob) .getPGPPublicKeyRing().getPublicKeys(); @@ -629,7 +667,7 @@ private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException { return masterKey; } - private boolean isSigningKey(PGPPublicKey key) { + private static boolean isSigningKey(PGPPublicKey key) { Iterator signatures = key.getSignatures(); while (signatures.hasNext()) { PGPSignature sig = (PGPSignature) signatures.next(); @@ -641,7 +679,7 @@ private boolean isSigningKey(PGPPublicKey key) { return false; } - private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException, + private static KeyBox readKeyBoxFile(Path keyboxFile) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, NoOpenPgpKeyException { if (keyboxFile.toFile().length() == 0) { diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java new file mode 100644 index 000000000..7161895a6 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifier.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.Security; +import java.text.MessageFormat; +import java.time.Instant; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.Locale; + +import org.bouncycastle.bcpg.sig.IssuerFingerprint; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.util.LRUMap; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; + +/** + * A {@link GpgSignatureVerifier} to verify GPG signatures using BouncyCastle. + */ +public class BouncyCastleGpgSignatureVerifier implements GpgSignatureVerifier { + + private static void registerBouncyCastleProviderIfNecessary() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * Creates a new instance and registers the BouncyCastle security provider + * if needed. + */ + public BouncyCastleGpgSignatureVerifier() { + registerBouncyCastleProviderIfNecessary(); + } + + // To support more efficient signature verification of multiple objects we + // cache public keys once found in a LRU cache. + + private static final Object NO_KEY = new Object(); + + private LRUMap byFingerprint = new LRUMap<>(16, 200); + + private LRUMap bySigner = new LRUMap<>(16, 200); + + @Override + public String getName() { + return "bc"; //$NON-NLS-1$ + } + + @Override + @Nullable + public SignatureVerification verifySignature(@NonNull RevObject object, + @NonNull GpgConfig config) throws IOException { + if (object instanceof RevCommit) { + RevCommit commit = (RevCommit) object; + byte[] signatureData = commit.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = commit.getRawBuffer(); + // Now remove the GPG signature + byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' }; + int start = RawParseUtils.headerStart(header, raw, 0); + if (start < 0) { + return null; + } + int end = RawParseUtils.headerEnd(raw, start); + // start is at the beginning of the header's content + start -= header.length + 1; + // end is on the terminating LF; we need to skip that, too + if (end < raw.length) { + end++; + } + byte[] data = new byte[raw.length - (end - start)]; + System.arraycopy(raw, 0, data, 0, start); + System.arraycopy(raw, end, data, start, raw.length - end); + return verify(data, signatureData); + } else if (object instanceof RevTag) { + RevTag tag = (RevTag) object; + byte[] signatureData = tag.getRawGpgSignature(); + if (signatureData == null) { + return null; + } + byte[] raw = tag.getRawBuffer(); + // The signature is just tacked onto the end of the message, which + // is last in the buffer. + byte[] data = Arrays.copyOfRange(raw, 0, + raw.length - signatureData.length); + return verify(data, signatureData); + } + return null; + } + + static PGPSignature parseSignature(InputStream in) + throws IOException, PGPException { + try (InputStream sigIn = PGPUtil.getDecoderStream(in)) { + JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(sigIn); + Object obj = pgpFactory.nextObject(); + if (obj instanceof PGPCompressedData) { + obj = new JcaPGPObjectFactory( + ((PGPCompressedData) obj).getDataStream()).nextObject(); + } + if (obj instanceof PGPSignatureList) { + return ((PGPSignatureList) obj).get(0); + } + return null; + } + } + + @Override + public SignatureVerification verify(byte[] data, byte[] signatureData) + throws IOException { + PGPSignature signature = null; + String fingerprint = null; + String signer = null; + String keyId = null; + try (InputStream sigIn = new ByteArrayInputStream(signatureData)) { + signature = parseSignature(sigIn); + if (signature != null) { + // Try to figure out something to find the public key with. + if (signature.hasSubpackets()) { + PGPSignatureSubpacketVector packets = signature + .getHashedSubPackets(); + IssuerFingerprint fingerprintPacket = packets + .getIssuerFingerprint(); + if (fingerprintPacket != null) { + fingerprint = Hex + .toHexString(fingerprintPacket.getFingerprint()) + .toLowerCase(Locale.ROOT); + } + signer = packets.getSignerUserID(); + if (signer != null) { + signer = BouncyCastleGpgSigner.extractSignerId(signer); + } + } + keyId = Long.toUnsignedString(signature.getKeyID(), 16) + .toLowerCase(Locale.ROOT); + } else { + throw new JGitInternalException(BCText.get().nonSignatureError); + } + } catch (PGPException e) { + throw new JGitInternalException(BCText.get().signatureParseError, + e); + } + Date signatureCreatedAt = signature.getCreationTime(); + if (fingerprint == null && signer == null && keyId == null) { + return new VerificationResult(signatureCreatedAt, null, null, null, + false, false, TrustLevel.UNKNOWN, + BCText.get().signatureNoKeyInfo); + } + if (fingerprint != null && keyId != null + && !fingerprint.endsWith(keyId)) { + return new VerificationResult(signatureCreatedAt, signer, fingerprint, + null, false, false, TrustLevel.UNKNOWN, + MessageFormat.format(BCText.get().signatureInconsistent, + keyId, fingerprint)); + } + if (fingerprint == null && keyId != null) { + fingerprint = keyId; + } + // Try to find the public key + String keySpec = '<' + signer + '>'; + Object cached = null; + PGPPublicKey publicKey = null; + try { + cached = byFingerprint.get(fingerprint); + if (cached != null) { + if (cached instanceof PGPPublicKey) { + publicKey = (PGPPublicKey) cached; + } + } else if (!StringUtils.isEmptyOrNull(signer)) { + cached = bySigner.get(signer); + if (cached != null) { + if (cached instanceof PGPPublicKey) { + publicKey = (PGPPublicKey) cached; + } + } + } + if (cached == null) { + publicKey = BouncyCastleGpgKeyLocator.findPublicKey(fingerprint, + keySpec); + } + } catch (IOException | PGPException e) { + throw new JGitInternalException( + BCText.get().signatureKeyLookupError, e); + } + if (publicKey == null) { + if (cached == null) { + byFingerprint.put(fingerprint, NO_KEY); + byFingerprint.put(keyId, NO_KEY); + if (signer != null) { + bySigner.put(signer, NO_KEY); + } + } + return new VerificationResult(signatureCreatedAt, signer, + fingerprint, null, false, false, TrustLevel.UNKNOWN, + BCText.get().signatureNoPublicKey); + } + if (cached == null) { + byFingerprint.put(fingerprint, publicKey); + byFingerprint.put(keyId, publicKey); + if (signer != null) { + bySigner.put(signer, publicKey); + } + } + String user = null; + Iterator userIds = publicKey.getUserIDs(); + if (!StringUtils.isEmptyOrNull(signer)) { + while (userIds.hasNext()) { + String userId = userIds.next(); + if (BouncyCastleGpgKeyLocator.containsSigningKey(userId, + keySpec)) { + user = userId; + break; + } + } + } + if (user == null) { + userIds = publicKey.getUserIDs(); + if (userIds.hasNext()) { + user = userIds.next(); + } + } + boolean expired = false; + long validFor = publicKey.getValidSeconds(); + if (validFor > 0 && signatureCreatedAt != null) { + Instant expiredAt = publicKey.getCreationTime().toInstant() + .plusSeconds(validFor); + expired = expiredAt.isBefore(signatureCreatedAt.toInstant()); + } + // Trust data is not defined in OpenPGP; the format is implementation + // specific. We don't use the GPG trustdb but simply the trust packet + // on the public key, if present. Even if present, it may or may not + // be set. + byte[] trustData = publicKey.getTrustData(); + TrustLevel trust = parseGpgTrustPacket(trustData); + boolean verified = false; + try { + signature.init( + new JcaPGPContentVerifierBuilderProvider() + .setProvider(BouncyCastleProvider.PROVIDER_NAME), + publicKey); + signature.update(data); + verified = signature.verify(); + } catch (PGPException e) { + throw new JGitInternalException( + BCText.get().signatureVerificationError, e); + } + return new VerificationResult(signatureCreatedAt, signer, fingerprint, user, + verified, expired, trust, null); + } + + private TrustLevel parseGpgTrustPacket(byte[] packet) { + if (packet == null || packet.length < 6) { + // A GPG trust packet has at least 6 bytes. + return TrustLevel.UNKNOWN; + } + if (packet[2] != 'g' || packet[3] != 'p' || packet[4] != 'g') { + // Not a GPG trust packet + return TrustLevel.UNKNOWN; + } + int trust = packet[0] & 0x0F; + switch (trust) { + case 0: // No determined/set + case 1: // Trust expired; i.e., calculation outdated or key expired + case 2: // Undefined: not enough information to set + return TrustLevel.UNKNOWN; + case 3: + return TrustLevel.NEVER; + case 4: + return TrustLevel.MARGINAL; + case 5: + return TrustLevel.FULL; + case 6: + return TrustLevel.ULTIMATE; + default: + return TrustLevel.UNKNOWN; + } + } + + @Override + public void clear() { + byFingerprint.clear(); + bySigner.clear(); + } + + private static class VerificationResult implements SignatureVerification { + + private final Date creationDate; + + private final String signer; + + private final String keyUser; + + private final String fingerprint; + + private final boolean verified; + + private final boolean expired; + + private final @NonNull TrustLevel trustLevel; + + private final String message; + + public VerificationResult(Date creationDate, String signer, + String fingerprint, String user, boolean verified, + boolean expired, @NonNull TrustLevel trust, String message) { + this.creationDate = creationDate; + this.signer = signer; + this.fingerprint = fingerprint; + this.keyUser = user; + this.verified = verified; + this.expired = expired; + this.trustLevel = trust; + this.message = message; + } + + @Override + public Date getCreationDate() { + return creationDate; + } + + @Override + public String getSigner() { + return signer; + } + + @Override + public String getKeyUser() { + return keyUser; + } + + @Override + public String getKeyFingerprint() { + return fingerprint; + } + + @Override + public boolean isExpired() { + return expired; + } + + @Override + public TrustLevel getTrustLevel() { + return trustLevel; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public boolean getVerified() { + return verified; + } + } +} diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java new file mode 100644 index 000000000..ae82b758a --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSignatureVerifierFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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; + +import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; + +/** + * A {@link GpgSignatureVerifierFactory} that creates + * {@link GpgSignatureVerifier} instances that verify GPG signatures using + * BouncyCastle and that do cache public keys. + */ +public final class BouncyCastleGpgSignatureVerifierFactory + extends GpgSignatureVerifierFactory { + + @Override + public GpgSignatureVerifier getVerifier() { + return new BouncyCastleGpgSignatureVerifier(); + } + +} 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 f448d5e9e..9f48e5431 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020, Salesforce and others + * Copyright (C) 2018, 2021, 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 @@ -39,9 +39,9 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.GpgSignature; import org.eclipse.jgit.lib.GpgSigner; -import org.eclipse.jgit.lib.GpgObjectSigner; import org.eclipse.jgit.lib.ObjectBuilder; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.GpgConfig.GpgFormat; @@ -210,7 +210,7 @@ public void signObject(@NonNull ObjectBuilder object, } } - private String extractSignerId(String pgpUserId) { + static String extractSignerId(String pgpUserId) { int from = pgpUserId.indexOf('<'); if (from >= 0) { int to = pgpUserId.indexOf('>', from + 1); 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 afa253eeb..df55eb077 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 @@ -77,14 +77,15 @@ invalidHttpProxyOnlyHttpSupported=Invalid http_proxy: {0}: Only http supported. invalidRecurseSubmodulesMode=Invalid recurse submodules mode: {0} invalidUntrackedFilesMode=Invalid untracked files mode ''{0}'' jgitVersion=jgit version {0} -lineFormat={0} -listeningOn=Listening on {0} lfsNoAccessKey=No accessKey in {0} lfsNoSecretKey=No secretKey in {0} lfsProtocolUrl=LFS protocol URL: {0} lfsStoreDirectory=LFS objects stored in: {0} lfsStoreUrl=LFS store URL: {0} lfsUnknownStoreType="Unknown LFS store type: {0}" +lineFormat={0} +listeningOn=Listening on {0} +logNoSignatureVerifier="No signature verifier available" mergeConflict=CONFLICT(content): Merge conflict in {0} mergeCheckoutConflict=error: Your local changes to the following files would be overwritten by merge: mergeFailed=Automatic merge failed; fix conflicts and then commit the result @@ -411,6 +412,7 @@ usage_show=Display one commit usage_showRefNamesMatchingCommits=Show ref names matching commits usage_showPatch=display patch usage_showNotes=Add this ref to the list of note branches from which notes are displayed +usage_showSignature=Verify signatures of signed commits in the log usage_showTimeInMilliseconds=Show mtime in milliseconds usage_squash=Squash commits as if a real merge happened, but do not make a commit or move the HEAD. usage_srcPrefix=show the source prefix instead of "a/" @@ -424,6 +426,7 @@ usage_tagLocalUser=create a signed annotated tag using the specified GPG key ID usage_tagMessage=create an annotated tag with the given message, unsigned unless -s or -u are given, or config tag.gpgSign is true, or tar.forceSignAnnotated is true and -a is not given usage_tagSign=create a signed annotated tag usage_tagNoSign=suppress signing the tag +usage_tagVerify=Verify the GPG signature usage_untrackedFilesMode=show untracked files usage_updateRef=reference to update usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java index 55efd23c6..353b64b9b 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2010, Google Inc. - * Copyright (C) 2006-2008, Robin Rosenberg - * Copyright (C) 2008, Shawn O. Pearce and others + * Copyright (C) 2006, 2008, Robin Rosenberg + * Copyright (C) 2008, 2021, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -31,12 +31,17 @@ import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.notes.NoteMap; import org.eclipse.jgit.pgm.internal.CLIText; +import org.eclipse.jgit.pgm.internal.VerificationUtils; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.util.GitDateFormatter; @@ -68,6 +73,9 @@ void addAdditionalNoteRef(String notesRef) { additionalNoteRefs.add(notesRef); } + @Option(name = "--show-signature", usage = "usage_showSignature") + private boolean showSignature; + @Option(name = "--date", usage = "usage_date") void dateFormat(String date) { if (date.toLowerCase(Locale.ROOT).equals(date)) @@ -147,6 +155,10 @@ void noPrefix(@SuppressWarnings("unused") boolean on) { // END -- Options shared with Diff + private GpgSignatureVerifier verifier; + + private GpgConfig config; + Log() { dateFormatter = new GitDateFormatter(Format.DEFAULT); } @@ -161,6 +173,7 @@ protected void init(Repository repository, String gitDir) { /** {@inheritDoc} */ @Override protected void run() { + config = new GpgConfig(db.getConfig()); diffFmt.setRepository(db); try { diffFmt.setPathFilter(pathFilter); @@ -197,6 +210,9 @@ protected void run() { throw die(e.getMessage(), e); } finally { diffFmt.close(); + if (verifier != null) { + verifier.clear(); + } } } @@ -229,6 +245,9 @@ protected void show(RevCommit c) throws Exception { } outw.println(); + if (showSignature) { + showSignature(c); + } final PersonIdent author = c.getAuthorIdent(); outw.println(MessageFormat.format(CLIText.get().authorInfo, author.getName(), author.getEmailAddress())); outw.println(MessageFormat.format(CLIText.get().dateInfo, @@ -252,6 +271,27 @@ protected void show(RevCommit c) throws Exception { outw.flush(); } + private void showSignature(RevCommit c) throws IOException { + if (c.getRawGpgSignature() == null) { + return; + } + if (verifier == null) { + GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory + .getDefault(); + if (factory == null) { + throw die(CLIText.get().logNoSignatureVerifier, null); + } + verifier = factory.getVerifier(); + } + SignatureVerification verification = verifier.verifySignature(c, + config); + if (verification == null) { + return; + } + VerificationUtils.writeVerification(outw, verification, + verifier.getName(), c.getCommitterIdent()); + } + /** * @param c * @return true if at least one note was printed, diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java index 1d43220ca..3beab60a8 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java @@ -29,10 +29,15 @@ import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; import org.eclipse.jgit.pgm.internal.CLIText; +import org.eclipse.jgit.pgm.internal.VerificationUtils; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; @@ -59,6 +64,9 @@ class Show extends TextBuiltin { @Option(name = "--", metaVar = "metaVar_path", handler = PathTreeFilterHandler.class) protected TreeFilter pathFilter = TreeFilter.ALL; + @Option(name = "--show-signature", usage = "usage_showSignature") + private boolean showSignature; + // BEGIN -- Options shared with Diff @Option(name = "-p", usage = "usage_showPatch") boolean showPatch; @@ -220,13 +228,16 @@ private void show(RevTag tag) throws IOException { } outw.println(); - String[] lines = tag.getFullMessage().split("\n"); //$NON-NLS-1$ - for (String s : lines) { - outw.println(s); + String fullMessage = tag.getFullMessage(); + if (!fullMessage.isEmpty()) { + String[] lines = tag.getFullMessage().split("\n"); //$NON-NLS-1$ + for (String s : lines) { + outw.println(s); + } } byte[] rawSignature = tag.getRawGpgSignature(); if (rawSignature != null) { - lines = RawParseUtils.decode(rawSignature).split("\n"); //$NON-NLS-1$ + String[] lines = RawParseUtils.decode(rawSignature).split("\n"); //$NON-NLS-1$ for (String s : lines) { outw.println(s); } @@ -258,6 +269,10 @@ private void show(RevWalk rw, RevCommit c) throws IOException { c.getId().copyTo(outbuffer, outw); outw.println(); + if (showSignature) { + showSignature(c); + } + final PersonIdent author = c.getAuthorIdent(); outw.println(MessageFormat.format(CLIText.get().authorInfo, author.getName(), author.getEmailAddress())); @@ -296,4 +311,28 @@ private void showDiff(RevCommit c) throws IOException { } outw.println(); } + + private void showSignature(RevCommit c) throws IOException { + if (c.getRawGpgSignature() == null) { + return; + } + GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory + .getDefault(); + if (factory == null) { + throw die(CLIText.get().logNoSignatureVerifier, null); + } + GpgSignatureVerifier verifier = factory.getVerifier(); + GpgConfig config = new GpgConfig(db.getConfig()); + try { + SignatureVerification verification = verifier.verifySignature(c, + config); + if (verification == null) { + return; + } + VerificationUtils.writeVerification(outw, verification, + verifier.getName(), c.getCommitterIdent()); + } finally { + verifier.clear(); + } + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java index 4cc62b339..e2cd31d19 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java @@ -4,7 +4,7 @@ * Copyright (C) 2008, Charles O'Farrell * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2008, 2020 Shawn O. Pearce and others + * Copyright (C) 2008, 2021 Shawn O. Pearce 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 @@ -22,43 +22,60 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListTagCommand; import org.eclipse.jgit.api.TagCommand; +import org.eclipse.jgit.api.VerificationResult; +import org.eclipse.jgit.api.VerifySignatureCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; +import org.eclipse.jgit.pgm.internal.VerificationUtils; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_CreateATag") class Tag extends TextBuiltin { - @Option(name = "-f", usage = "usage_forceReplacingAnExistingTag") + + @Option(name = "--force", aliases = { "-f" }, forbids = { "--delete", + "--verify" }, usage = "usage_forceReplacingAnExistingTag") private boolean force; - @Option(name = "-d", usage = "usage_tagDelete") + @Option(name = "--delete", aliases = { "-d" }, forbids = { + "--verify" }, usage = "usage_tagDelete") private boolean delete; @Option(name = "--annotate", aliases = { - "-a" }, usage = "usage_tagAnnotated") + "-a" }, forbids = { "--delete", + "--verify" }, usage = "usage_tagAnnotated") private boolean annotated; - @Option(name = "-m", metaVar = "metaVar_message", usage = "usage_tagMessage") + @Option(name = "-m", forbids = { "--delete", + "--verify" }, metaVar = "metaVar_message", usage = "usage_tagMessage") private String message; @Option(name = "--sign", aliases = { "-s" }, forbids = { - "--no-sign" }, usage = "usage_tagSign") + "--no-sign", "--delete", "--verify" }, usage = "usage_tagSign") private boolean sign; @Option(name = "--no-sign", usage = "usage_tagNoSign", forbids = { - "--sign" }) + "--sign", "--delete", "--verify" }) private boolean noSign; @Option(name = "--local-user", aliases = { - "-u" }, metaVar = "metaVar_tagLocalUser", usage = "usage_tagLocalUser") + "-u" }, forbids = { "--delete", + "--verify" }, metaVar = "metaVar_tagLocalUser", usage = "usage_tagLocalUser") private String gpgKeyId; + @Option(name = "--verify", aliases = { "-v" }, forbids = { "--delete", + "--force", "--annotate", "-m", "--sign", "--no-sign", + "--local-user" }, usage = "usage_tagVerify") + private boolean verify; + @Argument(index = 0, metaVar = "metaVar_name") private String tagName; @@ -70,7 +87,25 @@ class Tag extends TextBuiltin { protected void run() { try (Git git = new Git(db)) { if (tagName != null) { - if (delete) { + if (verify) { + VerifySignatureCommand verifySig = git.verifySignature() + .setMode(VerifySignatureCommand.VerifyMode.TAGS) + .addName(tagName); + + VerificationResult verification = verifySig.call() + .get(tagName); + if (verification == null) { + showUnsigned(git, tagName); + } else { + Throwable error = verification.getException(); + if (error != null) { + throw die(error.getMessage(), error); + } + writeVerification(verifySig.getVerifier().getName(), + (RevTag) verification.getObject(), + verification.getVerification()); + } + } else if (delete) { List deletedTags = git.tagDelete().setTags(tagName) .call(); if (deletedTags.isEmpty()) { @@ -116,4 +151,36 @@ protected void run() { throw die(e.getMessage(), e); } } + + private void showUnsigned(Git git, String wantedTag) throws IOException { + ObjectId id = git.getRepository().resolve(wantedTag); + if (id != null && !ObjectId.zeroId().equals(id)) { + try (RevWalk walk = new RevWalk(git.getRepository())) { + showTag(walk.parseTag(id)); + } + } else { + throw die( + MessageFormat.format(CLIText.get().tagNotFound, wantedTag)); + } + } + + private void showTag(RevTag tag) throws IOException { + outw.println("object " + tag.getObject().name()); //$NON-NLS-1$ + outw.println("type " + Constants.typeString(tag.getObject().getType())); //$NON-NLS-1$ + outw.println("tag " + tag.getTagName()); //$NON-NLS-1$ + outw.println("tagger " + tag.getTaggerIdent().toExternalString()); //$NON-NLS-1$ + outw.println(); + outw.print(tag.getFullMessage()); + } + + private void writeVerification(String name, RevTag tag, + SignatureVerification verification) throws IOException { + showTag(tag); + if (verification == null) { + outw.println(); + return; + } + VerificationUtils.writeVerification(outw, verification, name, + tag.getTaggerIdent()); + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index c68019e5d..991b3ba58 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, 2013 Sasa Zivkov - * Copyright (C) 2013, Obeo and others + * Copyright (C) 2013, 2021 Obeo and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -163,6 +163,7 @@ public static String fatalError(String message) { /***/ public String lfsUnknownStoreType; /***/ public String lineFormat; /***/ public String listeningOn; + /***/ public String logNoSignatureVerifier; /***/ public String mergeCheckoutConflict; /***/ public String mergeConflict; /***/ public String mergeFailed; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java new file mode 100644 index 000000000..c1f8a86a8 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/VerificationUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.pgm.internal; + +import java.io.IOException; + +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.util.GitDateFormatter; +import org.eclipse.jgit.util.SignatureUtils; +import org.eclipse.jgit.util.io.ThrowingPrintWriter; + +/** + * Utilities for signature verification. + */ +public final class VerificationUtils { + + private VerificationUtils() { + // No instantiation + } + + /** + * Writes information about a signature verification to the given writer. + * + * @param out + * to write to + * @param verification + * to show + * @param name + * of the verifier used + * @param creator + * of the object verified; used for time zone information + * @throws IOException + * if writing fails + */ + public static void writeVerification(ThrowingPrintWriter out, + SignatureVerification verification, String name, + PersonIdent creator) throws IOException { + String[] text = SignatureUtils + .toString(verification, creator, + new GitDateFormatter(GitDateFormatter.Format.LOCALE)) + .split("\n"); //$NON-NLS-1$ + for (String line : text) { + out.print(name); + out.print(": "); //$NON-NLS-1$ + out.println(line); + } + } +} 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 a8b2e563f..192462104 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -622,6 +622,8 @@ shortCompressedStreamAt=Short compressed stream at {0} shortReadOfBlock=Short read of block. shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section. shortSkipOfBlock=Short skip of block. +signatureVerificationError=Signature verification failed +signatureVerificationUnavailable=No signature verifier registered signedTagMessageNoLf=A non-empty message of a signed tag must end in LF. signingServiceUnavailable=Signing service is not available similarityScoreMustBeWithinBounds=Similarity score must be between 0 and 100. @@ -763,6 +765,13 @@ uriNotFoundWithMessage={0} not found: {1} URINotSupported=URI not supported: {0} userConfigInvalid=Git config in the user's home directory {0} is invalid {1} validatingGitModules=Validating .gitmodules files +verifySignatureBad=BAD signature from "{0}" +verifySignatureExpired=Expired signature from "{0}" +verifySignatureGood=Good signature from "{0}" +verifySignatureIssuer=issuer "{0}" +verifySignatureKey=using key {0} +verifySignatureMade=Signature made {0} +verifySignatureTrust=[{0}] walkFailure=Walk failure. wantNoSpaceWithCapabilities=No space between oid and first capability in first want line wantNotValid=want {0} not valid 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 64314772b..3b3e10e7b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, Christian Halstrick - * Copyright (C) 2010, Chris Aniszczyk and others + * Copyright (C) 2010, 2021 Chris Aniszczyk 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 @@ -772,6 +772,16 @@ public RemoteSetUrlCommand remoteSetUrl() { return new RemoteSetUrlCommand(repo); } + /** + * Return a command to verify signatures of tags or commits. + * + * @return a {@link VerifySignatureCommand} + * @since 5.11 + */ + public VerifySignatureCommand verifySignature() { + return new VerifySignatureCommand(repo); + } + /** * Get repository * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java new file mode 100644 index 000000000..21cddf75b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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 org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.revwalk.RevObject; + +/** + * A {@code VerificationResult} describes the outcome of a signature + * verification. + * + * @see VerifySignatureCommand + * + * @since 5.11 + */ +public interface VerificationResult { + + /** + * If an error occurred during signature verification, this retrieves the + * exception. + * + * @return the exception, or {@code null} if none occurred + */ + Throwable getException(); + + /** + * Retrieves the signature verification result. + * + * @return the result, or {@code null} if none was computed + */ + GpgSignatureVerifier.SignatureVerification getVerification(); + + /** + * Retrieves the git object of which the signature was verified. + * + * @return the git object + */ + RevObject getObject(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java new file mode 100644 index 000000000..6a2a44ea2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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 java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.ServiceUnavailableException; +import org.eclipse.jgit.api.errors.WrongObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GpgConfig; +import org.eclipse.jgit.lib.GpgSignatureVerifier; +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.GpgSignatureVerifierFactory; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * A command to verify GPG signatures on tags or commits. + * + * @since 5.11 + */ +public class VerifySignatureCommand extends GitCommand> { + + /** + * Describes what kind of objects shall be handled by a + * {@link VerifySignatureCommand}. + */ + public enum VerifyMode { + /** + * Handle any object type, ignore anything that is not a commit or tag. + */ + ANY, + /** + * Handle only commits; throw a {@link WrongObjectTypeException} for + * anything else. + */ + COMMITS, + /** + * Handle only tags; throw a {@link WrongObjectTypeException} for + * anything else. + */ + TAGS + } + + private final Set namesToCheck = new HashSet<>(); + + private VerifyMode mode = VerifyMode.ANY; + + private GpgSignatureVerifier verifier; + + private GpgConfig config; + + private boolean ownVerifier; + + /** + * Creates a new {@link VerifySignatureCommand} for the given {@link Repository}. + * + * @param repo + * to operate on + */ + public VerifySignatureCommand(Repository repo) { + super(repo); + } + + /** + * Add a name of an object (SHA-1, ref name; anything that can be + * {@link Repository#resolve(String) resolved}) to the command to have its + * signature verified. + * + * @param name + * to add + * @return {@code this} + */ + public VerifySignatureCommand addName(String name) { + checkCallable(); + namesToCheck.add(name); + return this; + } + + /** + * Add names of objects (SHA-1, ref name; anything that can be + * {@link Repository#resolve(String) resolved}) to the command to have their + * signatures verified. + * + * @param names + * to add; duplicates will be ignored + * @return {@code this} + */ + public VerifySignatureCommand addNames(String... names) { + checkCallable(); + namesToCheck.addAll(Arrays.asList(names)); + return this; + } + + /** + * Add names of objects (SHA-1, ref name; anything that can be + * {@link Repository#resolve(String) resolved}) to the command to have their + * signatures verified. + * + * @param names + * to add; duplicates will be ignored + * @return {@code this} + */ + public VerifySignatureCommand addNames(Collection names) { + checkCallable(); + namesToCheck.addAll(names); + return this; + } + + /** + * Sets the mode of operation for this command. + * + * @param mode + * the {@link VerifyMode} to set + * @return {@code this} + */ + public VerifySignatureCommand setMode(@NonNull VerifyMode mode) { + checkCallable(); + this.mode = mode; + return this; + } + + /** + * Sets the {@link GpgSignatureVerifier} to use. + * + * @param verifier + * the {@link GpgSignatureVerifier} to use, or {@code null} to + * use the default verifier + * @return {@code this} + */ + public VerifySignatureCommand setVerifier(GpgSignatureVerifier verifier) { + checkCallable(); + this.verifier = verifier; + return this; + } + + /** + * Sets an external {@link GpgConfig} to use. Whether it will be used it at + * the discretion of the {@link #setVerifier(GpgSignatureVerifier)}. + * + * @param config + * to set; if {@code null}, the config will be loaded from the + * git config of the repository + * @return {@code this} + * @since 5.11 + */ + public VerifySignatureCommand setGpgConfig(GpgConfig config) { + checkCallable(); + this.config = config; + return this; + } + + /** + * Retrieves the currently set {@link GpgSignatureVerifier}. Can be used + * after a successful {@link #call()} to get the verifier that was used. + * + * @return the {@link GpgSignatureVerifier} + */ + public GpgSignatureVerifier getVerifier() { + return verifier; + } + + /** + * {@link Repository#resolve(String) Resolves} all names added to the + * command to git objects and verifies their signature. Non-existing objects + * are ignored. + *

+ * Depending on the {@link #setMode(VerifyMode)}, only tags or commits or + * any kind of objects are allowed. + *

+ *

+ * Unsigned objects are silently skipped. + *

+ * + * @return a map of the given names to the corresponding + * {@link VerificationResult}, excluding ignored or skipped objects. + * @throws ServiceUnavailableException + * if no {@link GpgSignatureVerifier} was set and no + * {@link GpgSignatureVerifierFactory} is available + * @throws WrongObjectTypeException + * if a name resolves to an object of a type not allowed by the + * {@link #setMode(VerifyMode)} mode + */ + @Override + @NonNull + public Map call() + throws ServiceUnavailableException, WrongObjectTypeException { + checkCallable(); + setCallable(false); + Map result = new HashMap<>(); + if (verifier == null) { + GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory + .getDefault(); + if (factory == null) { + throw new ServiceUnavailableException( + JGitText.get().signatureVerificationUnavailable); + } + verifier = factory.getVerifier(); + ownVerifier = true; + } + if (config == null) { + config = new GpgConfig(repo.getConfig()); + } + try (RevWalk walk = new RevWalk(repo)) { + for (String toCheck : namesToCheck) { + ObjectId id = repo.resolve(toCheck); + if (id != null && !ObjectId.zeroId().equals(id)) { + RevObject object; + try { + object = walk.parseAny(id); + } catch (MissingObjectException e) { + continue; + } + VerificationResult verification = verifyOne(object); + if (verification != null) { + result.put(toCheck, verification); + } + } + } + } catch (IOException e) { + throw new JGitInternalException( + JGitText.get().signatureVerificationError, e); + } finally { + if (ownVerifier) { + verifier.clear(); + } + } + return result; + } + + private VerificationResult verifyOne(RevObject object) + throws WrongObjectTypeException, IOException { + int type = object.getType(); + if (VerifyMode.TAGS.equals(mode) && type != Constants.OBJ_TAG) { + throw new WrongObjectTypeException(object, Constants.OBJ_TAG); + } else if (VerifyMode.COMMITS.equals(mode) + && type != Constants.OBJ_COMMIT) { + throw new WrongObjectTypeException(object, Constants.OBJ_COMMIT); + } + if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) { + try { + GpgSignatureVerifier.SignatureVerification verification = verifier + .verifySignature(object, config); + if (verification == null) { + // Not signed + return null; + } + // Create new result + return new Result(object, verification, null); + } catch (JGitInternalException e) { + return new Result(object, null, e); + } + } + return null; + } + + private static class Result implements VerificationResult { + + private final Throwable throwable; + + private final SignatureVerification verification; + + private final RevObject object; + + public Result(RevObject object, SignatureVerification verification, + Throwable throwable) { + this.object = object; + this.verification = verification; + this.throwable = throwable; + } + + @Override + public Throwable getException() { + return throwable; + } + + @Override + public SignatureVerification getVerification() { + return verification; + } + + @Override + public RevObject getObject() { + return object; + } + + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongObjectTypeException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongObjectTypeException.java new file mode 100644 index 000000000..f639c2f83 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/WrongObjectTypeException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** + * A given object is not of an expected object type. + * + * @since 5.11 + */ +public class WrongObjectTypeException extends GitAPIException { + + private static final long serialVersionUID = 1L; + + private String name; + + private int type; + + /** + * Construct a {@link WrongObjectTypeException} for the specified object id, + * giving the expected type. + * + * @param id + * {@link ObjectId} of the object with the unexpected type + * @param type + * expected object type code; see + * {@link Constants}{@code .OBJ_*}. + */ + public WrongObjectTypeException(ObjectId id, int type) { + super(MessageFormat.format(JGitText.get().objectIsNotA, id.name(), + Constants.typeString(type))); + this.name = id.name(); + this.type = type; + } + + /** + * Retrieves the name (SHA-1) of the object. + * + * @return the name + */ + public String getObjectId() { + return name; + } + + /** + * Retrieves the expected type code. See {@link Constants}{@code .OBJ_*}. + * + * @return the type code + */ + public int getExpectedType() { + return type; + } +} 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 07fb59ddf..aaba8d624 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, 2013 Sasa Zivkov - * Copyright (C) 2012, Research In Motion Limited and others + * Copyright (C) 2012, 2021 Research In Motion Limited 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 @@ -650,6 +650,8 @@ public static JGitText get() { /***/ public String shortReadOfBlock; /***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes; /***/ public String shortSkipOfBlock; + /***/ public String signatureVerificationError; + /***/ public String signatureVerificationUnavailable; /***/ public String signedTagMessageNoLf; /***/ public String signingServiceUnavailable; /***/ public String similarityScoreMustBeWithinBounds; @@ -791,6 +793,13 @@ public static JGitText get() { /***/ public String URINotSupported; /***/ public String userConfigInvalid; /***/ public String validatingGitModules; + /***/ public String verifySignatureBad; + /***/ public String verifySignatureExpired; + /***/ public String verifySignatureGood; + /***/ public String verifySignatureIssuer; + /***/ public String verifySignatureKey; + /***/ public String verifySignatureMade; + /***/ public String verifySignatureTrust; /***/ public String walkFailure; /***/ public String wantNoSpaceWithCapabilities; /***/ public String wantNotValid; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java new file mode 100644 index 000000000..a7a39c998 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.lib; + +import java.io.IOException; +import java.util.Date; + +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.revwalk.RevObject; + +/** + * A {@code GpgVerifier} can verify GPG signatures on git commits and tags. + * + * @since 5.11 + */ +public interface GpgSignatureVerifier { + + /** + * Verifies the signature on a signed commit or tag. + * + * @param object + * to verify + * @param config + * the {@link GpgConfig} to use + * @return a {@link SignatureVerification} describing the outcome of the + * verification, or {@code null} if the object was not signed + * @throws IOException + * if an error occurs getting a public key + * @throws org.eclipse.jgit.api.errors.JGitInternalException + * if signature verification fails + */ + @Nullable + SignatureVerification verifySignature(@NonNull RevObject object, + @NonNull GpgConfig config) throws IOException; + + + /** + * Verifies a given signature for given data. + * + * @param data + * the signature is for + * @param signatureData + * the ASCII-armored signature + * @return a {@link SignatureVerification} describing the outcome + * @throws IOException + * if the signature cannot be parsed + * @throws JGitInternalException + * if signature verification fails + */ + public SignatureVerification verify(byte[] data, byte[] signatureData) + throws IOException; + + /** + * Retrieves the name of this verifier. This should be a short string + * identifying the engine that verified the signature, like "gpg" if GPG is + * used, or "bc" for a BouncyCastle implementation. + * + * @return the name + */ + @NonNull + String getName(); + + /** + * A {@link GpgSignatureVerifier} may cache public keys to speed up + * verifying signatures on multiple objects. This clears this cache, if any. + */ + void clear(); + + /** + * A {@code SignatureVerification} returns data about a (positively or + * negatively) verified signature. + */ + interface SignatureVerification { + + // Data about the signature. + + @NonNull + Date getCreationDate(); + + // Data from the signature used to find a public key. + + /** + * Obtains the signer as stored in the signature, if known. + * + * @return the signer, or {@code null} if unknown + */ + String getSigner(); + + /** + * Obtains the short or long fingerprint of the public key as stored in + * the signature, if known. + * + * @return the fingerprint, or {@code null} if unknown + */ + String getKeyFingerprint(); + + // Some information about the found public key. + + /** + * Obtains the OpenPGP user ID associated with the key. + * + * @return the user id, or {@code null} if unknown + */ + String getKeyUser(); + + /** + * Tells whether the public key used for this signature verification was + * expired when the signature was created. + * + * @return {@code true} if the key was expired already, {@code false} + * otherwise + */ + boolean isExpired(); + + /** + * Obtains the trust level of the public key used to verify the + * signature. + * + * @return the trust level + */ + @NonNull + TrustLevel getTrustLevel(); + + // The verification result. + + /** + * Tells whether the signature verification was successful. + * + * @return {@code true} if the signature was verified successfully; + * {@code false} if not. + */ + boolean getVerified(); + + /** + * Obtains a human-readable message giving additional information about + * the outcome of the verification. + * + * @return the message, or {@code null} if none set. + */ + String getMessage(); + } + + /** + * The owner's trust in a public key. + */ + enum TrustLevel { + UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java new file mode 100644 index 000000000..4b1dbedeb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.lib; + +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@code GpgSignatureVerifierFactory} creates {@link GpgSignatureVerifier} instances. + * + * @since 5.11 + */ +public abstract class GpgSignatureVerifierFactory { + + private static final Logger LOG = LoggerFactory + .getLogger(GpgSignatureVerifierFactory.class); + + private static volatile GpgSignatureVerifierFactory defaultFactory = loadDefault(); + + private static GpgSignatureVerifierFactory loadDefault() { + try { + ServiceLoader loader = ServiceLoader + .load(GpgSignatureVerifierFactory.class); + Iterator iter = loader.iterator(); + if (iter.hasNext()) { + return iter.next(); + } + } catch (ServiceConfigurationError e) { + LOG.error(e.getMessage(), e); + } + return null; + } + + /** + * Retrieves the default factory. + * + * @return the default factory or {@code null} if none set + */ + public static GpgSignatureVerifierFactory getDefault() { + return defaultFactory; + } + + /** + * Sets the default factory. + * + * @param factory + * the new default factory + */ + public static void setDefault(GpgSignatureVerifierFactory factory) { + defaultFactory = factory; + } + + /** + * Creates a new {@link GpgSignatureVerifier}. + * + * @return the new {@link GpgSignatureVerifier} + */ + public abstract GpgSignatureVerifier getVerifier(); + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java index 3499136f9..b9d145008 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java @@ -1,7 +1,7 @@ /* - * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, 2009, Google Inc. * Copyright (C) 2008, Marek Zawirski - * Copyright (C) 2008, Shawn O. Pearce and others + * Copyright (C) 2008, 2021, Shawn O. Pearce 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 @@ -343,6 +343,22 @@ public final String getTagName() { return tagName; } + /** + * Obtain the raw unparsed tag body (NOTE - THIS IS NOT A COPY). + *

+ * This method is exposed only to provide very fast, efficient access to + * this tag's message buffer. Applications relying on this buffer should be + * very careful to ensure they do not modify its contents during their use + * of it. + * + * @return the raw unparsed tag body. This is NOT A COPY. Do not + * alter the returned array. + * @since 5.11 + */ + public final byte[] getRawBuffer() { + return buffer; + } + /** * Discard the message buffer to reduce memory usage. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java new file mode 100644 index 000000000..cf06172c1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.util.Locale; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification; +import org.eclipse.jgit.lib.GpgSignatureVerifier.TrustLevel; +import org.eclipse.jgit.lib.PersonIdent; + +/** + * Utilities for signature verification. + * + * @since 5.11 + */ +public final class SignatureUtils { + + private SignatureUtils() { + // No instantiation + } + + /** + * Writes information about a signature verification to a string. + * + * @param verification + * to show + * @param creator + * of the object verified; used for time zone information + * @param formatter + * to use for dates + * @return a textual representation of the {@link SignatureVerification}, + * using LF as line separator + */ + public static String toString(SignatureVerification verification, + PersonIdent creator, GitDateFormatter formatter) { + StringBuilder result = new StringBuilder(); + // Use the creator's timezone for the signature date + PersonIdent dateId = new PersonIdent(creator, + verification.getCreationDate()); + result.append(MessageFormat.format(JGitText.get().verifySignatureMade, + formatter.formatDate(dateId))); + result.append('\n'); + result.append(MessageFormat.format( + JGitText.get().verifySignatureKey, + verification.getKeyFingerprint().toUpperCase(Locale.ROOT))); + result.append('\n'); + if (!StringUtils.isEmptyOrNull(verification.getSigner())) { + result.append( + MessageFormat.format(JGitText.get().verifySignatureIssuer, + verification.getSigner())); + result.append('\n'); + } + String msg; + if (verification.getVerified()) { + if (verification.isExpired()) { + msg = JGitText.get().verifySignatureExpired; + } else { + msg = JGitText.get().verifySignatureGood; + } + } else { + msg = JGitText.get().verifySignatureBad; + } + result.append(MessageFormat.format(msg, verification.getKeyUser())); + if (!TrustLevel.UNKNOWN.equals(verification.getTrustLevel())) { + result.append(' ' + MessageFormat + .format(JGitText.get().verifySignatureTrust, verification + .getTrustLevel().name().toLowerCase(Locale.ROOT))); + } + result.append('\n'); + msg = verification.getMessage(); + if (!StringUtils.isEmptyOrNull(msg)) { + result.append(msg); + result.append('\n'); + } + return result.toString(); + } +} From 64cbea8a9794047fe576d03ab8a46e4eaf7eabee Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 17 Jan 2021 16:21:28 +0100 Subject: [PATCH 22/35] GPG: compute the keygrip to find a secret key The gpg-agent stores secret keys in individual files in the secret key directory private-keys-v1.d. The files have the key's keygrip (in upper case) as name and extension ".key". A keygrip is a SHA1 hash over the parameters of the public key. By computing this keygrip, we can pre-compute the expected file name and then check only that one file instead of having to iterate over all keys stored in that directory. This file naming scheme is actually an implementation detail of gpg-agent. It is unlikely to change, though. The keygrip itself is computed via libgcrypt and will remain stable according to the GPG main author.[1] Add an implementation for calculating the keygrip and include tests. Do not iterate over files in BouncyCastleGpgKeyLocator but only check the single file identified by the keygrip. Ideally upstream BouncyCastle would provide such a getKeyGrip() method. But as it re-builds GPG and libgcrypt internals, it's doubtful it would be included there, and since BouncyCastle even lacks a number of curve OIDs for ed25519/curve25519 and uses the short-Weierstrass parameters instead of the more common Montgomery parameters, including it there might be quite a bit of work. [1] http://gnupg.10057.n7.nabble.com/GnuPG-2-1-x-and-2-2-x-keyring-formats-tp54146p54154.html Bug: 547536 Change-Id: I30022a0e7b33b1bf35aec1222f84591f0c30ddfd Signed-off-by: Thomas Wolf --- lib/BUILD | 2 + org.eclipse.jgit.gpg.bc.test/.classpath | 9 +- org.eclipse.jgit.gpg.bc.test/.gitignore | 1 + org.eclipse.jgit.gpg.bc.test/BUILD | 20 ++ .../META-INF/MANIFEST.MF | 12 +- org.eclipse.jgit.gpg.bc.test/build.properties | 2 +- org.eclipse.jgit.gpg.bc.test/pom.xml | 6 + .../gpg/bc/internal/keys/brainpool256.asc | Bin 0 -> 701 bytes .../gpg/bc/internal/keys/brainpool384.asc | Bin 0 -> 872 bytes .../gpg/bc/internal/keys/brainpool512.asc | Bin 0 -> 1046 bytes .../jgit/gpg/bc/internal/keys/dsa-elgamal.asc | Bin 0 -> 2627 bytes .../jgit/gpg/bc/internal/keys/ed25519.asc | Bin 0 -> 372 bytes .../jgit/gpg/bc/internal/keys/nistp256.asc | Bin 0 -> 697 bytes .../jgit/gpg/bc/internal/keys/nistp384.asc | Bin 0 -> 859 bytes .../jgit/gpg/bc/internal/keys/nistp521.asc | Bin 0 -> 1050 bytes .../eclipse/jgit/gpg/bc/internal/keys/rsa.asc | Bin 0 -> 2407 bytes .../jgit/gpg/bc/internal/keys/secp256k1.asc | Bin 0 -> 689 bytes .../jgit/gpg/bc/internal/keys/x25519.asc | Bin 0 -> 612 bytes .../bc/internal/keys/KeyGrip25519Test.java | 61 ++++ .../gpg/bc/internal/keys/KeyGripTest.java | 143 ++++++++ org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF | 13 +- .../jgit/gpg/bc/internal/BCText.properties | 8 +- .../eclipse/jgit/gpg/bc/internal/BCText.java | 6 + .../internal/BouncyCastleGpgKeyLocator.java | 112 +++--- .../jgit/gpg/bc/internal/keys/KeyGrip.java | 322 ++++++++++++++++++ 25 files changed, 650 insertions(+), 67 deletions(-) create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java create mode 100644 org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java diff --git a/lib/BUILD b/lib/BUILD index b56ba2f66..8ad49d450 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -162,6 +162,7 @@ java_library( "//org.eclipse.jgit:__pkg__", "//org.eclipse.jgit.gpg.bc:__pkg__", "//org.eclipse.jgit.test:__pkg__", + "//org.eclipse.jgit.gpg.bc.test:__pkg__", ], exports = ["@bcpg//jar"], ) @@ -172,6 +173,7 @@ java_library( "//org.eclipse.jgit:__pkg__", "//org.eclipse.jgit.gpg.bc:__pkg__", "//org.eclipse.jgit.test:__pkg__", + "//org.eclipse.jgit.gpg.bc.test:__pkg__", ], exports = ["@bcprov//jar"], ) diff --git a/org.eclipse.jgit.gpg.bc.test/.classpath b/org.eclipse.jgit.gpg.bc.test/.classpath index f08af0a4e..0acccbaae 100644 --- a/org.eclipse.jgit.gpg.bc.test/.classpath +++ b/org.eclipse.jgit.gpg.bc.test/.classpath @@ -2,10 +2,15 @@ - + - + + + + + + diff --git a/org.eclipse.jgit.gpg.bc.test/.gitignore b/org.eclipse.jgit.gpg.bc.test/.gitignore index 934e0e06f..8b6760c93 100644 --- a/org.eclipse.jgit.gpg.bc.test/.gitignore +++ b/org.eclipse.jgit.gpg.bc.test/.gitignore @@ -1,2 +1,3 @@ /bin +/bin-tst /target diff --git a/org.eclipse.jgit.gpg.bc.test/BUILD b/org.eclipse.jgit.gpg.bc.test/BUILD index 1e3677d92..59859b283 100644 --- a/org.eclipse.jgit.gpg.bc.test/BUILD +++ b/org.eclipse.jgit.gpg.bc.test/BUILD @@ -1,3 +1,8 @@ +load( + "@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", + "genrule2", +) +load("@rules_java//java:defs.bzl", "java_import") load( "@com_googlesource_gerrit_bazlets//tools:junit.bzl", "junit_tests", @@ -8,7 +13,22 @@ junit_tests( srcs = glob(["tst/**/*.java"]), tags = ["bc"], deps = [ + "//lib:bcpg", + "//lib:bcprov", "//lib:junit", "//org.eclipse.jgit.gpg.bc:gpg-bc", + "//org.eclipse.jgit.gpg.bc.test:tst_rsrc", ], ) + +java_import( + name = "tst_rsrc", + jars = [":tst_rsrc_jar"], +) + +genrule2( + name = "tst_rsrc_jar", + srcs = glob(["tst-rsrc/**"]), + outs = ["tst_rsrc.jar"], + cmd = "o=$$PWD/$@ && tar cf - $(SRCS) | tar -C $$TMP --strip-components=2 -xf - && cd $$TMP && zip -qr $$o .", +) 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 35a418c5a..39ece1fcf 100644 --- a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF @@ -7,8 +7,16 @@ Bundle-Version: 5.11.0.qualifier Bundle-Vendor: %Bundle-Vendor Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.eclipse.jgit.gpg.bc.internal;version="[5.11.0,5.12.0)", - org.junit;version="[4.13,5.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.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="[5.11.0,5.12.0)", + org.eclipse.jgit.gpg.bc.internal.keys;version="[5.11.0,5.12.0)", + org.eclipse.jgit.util.sha1;version="[5.11.0,5.12.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.gpg.bc.internal;x-internal:=true Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" diff --git a/org.eclipse.jgit.gpg.bc.test/build.properties b/org.eclipse.jgit.gpg.bc.test/build.properties index 9ffa0caf7..e36d6667e 100644 --- a/org.eclipse.jgit.gpg.bc.test/build.properties +++ b/org.eclipse.jgit.gpg.bc.test/build.properties @@ -1,5 +1,5 @@ source.. = tst/ -output.. = bin/ +output.. = bin-tst/ bin.includes = META-INF/,\ .,\ plugin.properties diff --git a/org.eclipse.jgit.gpg.bc.test/pom.xml b/org.eclipse.jgit.gpg.bc.test/pom.xml index f244fb476..cac7e151e 100644 --- a/org.eclipse.jgit.gpg.bc.test/pom.xml +++ b/org.eclipse.jgit.gpg.bc.test/pom.xml @@ -85,6 +85,12 @@ src/ tst/ + + + tst-rsrc/ + + + org.apache.maven.plugins diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc new file mode 100644 index 0000000000000000000000000000000000000000..8427cfcc0555a1385dad05283e3d21006ba94388 GIT binary patch literal 701 zcmaJ<$*!V60KD@R-pvc>h~mLRH{C$f>^?xesUVxm_Gp%`AK%O*bC_i6Sd~;&D)sU4 z2E;0{7LMvH93L7)UQiq@pkd4OTm9g;r()u;vk~)5olY(S10lf08c0p-8upmy+L;fw z>_^Gk?E3P)KI>&=J3-wuMD2XyQr(u%I~_0aXpq&xROF!~a3F}J1j5XxvYQ$pb;EZf2(k?V zSpt+CV^}!KeyYw~vC^KteQX6YFV^gpMna{4#~5^!068t)zkCfmfOxN(Rg}jXFI<$z znD|@}S~@M9{4#D?tTlZr#tl63UX~HSuR6znO@J_PIuW$$GrFw^^k}{db+IKocAU5t zdZPKT^j^9B8I$}oCf*0J6q7>J%cs}zl4;Bv+TMp*gp|wrSF`#Q##g-+ZG6^aK?p{$ z4E$4!eeHfhN%nPQyL^`GX7jk7RJp{eV(r|)gfd77mjE}yLkb8WR|KOBf@mJ^w?>Wj zp7=mzvqz-bx8g>%D0KB(aFuHSDji$DdG1>f%n0!7TD&!4_biT#A|9({)w#plsZ6G{ zb9-DPWF$E5n8bjyFP5H0PwTG-7YfpRy-cGD1-!h-T2Ey~$_*zWo20FB{yrQI+<|qM N4fpGM@8tjd@+ZKw-Q@rP literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc new file mode 100644 index 0000000000000000000000000000000000000000..bdb20fe939744e44c9af55d5d4d45c777accf982 GIT binary patch literal 872 zcmaKrxpJdG07P@X!nw=EB^wAfm>C8%2*f4Ck%?;n36KQh`ueqZgUz&Tt@EgYMx zzR^_MnPfWh7;CdN(ayVo_~nk^c8djTj61N|s!vAX zp74AH4j?I*0~5J)&j4!GfMQ*9djo+1K^P-2W3;Ch(sm#uu9w#J*`m!hwnyoRZGMo) ztv@I2*wX+pkrvJ`3J%DF|3Jg9AW|GYI7?c5*8}44;>B^e{1YznKL1{-FP1#o#4uz+ z$7ChD@*5{F1ksgGB@I_w;4NW>CEtgQJ*a+Tf3~WLp3-^;n)XEuKo{w2^n*fS+;)`) zQ`y;}>h9ioHOsn8(e-+7jvkRJ5o%Ah(twZr;reX`A%G#8EfwNZaDC`cM!L(_NXT1J zZnkZSyo!IfO{r^R{!31ZURldoO}llGVP98{l29jX^r$V$lO*eYIWviRi766}nrml- zP2~%%L?>&W5lt={O47hnCPD4Y;EpsoNf*_a@nbHICtyM3wuEVc8~>;w-)dACS5SK$ zj9^8MEyUu$xl*{TqFtTai%>(?$j+&X0O$>@|LvI{KzRboy3Ajso^x}zA=AZf{*HdF7zANFBVV$ literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc new file mode 100644 index 0000000000000000000000000000000000000000..5b4bca2c67c21ef74fc8980af295fc1a22345f71 GIT binary patch literal 1046 zcma*mJF}un6b0a%UvYBk6rg|*Owir5*dUMQrI-OKC~rX!e*K)8Xrfzpg?mN2zFObk zKY>^$_Lr->Uv3DG1bvw}{(?sbnSa`EhN%q;p9TjPAImTos)3mhK(P&hg~3KyDy?a@ zM{hUEn6|T|4J+#E5U|U;7<*lEls?zhEABCRkz3THD)zUr$Tv~ ziMpjkM^MNc*757bbn7h>ClN_onMbGS885;k)54F{K0^qt0Oddd#h^ht7zkttECc}> zM<_1|nhD~`*;G%Ww2-ty4bFma+pki zmam0xpE>Pp?u?jJ`Q`lCg?doBVzJ#*|FNLZD54HkQKTngHG9b{`UjksFUA0t2J##N z%40BlXv94&alTURW9#V2U*AtY(WmCXY6r7&#YpbdqY;rKAH4U%`@+E0<;C0Gn=Zj- zT2YWq(-m{NtaIZBUj~m;71ha#w2V%UYjG`#%CSrUzj;9`yWI<>RA*CT9Tjie1Gd=_ zj@zZEhl#g{r;@unKLi~OZ%OLJQ%E?DT?V)b_&A{!M0rh^_YW+dTR;Fwwf{$%|4YgI zF}~0rN>2>MB4(s^pXuAC*0SxUGKZ{EkXaU=)Vx8cZ`jfe`3jgTmcAbYDaIjRY=zibF*K# zbI+3o{ywO-HH0!hLIrwBYfEteJSj9$A%^re1ZLru z&&Qe@?29U*zSuCn>)4BMPE1XeEI}5ac$%bFB=z2cC%m3L72{00kgEOheJAZVe9OK{ zIYb!v#VGZf(CwN8E|q?H6B#&?8BG3HA?38)`bOsVAb++hL@ zm?Bo+l=kvY8`mV@6Ct+7LZgVIQ+I|YL9VzkCTYTzGm)6j_(xAo1RP;A@4aP{1V+G; zqQEU$I(c*`CLpij;s~Q^J%hoC_3%Zf#9Sj(4`-9@g1$Y`S*R)eo}GF`uY&|ZlXqiS zVWM#CkLiUxgO79ak$afp!>Y4*?+fwGQT}Zo3!cmA^b4=6UjfC%QM200Edv+Gp&FhY zrY%;9Xn95-&q9qvLp@>L*z#PqdSOmYdRq^hfsy@4ef>VJwA16{cM~Q(mB}DRU==9Q zM?8U?Lvy~4&$rY}pa}V~*xPfKebXDd_Wd~v%xKh<>L4*;H#^H__}eR5`CZXfO;03) z`J8pFo%rh4MS1RUVl8m*ApgoBHN+P#NiLKXMozWu!{+z(JedI43b~i>4=w0ypRH?GZ&&?V|9U4w!% zjtXJ=vYUuVlYuzq4B)AeObd~Gg7xjjXm8^wX>cJkCkV-sm2r{d)kgLW&@FY z#gwOGBuQ{j>7H)pz&P}zWAoA_y^N~Cu7M1` zXYhM0K&U$B|M>Z?5*fafa-^+!6#N0A$xOJg9N9eDzr6!ztaz4VxV4bH7;uj~(3&ap zw1rPd_{XMqkrBnx2p=S2&-iHMLBMOykjdwp0m4^?>#p_r�F5%jDFx1Xi58wiBD#OwKKY3uYUDY^F=hwUEY06~j##Ucxfdz0QT zH<1a9z;AR@?Xo3gXiOd(50q!pC*!QkNj4A?1DEN%D7%p`i-;;FCkzDAq;8-+YQyN` zCP&Nkz17S7NxkTi0dV*bYcZ9=IOEIMzAr@Mct?+b(ov*p=urCfA9RXL>?k;Mlece3 zCB8c{txg^8hu?7CBXzD|OE&Zk5Ih6gtL;#YvN2teX7gJ3c^x_b>MAuIbUCzRf4pC$ zyj~LyYr=S@CWA@BSpGdaHFYN;_+^&;fC94v9|46?<&7-PQUitIhb78B`_`E%7OgD} z)$WOb#eS)@N`b}n6^wIFAJSqP49aD);vkMod|kT`Y(0ZTvIu!VD%~QunqG!!7qIIc$vu6vxc-kUN5l;)js_LRV5n}g$KppFL=2YI1G=U z(T0IN!9~^k?w7c&L-=d*bL%&b0Q1v)l{OBAK}ZSWRP_35Fu$kPLFI!%Osc89g;mA9 zpFicWWOgE2uR6INu->EwIAY=asGAW1rchFo>kYFjxrE{UNk3RYCFkJjW8R!V!_2C- zhL<`kKMEFcp?8>r_dTL~zwIsWqy+Nuw^3p+9-$)h{qNu}W<(WOsSB-R=%v*Z&sTps z3RIw8jQ=GW@^4W_Q~wWTg7kJIF#WdBpM|4rQU}>D4YT60w(oEI-L7*p0L=_BBYc&~o=Xz`RXu^i=hb{mIwUXjwhl8pwaxgVG`YEdV7`{5R_G FKLM+=SMUG; literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc new file mode 100644 index 0000000000000000000000000000000000000000..636a5a95f3f48270fb90b0dbe8b55d79a8b0a8c4 GIT binary patch literal 372 zcmaKoyLN&=6h-^_3VN3dd?6MVcP=Oc@-%>B2aG`wg@_6G_c3YGWtV5K)9ms11vR$J zmHe$Q`4QbQlx%}bbmyV{W5oK2Z)jlUaVon2jb%80JE=DS za^*Z`VH9PH;i1l~L^U`0oqs6dU5A`I-2wP=RvufwGja&@tDTwC$?noOSVv?P-Xm+z jk2j}I z0?6%i(~j6+c^cXlViZ1wcderc0%{nI%;>|hMm{Y40eV5|;Bi5d(BWE-Z`S=i@HCFU z^`4W~d$<{l>Kp|K^=9D8A7H^2s9fwAJ{TNg>I#5#639|f_p2dOw48ZQYf`QmcieEnU-dHy>L0c!6yJw5K39#Z%&gyi%hLa5u3?U5G&OfU(9EXf_eBjOey0G6l(g?iP_Mgy tHaVB%M3+l-%_cwTo?qvgNl-|4r-rGMwtRkapO*ODa(`}zJ>x&0`~|H}*+KvS literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc new file mode 100644 index 0000000000000000000000000000000000000000..b2b59959e6e5f73141cae12ccb68bb51b07950c6 GIT binary patch literal 859 zcmaKrxsszu00n)%qJ5%62xuW0V^kFd6i0K2+d~`$0)zmmOXBNycAXt-p83+ti2VHg zJ&1Mk{o(1}hZjMcpbraYAJBG@^)LP8xKTgiWOdOEI+`V83=mNP3xg2Z&=}^!ISRWp zZ<0XxxkT+wh{3SbUNIXn!Lp4lPKaRZA>zCGQ@3T4Je%?uZ7^v&$2Nscv4Sg1p3Umy zfygiOm4Dt`OC0Q{vX-Z5IdbU%DstO;5*a#C_NcCb8tA-$^!S>+_srR-(Ahr5FUJHb zLppOb#Cif5Q3)lGgKcCXpW1->37`%Ht*nB!23m*l{7&r&+g0DQ!=@^siKfH7p0yL6 zG%&p46=J|4+WUWi$@Db^dU-u8UCQ!ZZ*D~~lF@)w)!SOBRAO>@o*VUrbDwt_#zEjo zyq`sd_Gs!E+^^Z;1X7tD>^6)j5&J=Qg}795cbIcHFi;+m6AQ;MY+T$;B6s=PpULB= zwJUYe0#mf)Yv=NXT(Y2GvQ=UlE!6h`p^ggOAeq~&8O}?#X@Y#Vjx%?3M_$ds9~L~K zVUfTz%Q8>LuQx15bEKgZTFZTRd3~95x3P_Yvvv~-dU^G;b@yk3TaCINlJyJ&*Wcc! zqmfqVF5TjhwnQw{{si2UD1fS+O30|Ll}Q(@LZYaQMPh@{+f+?Dad z+n?Pzo!cg`pl_D(1>^usCPx4N6cE@*NzaAr9D%)=_MTopz-hf2bRYzM^J<=b^}NSI zGkO(?xGjC>nG($K&AN;uv^StYssm0Avf}X`#KeuVc6Xlwn?4ouj^!zstMMD~zI#W+ lc1X1cb=q9zMA!$GmK0WP@KW~Xeu>6T;Qk8(f2)6~<{xES55oWe literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc new file mode 100644 index 0000000000000000000000000000000000000000..db18f81a2968cb18b3078982c9a31c3269314965 GIT binary patch literal 1050 zcmaKrxsszu07QMhqJ5$R5FmjuMimsOxIqX69+vfBys` z6U=p@Xkh6}N6{BFY)Su@eiKBinJAoXS=~)V=MVytiSXtE!Hy+Oz$@oYhu)j$ zW-LB*=UhT+we|?c-XLjWk_tobvSQiDhIW;V;mdu>Gs?WPXoO;R^gk+ufI4a>O15LQ=R za!Ui0ZU{;f5}@K_P!&L_5%N#=U0eqo953R14QZ?ydii!+`_L=kxd*e<0I6);zjmFF zH>}#H!>2#?ub@fK_X3MmxKn}x>|4c|J{P^@I#a0mA-L)JlcxxtGed%JBLdw}5ke63 z(mpsDG+OI&PObM;^v(Q0h?XEjJAC6zIh96DZtc|BUr44F?x%#~1k#!glR`LKj>VTf zmsR6)%-E-KvOXRieEQVB^743l=30a9Nl|ONbGG%>tinah{!@03q)?E2_}8&Uy5?_-(5k8)=1vS;PuH!NHQZ@ph=CcfSXc@wbj>-^y=diuIx%+ufW9%tl6*-JIsSO#vd5w$TR75+Gvz>o7S#IEPeJar(O>NXe*DW-1O z%%iMH(J3jf(oG3BqXfF-f#03@1=X1TR9N4lR}G&7pSPE&OEXUEaTI2|4_%VV)${v$ zjL}`<1NU=ir(7#uIzyxw%k-pN=KR1>0{(!}WDT;5#7a!BMmY6TFTsc3R~7v@pfn^a z#h$#;pqYN|o)JkflqwV)mp&n{jkAMAWwc}a`Z(gxd|d*@5=$*@8SlJM)n#b;Lw=lY XTxa{~_#*zWkM2qQ{u){G|D66GnrlwU literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc new file mode 100644 index 0000000000000000000000000000000000000000..e74df7a2a9da4086d78d218dd231b0085b8d7a6b GIT binary patch literal 2407 zcmaJ@w-T({0qgmS+cR&-sc=DFG6$Bl9bw5iFJJF#H|{KTQY&fn&p&@5$f{=laaH$^ z8wjQ*{xRg_k6=2Y@gMge2pWKD3(6{0A5xHb@vc~6w7cZg_=mPT(w<3S%Oh!BO=ABw z4e%fASBleqJM0teVGrrk>i3IxtaiaC0UqmS+F8pCUNRGgnx!F85sM-#t{@9cN4T{T zG1Uf?XedqW(N$2RCiZI1(wig&C%1BDv<(b>C*dvQFK^W z^;xkJutCxxRY=$XlSHxG>56mctW>%(zrOp5!JUBTf|f9c^w^j`u*HLiYqz+|zU+vC z;#K3>P%Yu=9doZR5j)Y4WlmBy0k$N=;~uJySB>!b%|wWLj}7GLqDuIZlBTrpI9s2} zg`x3?GIGQ0`|>5`*Y@KQO3d(itNdkcAQRjA(!L3~Q`5MKI*E|zGhnUuq2Bsd0wjZ| zif5XG>n!8B9W-+_yq|PgKnyN@G0XM?uWH{Jm4H=NUz^($f+^eAZ^DH*IO|=60O{~A z&!I-mNVN2k4&&sqJ3aOf8Xcz7Aomee`xP0Dst}*NVDMmc|S>oD?%|hH)%TI6Xbn)x5@&h>G&Orp|l; z_5ss35vn`W%zc~4x({e+{lZ0cCv9v%G$O+(K?LyWpI108sPsVcp^#&(ZAEaz9 zYX~QWY7ZwTRe-m2rzh?o+d!xDkojz+j*xBxi4)gKB@{h6@5fYZxVXo}WA|*?F0&K+ z!b|A#G%bnh?@dJNtFkF zGyrUFoY-vhrSDvXo^P_p+v{iF3OWfjp5yd9vE@X{}&*_ zMFo`RXOU05TIk%Jk@~ErNEyi(dSgnc6rZ4Sv^m09$rf)8@vKj-hz;cXm8|8f$@U#b zoQvmQ|WY6Zf1k6*rsozUx&E%5#_x?r@09RF31^S6>(kjh zeh_wH9jQ{aCb>I(7{5DSkk>PQJztH|a#$uYa)uaup3<>Yoa8bqww>Iy^xFBhuIEFo zCy(Q}XY=`Cr2rvVSXcH%3_ABZmIQRCDjMF`7n;&)R3hf^?<=#RcC$DG%Gg zMLH|kNUHUTS}JBzcCx-tJYfUZuFQsO*~68FhckqIw#IK4O*Sb}$D)&Ih#Ga0M)2FM zdlnwGN-`_L7la2CPUAZ2{z}>n%{-&H;`f(d$WmI+Qv|W4D|mVF05aty+L$iQj5>Gc z=y~sxs4{Hyc*MKv3)!vRRz|0?JVW46^o_;RVhQ;^HB!_?oeyP2gA!_;DTP7t%#jFh zJ&~u0#*!Jq5v%1Y8!-1tdc8JAdCh$@vDyhR(QS)xhZ%}7aUy9I2n?^Hqh&Tb)~TF*u@v3{OMU}iI-iY}GlU=!qtAp-ZCFWB zmX;*?{717&Ej{TZ)S*Cn-Xz)$K6-i)ok`kv_p@2BU{(6NNDF!yMZd#pbFAE_Nt@qG zlb6h`RywK*Tf!+QypaYcv<{!x%gWFoV{=2|uC`G54)%6@WeVJTeh%_C7qyWEj@p5k zPII3B@NMmGP@vvBR!CDj0Af_g9(RcuOM*Wq|Resncj;jY@Yxnm}HvS;Mt!D#robOP-O$TY2H_nKw|Hz>% literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc new file mode 100644 index 0000000000000000000000000000000000000000..837f8a867e434456ea4171dc8dfb6814861383d2 GIT binary patch literal 689 zcmaLV%Z{Qz7zN-xPvPDe6`{q&EdDAAsD|RA&?W8=5D*cOtNrwI$s{xBOy(@l*?l>S z#g8DOQ+wv>?#vC4Me)oe;S5<0Hh-=QL8ypAcfUEH8Hhg=Qqr(`12Dj;>^;Uqh()-h^V&|#Ly_BqF8}*0xH;UHh zr?DbC;ZAtj)XGkDBM9ZFOt7;ob_4-yuSB-p3EgqK+Qjn@Lkq;!suZ`ylsx$(h~~zL zPyyue&}-~BB>fBW5b!bG;wW;$J=lerc49hMh0|B*g&O^^f4rQ7Ddewx65l9)pa26W z_bR%T#9AsbVe;kLQLDp_d2FzQT6d6S14crTluHO@J_g7H$XoUm=ASP6;EM|;ex@#k z@c4;Y$j+_~fmr)RcZGYcQfLr+F^eRG@P;5FutUK>1EZo6_1BL*kE)qo%-!BPi}Tf) z&0dEHwfsp)!%4V7u7xKK$tK7Ru=Dwy0nqCn*{;?JtuxyZ8A8x3BC$e{mGFJ-A| zvK#k|eW%7Jl2<5#^m{|v_d=0V{{l1ymAHK@begoe>DO+`AAY>K2U)`q^0pX4#b^kL z?Y(juooQU3)SZ3RI>eh}D0aYeHU{iQ(nUv1g=b*VT1Z&bU`HFn3q~lzP!Mc#f(->i z%v<&+{K_^!5(IMZ{FZ7*NegTg9SU@URS#w4&+=#$=wKKLkYFr^ 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 static org.junit.Assert.assertEquals; + +import java.math.BigInteger; +import java.util.Locale; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.util.sha1.SHA1; +import org.junit.Test; + +public class KeyGrip25519Test { + + interface Hash { + byte[] hash(SHA1 sha, BigInteger q) throws PGPException; + } + + private void assertKeyGrip(String key, String expectedKeyGrip, Hash hash) + throws Exception { + SHA1 grip = SHA1.newInstance(); + grip.setDetectCollision(false); + BigInteger pk = new BigInteger(key, 16); + byte[] keyGrip = hash.hash(grip, pk); + assertEquals("Keygrip should match", expectedKeyGrip, + Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT)); + } + + @Test + public void testCompressed() throws Exception { + assertKeyGrip("40" + + "773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB", + "9DB6C64A38830F4960701789475520BE8C821F47", + KeyGrip::hashEd25519); + } + + @Test + public void testCompressedNoPrefix() throws Exception { + assertKeyGrip( + "773E72848C1FD5F9652B29E2E7AF79571A04990E96F2016BF4E0EC1890C2B7DB", + "9DB6C64A38830F4960701789475520BE8C821F47", + KeyGrip::hashEd25519); + } + + @Test + public void testCurve25519() throws Exception { + assertKeyGrip("40" + + "918C1733127F6BF2646FAE3D081A18AE77111C903B906310B077505EFFF12740", + "0F89A565D3EA187CE839332398F5D480677DF49C", + KeyGrip::hashCurve25519); + } +} diff --git a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java new file mode 100644 index 000000000..a4aaf40d9 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGripTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.security.Security; +import java.util.Iterator; +import java.util.Locale; +import java.util.function.Consumer; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.util.encoders.Hex; +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; + +@RunWith(Parameterized.class) +public class KeyGripTest { + + @BeforeClass + public static void ensureBC() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + protected static class TestData { + + String filename; + + String[] expectedKeyGrips; + + TestData(String filename, String... keyGrips) { + this.filename = filename; + this.expectedKeyGrips = keyGrips; + } + + @Override + public String toString() { + return filename; + } + } + + @Parameters(name = "{0}") + public static TestData[] initTestData() { + return new TestData[] { + new TestData("rsa.asc", + "D148210FAF36468055B83D0F5A6DEB83FBC8E864", + "A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C"), + new TestData("dsa-elgamal.asc", + "552286BEB2999F0A9E26A50385B90D9724001187", + "CED7034A8EB5F4CE90DF99147EC33D86FCD3296C"), + new TestData("brainpool256.asc", + "A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7", + "C1678B7DE5F144C93B89468D5F9764ACE182ED36"), + new TestData("brainpool384.asc", + "2F25DB025DEBF3EA2715350209B985829B04F50A", + "B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8"), + new TestData("brainpool512.asc", + "5A484F56AB4B8B6583B6365034999F6543FAE1AE", + "9133E4A7E8FC8515518DF444C3F2F247EEBBADEC"), + new TestData("nistp256.asc", + "FC81AECE90BCE6E54D0D637D266109783AC8DAC0", + "A56DC8DB8355747A809037459B4258B8A743EAB5"), + new TestData("nistp384.asc", + "A1338230AED1C9C125663518470B49056C9D1733", + "797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF"), + new TestData("nistp521.asc", + "D91B789603EC9138AA20342A2B6DC86C81B70F5D", + "FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA"), + new TestData("secp256k1.asc", + "498B89C485489BA16B40755C0EBA580166393074", + "48FFED40D018747363BDEFFDD404D1F4870F8064"), + new TestData("ed25519.asc", + "940D97D75C306D737A59A98EAFF1272832CEDC0B"), + new TestData("x25519.asc", + "A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE", + "636C983EDB558527BA82780B52CB5DAE011BE46B") + }; + } + + // Injected by JUnit + @Parameter + public TestData data; + + private void readAsc(InputStream in, Consumer process) + throws IOException, PGPException { + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator()); + + Iterator keyRings = pgpPub.getKeyRings(); + while (keyRings.hasNext()) { + PGPPublicKeyRing keyRing = keyRings.next(); + + Iterator keys = keyRing.getPublicKeys(); + while (keys.hasNext()) { + process.accept(keys.next()); + } + } + } + + @Test + public void testGrip() throws Exception { + try (InputStream in = this.getClass() + .getResourceAsStream(data.filename)) { + int index[] = { 0 }; + readAsc(in, key -> { + byte[] keyGrip = null; + try { + keyGrip = KeyGrip.getKeyGrip(key); + } catch (PGPException e) { + throw new RuntimeException(e); + } + assertTrue("More keys than expected", + index[0] < data.expectedKeyGrips.length); + assertEquals("Wrong keygrip", data.expectedKeyGrips[index[0]++], + Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT)); + }); + assertEquals("Missing keys", data.expectedKeyGrips.length, + index[0]); + } + } +} diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF index e5f432dc5..b379a2b47 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -8,12 +8,19 @@ Bundle-Vendor: %Bundle-Vendor Bundle-Localization: plugin Bundle-Version: 5.11.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", +Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)", + org.bouncycastle.asn1.cryptlib;version="[1.65.0,2.0.0)", + org.bouncycastle.asn1.x9;version="[1.65.0,2.0.0)", + org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", org.bouncycastle.bcpg.sig;version="[1.65.0,2.0.0)", + org.bouncycastle.crypto.ec;version="[1.65.0,2.0.0)", org.bouncycastle.gpg;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)", + org.bouncycastle.jcajce.interfaces;version="[1.65.0,2.0.0)", org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)", + org.bouncycastle.math.ec;version="[1.65.0,2.0.0)", + org.bouncycastle.math.field;version="[1.65.0,2.0.0)", org.bouncycastle.openpgp;version="[1.65.0,2.0.0)", org.bouncycastle.openpgp.jcajce;version="[1.65.0,2.0.0)", org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)", @@ -27,5 +34,5 @@ Import-Package: org.bouncycastle.bcpg;version="[1.65.0,2.0.0)", org.eclipse.jgit.transport;version="[5.11.0,5.12.0)", org.eclipse.jgit.util;version="[5.11.0,5.12.0)", org.slf4j;version="[1.7.0,2.0.0)" -Export-Package: org.eclipse.jgit.gpg.bc.internal;version="5.11.0"; - x-friends:="org.eclipse.jgit.gpg.bc.test" +Export-Package: org.eclipse.jgit.gpg.bc.internal;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test", + org.eclipse.jgit.gpg.bc.internal.keys;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test" 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 83ed9059e..f2aa014d6 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,6 +1,8 @@ +corrupt25519Key=Ed25519/Curve25519 public key has wrong length: {0} credentialPassphrase=Passphrase -gpgFailedToParseSecretKey=Failed to parse secret key file in directory: {0}. Is the entered passphrase correct? +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 gpgNoKeyring=neither pubring.kbx nor secring.gpg files found 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} @@ -16,3 +18,7 @@ signatureNoPublicKey=No public key found to verify the signature signatureParseError=Signature cannot be parsed signatureVerificationError=Signature verification failed unableToSignCommitNoSecretKey=Unable to sign commit. Signing key not available. +uncompressed25519Key=Cannot handle ed25519 public key with uncompressed data: {0} +unknownCurve=Unknown curve {0} +unknownCurveParameters=Curve {0} does not have a prime field +unknownKeyType=Unknown key type {0} \ No newline at end of file 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 9403ba235..4753ac138 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 @@ -27,9 +27,11 @@ public static BCText get() { } // @formatter:off + /***/ public String corrupt25519Key; /***/ public String credentialPassphrase; /***/ public String gpgFailedToParseSecretKey; /***/ public String gpgNoCredentialsProvider; + /***/ public String gpgNoKeygrip; /***/ public String gpgNoKeyring; /***/ public String gpgNoKeyInLegacySecring; /***/ public String gpgNoPublicKeyFound; @@ -45,5 +47,9 @@ public static BCText get() { /***/ public String signatureParseError; /***/ public String signatureVerificationError; /***/ public String unableToSignCommitNoSecretKey; + /***/ public String uncompressed25519Key; + /***/ public String unknownCurve; + /***/ public String unknownCurveParameters; + /***/ public String unknownKeyType; } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java index 13655c0d5..7f0f32a2a 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java @@ -27,12 +27,8 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; import java.util.Locale; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.bouncycastle.gpg.SExprParser; import org.bouncycastle.gpg.keybox.BlobType; @@ -61,6 +57,7 @@ import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.gpg.bc.internal.keys.KeyGrip; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; @@ -158,15 +155,10 @@ public BouncyCastleGpgKeyLocator(String signingKey, private PGPSecretKey attemptParseSecretKey(Path keyFile, PGPDigestCalculatorProvider calculatorProvider, PBEProtectionRemoverFactory passphraseProvider, - PGPPublicKey publicKey) { + PGPPublicKey publicKey) throws IOException, PGPException { try (InputStream in = newInputStream(keyFile)) { return new SExprParser(calculatorProvider).parseSecretKey( new BufferedInputStream(in), passphraseProvider, publicKey); - } catch (IOException | PGPException | ClassCastException e) { - if (log.isDebugEnabled()) - log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$ - e.getMessage(), e); - return null; } } @@ -472,67 +464,71 @@ private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( PGPPublicKey publicKey, Path userKeyboxPath) throws PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { - /* - * this is somewhat brute-force but there doesn't seem to be another - * way; we have to walk all private key files we find and try to open - * them - */ - - PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() - .build(); - - try (Stream keyFiles = Files.walk(USER_SECRET_KEY_DIR)) { - List allPaths = keyFiles.filter(Files::isRegularFile) - .collect(Collectors.toCollection(ArrayList::new)); - if (allPaths.isEmpty()) { - return null; - } + byte[] keyGrip = null; + try { + keyGrip = KeyGrip.getKeyGrip(publicKey); + } catch (PGPException e) { + throw new PGPException( + MessageFormat.format(BCText.get().gpgNoKeygrip, + Hex.toHexString(publicKey.getFingerprint())), + e); + } + String filename = Hex.toHexString(keyGrip).toUpperCase(Locale.ROOT) + + ".key"; //$NON-NLS-1$ + Path keyFile = USER_SECRET_KEY_DIR.resolve(filename); + if (!Files.exists(keyFile)) { + return null; + } + boolean clearPrompt = false; + try { + PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() + .build(); PBEProtectionRemoverFactory passphraseProvider = p -> { throw new EncryptedPgpKeyException(); }; - for (int attempts = 0; attempts < 2; attempts++) { - // Second pass will traverse only the encrypted keys with a real - // passphrase provider. - Iterator pathIterator = allPaths.iterator(); - while (pathIterator.hasNext()) { - Path keyFile = pathIterator.next(); - try { - PGPSecretKey secretKey = attemptParseSecretKey(keyFile, - calculatorProvider, passphraseProvider, - publicKey); - pathIterator.remove(); - if (secretKey != null) { - if (!secretKey.isSigningKey()) { - throw new PGPException(MessageFormat.format( - BCText.get().gpgNotASigningKey, - signingKey)); - } - return new BouncyCastleGpgKey(secretKey, - userKeyboxPath); - } - } catch (EncryptedPgpKeyException e) { - // Ignore; we'll try again. - } - } - if (attempts > 0 || allPaths.isEmpty()) { - break; - } - // allPaths contains only the encrypted keys now. + PGPSecretKey secretKey = null; + try { + // Try without passphrase + secretKey = attemptParseSecretKey(keyFile, calculatorProvider, + passphraseProvider, publicKey); + } catch (EncryptedPgpKeyException e) { + // Let's try again with a passphrase passphraseProvider = new JcePBEProtectionRemoverFactory( passphrasePrompt.getPassphrase( publicKey.getFingerprint(), userKeyboxPath)); - } + clearPrompt = true; + try { + secretKey = attemptParseSecretKey(keyFile, calculatorProvider, + passphraseProvider, publicKey); + } catch (PGPException e1) { + throw new PGPException(MessageFormat.format( + BCText.get().gpgFailedToParseSecretKey, + keyFile.toAbsolutePath()), e); - passphrasePrompt.clear(); + } + } + if (secretKey != null) { + if (!secretKey.isSigningKey()) { + throw new PGPException(MessageFormat.format( + BCText.get().gpgNotASigningKey, signingKey)); + } + clearPrompt = false; + return new BouncyCastleGpgKey(secretKey, userKeyboxPath); + } return null; } catch (RuntimeException e) { - passphrasePrompt.clear(); throw e; + } catch (FileNotFoundException | NoSuchFileException e) { + clearPrompt = false; + return null; } catch (IOException e) { - passphrasePrompt.clear(); throw new PGPException(MessageFormat.format( BCText.get().gpgFailedToParseSecretKey, - USER_SECRET_KEY_DIR.toAbsolutePath()), e); + keyFile.toAbsolutePath()), e); + } finally { + if (clearPrompt) { + passphrasePrompt.clear(); + } } } diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java new file mode 100644 index 000000000..b1d444601 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2021, Thomas Wolf 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.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.DSAPublicBCPGKey; +import org.bouncycastle.bcpg.ECPublicBCPGKey; +import org.bouncycastle.bcpg.ElGamalPublicBCPGKey; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.RSAPublicBCPGKey; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.field.FiniteField; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.util.encoders.Hex; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.gpg.bc.internal.BCText; +import org.eclipse.jgit.util.sha1.SHA1; + +/** + * Utilities to compute the keygrip of a key. A keygrip is a SHA1 hash + * over the public key parameters and is used internally by the gpg-agent to + * find the secret key belonging to a public key: the secret key is stored in a + * file under ~/.gnupg/private-keys-v1.d/ with a name "<keygrip>.key". While + * this storage organization is an implementation detail of GPG, the way + * keygrips are computed is not; they are computed by libgcrypt and their + * definition is stable. + */ +public final class KeyGrip { + + // Some OIDs apparently unknown to BouncyCastle. + + private static String OID_OPENPGP_ED25519 = "1.3.6.1.4.1.11591.15.1"; //$NON-NLS-1$ + + private static String OID_RFC8410_CURVE25519 = "1.3.101.110"; //$NON-NLS-1$ + + private static String OID_RFC8410_ED25519 = "1.3.101.112"; //$NON-NLS-1$ + + private KeyGrip() { + // No instantiation + } + + /** + * Computes the keygrip for a {@link PGPPublicKey}. + * + * @param publicKey + * to get the keygrip of + * @return the keygrip + * @throws PGPException + * if an unknown key type is encountered. + */ + @NonNull + public static byte[] getKeyGrip(PGPPublicKey publicKey) + throws PGPException { + SHA1 grip = SHA1.newInstance(); + grip.setDetectCollision(false); + + switch (publicKey.getAlgorithm()) { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_SIGN: + BigInteger modulus = ((RSAPublicBCPGKey) publicKey + .getPublicKeyPacket().getKey()).getModulus(); + hash(grip, modulus.toByteArray()); + break; + case PublicKeyAlgorithmTags.DSA: + DSAPublicBCPGKey dsa = (DSAPublicBCPGKey) publicKey + .getPublicKeyPacket().getKey(); + hash(grip, dsa.getP().toByteArray(), 'p', true); + hash(grip, dsa.getQ().toByteArray(), 'q', true); + hash(grip, dsa.getG().toByteArray(), 'g', true); + hash(grip, dsa.getY().toByteArray(), 'y', true); + break; + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + ElGamalPublicBCPGKey eg = (ElGamalPublicBCPGKey) publicKey + .getPublicKeyPacket().getKey(); + hash(grip, eg.getP().toByteArray(), 'p', true); + hash(grip, eg.getG().toByteArray(), 'g', true); + hash(grip, eg.getY().toByteArray(), 'y', true); + break; + case PublicKeyAlgorithmTags.ECDH: + case PublicKeyAlgorithmTags.ECDSA: + case PublicKeyAlgorithmTags.EDDSA: + ECPublicBCPGKey ec = (ECPublicBCPGKey) publicKey + .getPublicKeyPacket().getKey(); + ASN1ObjectIdentifier curveOID = ec.getCurveOID(); + // BC doesn't know these OIDs. + if (OID_OPENPGP_ED25519.equals(curveOID.getId()) + || OID_RFC8410_ED25519.equals(curveOID.getId())) { + return hashEd25519(grip, ec.getEncodedPoint()); + } else if (CryptlibObjectIdentifiers.curvey25519.equals(curveOID) + || OID_RFC8410_CURVE25519.equals(curveOID.getId())) { + // curvey25519 actually is the OpenPGP OID for Curve25519 and is + // known to BC, but the parameters are for the short Weierstrass + // form. See https://github.com/bcgit/bc-java/issues/399 . + // libgcrypt uses Montgomery form. + return hashCurve25519(grip, ec.getEncodedPoint()); + } + X9ECParameters params = getX9Parameters(curveOID); + if (params == null) { + throw new PGPException(MessageFormat + .format(BCText.get().unknownCurve, curveOID.getId())); + } + // Need to write p, a, b, g, n, q + BigInteger q = ec.getEncodedPoint(); + byte[] g = params.getG().getEncoded(false); + BigInteger a = params.getCurve().getA().toBigInteger(); + BigInteger b = params.getCurve().getB().toBigInteger(); + BigInteger n = params.getN(); + BigInteger p = null; + FiniteField field = params.getCurve().getField(); + if (ECAlgorithms.isFpField(field)) { + p = field.getCharacteristic(); + } + if (p == null) { + // Don't know... + throw new PGPException(MessageFormat.format( + BCText.get().unknownCurveParameters, curveOID.getId())); + } + hash(grip, p.toByteArray(), 'p', false); + hash(grip, a.toByteArray(), 'a', false); + hash(grip, b.toByteArray(), 'b', false); + hash(grip, g, 'g', false); + hash(grip, n.toByteArray(), 'n', false); + if (publicKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) { + hashQ25519(grip, q); + } else { + hash(grip, q.toByteArray(), 'q', false); + } + break; + default: + throw new PGPException( + MessageFormat.format(BCText.get().unknownKeyType, + Integer.toString(publicKey.getAlgorithm()))); + } + return grip.digest(); + } + + private static void hash(SHA1 grip, byte[] data) { + // Need to skip leading zero bytes + int i = 0; + while (i < data.length && data[i] == 0) { + i++; + } + int length = data.length - i; + if (i < data.length) { + if ((data[i] & 0x80) != 0) { + grip.update((byte) 0); + } + grip.update(data, i, length); + } + } + + private static void hash(SHA1 grip, byte[] data, char id, boolean zeroPad) { + // Need to skip leading zero bytes + int i = 0; + while (i < data.length && data[i] == 0) { + i++; + } + int length = data.length - i; + boolean addZero = false; + if (i < data.length && zeroPad && (data[i] & 0x80) != 0) { + addZero = true; + } + // libgcrypt includes an SExp in the hash + String prefix = "(1:" + id + (addZero ? length + 1 : length) + ':'; //$NON-NLS-1$ + grip.update(prefix.getBytes(StandardCharsets.US_ASCII)); + // For some items, gcrypt prepends a zero byte if the high bit is set + if (addZero) { + grip.update((byte) 0); + } + if (i < data.length) { + grip.update(data, i, length); + } + grip.update((byte) ')'); + } + + private static void hashQ25519(SHA1 grip, BigInteger q) + throws PGPException { + byte[] data = q.toByteArray(); + switch (data[0]) { + case 0x04: + if (data.length != 65) { + throw new PGPException(MessageFormat.format( + BCText.get().corrupt25519Key, Hex.toHexString(data))); + } + // Uncompressed: should not occur with ed25519 or curve25519 + throw new PGPException(MessageFormat.format( + BCText.get().uncompressed25519Key, Hex.toHexString(data))); + case 0x40: + if (data.length != 33) { + throw new PGPException(MessageFormat.format( + BCText.get().corrupt25519Key, Hex.toHexString(data))); + } + // Compressed; normal case. Skip prefix. + hash(grip, Arrays.copyOfRange(data, 1, data.length), 'q', false); + break; + default: + if (data.length != 32) { + throw new PGPException(MessageFormat.format( + BCText.get().corrupt25519Key, Hex.toHexString(data))); + } + // Compressed format without prefix. Should not occur? + hash(grip, data, 'q', false); + break; + } + } + + /** + * Computes the keygrip for an ed25519 public key. + *

+ * Package-visible for tests only. + *

+ * + * @param grip + * initialized {@link SHA1} + * @param q + * the public key's EC point + * @return the keygrip + * @throws PGPException + * if q indicates uncompressed format + */ + @SuppressWarnings("nls") + static byte[] hashEd25519(SHA1 grip, BigInteger q) throws PGPException { + // For the values, see RFC 7748: https://tools.ietf.org/html/rfc7748 + // p = 2^255 - 19 + hash(grip, Hex.decodeStrict( + "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"), + 'p', false); + // Field: a = 1 + hash(grip, new byte[] { 0x01 }, 'a', false); + // Field: b = 121665/121666 (mod p) + // See Berstein et.al., "Twisted Edwards Curves", + // https://doi.org/10.1007/978-3-540-68164-9_26 + hash(grip, Hex.decodeStrict( + "2DFC9311D490018C7338BF8688861767FF8FF5B2BEBE27548A14B235ECA6874A"), + 'b', false); + // Generator point with affine X,Y + // @formatter:off + // X(P) = 15112221349535400772501151409588531511454012693041857206046113283949847762202 + // Y(P) = 46316835694926478169428394003475163141307993866256225615783033603165251855960 + // the "04" signifies uncompressed format. + // @formatter:on + hash(grip, Hex.decodeStrict("04" + + "216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A" + + "6666666666666666666666666666666666666666666666666666666666666658"), + 'g', false); + // order = 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed + hash(grip, Hex.decodeStrict( + "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"), + 'n', false); + hashQ25519(grip, q); + return grip.digest(); + } + + /** + * Computes the keygrip for a curve25519 public key. + *

+ * Package-visible for tests only. + *

+ * + * @param grip + * initialized {@link SHA1} + * @param q + * the public key's EC point + * @return the keygrip + * @throws PGPException + * if q indicates uncompressed format + */ + @SuppressWarnings("nls") + static byte[] hashCurve25519(SHA1 grip, BigInteger q) throws PGPException { + hash(grip, Hex.decodeStrict( + "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED"), + 'p', false); + // Unclear: RFC 7748 says A = 486662. This value here is (A-2)/4 = + // 121665. Compare ecc-curves.c in libgcrypt: + // https://github.com/gpg/libgcrypt/blob/361a058/cipher/ecc-curves.c#L146 + hash(grip, new byte[] { 0x01, (byte) 0xDB, 0x41 }, 'a', false); + hash(grip, new byte[] { 0x01 }, 'b', false); + // libgcrypt uses the old g.y value before the erratum to RFC 7748 for + // the keygrip. The new value would be + // 5F51E65E475F794B1FE122D388B72EB36DC2B28192839E4DD6163A5D81312C14. See + // https://www.rfc-editor.org/errata/eid4730 and + // https://github.com/gpg/libgcrypt/commit/f67b6492e0b0 + hash(grip, Hex.decodeStrict("04" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "20AE19A1B8A086B4E01EDD2C7748D14C923D4D7E6D7C61B229E9C5A27ECED3D9"), + 'g', false); + hash(grip, Hex.decodeStrict( + "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"), + 'n', false); + hashQ25519(grip, q); + return grip.digest(); + } + + private static X9ECParameters getX9Parameters( + ASN1ObjectIdentifier curveOID) { + X9ECParameters params = CustomNamedCurves.getByOID(curveOID); + if (params == null) { + params = ECNamedCurveTable.getByOID(curveOID); + } + return params; + } + +} From 3b94ba6c24451ec3fc0c46b65125aba46ca299a5 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Tue, 16 Feb 2021 01:13:35 +0100 Subject: [PATCH 23/35] Fix boxing warnings Change-Id: Idf4887a99e87c375ec32e2fd289cfce82d78cbce Signed-off-by: Matthias Sohn --- .../eclipse/jgit/internal/storage/reftable/ReftableTest.java | 2 +- .../eclipse/jgit/internal/storage/file/FileReftableStack.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java index 56f881ec5..0d739b927 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java @@ -487,7 +487,7 @@ public void seekPastWithSeekRefsWithPrefix() throws IOException { public void seekPastWithLotsOfRefs() throws IOException { Ref[] refs = new Ref[500]; for (int i = 1; i <= 500; i++) { - refs[i - 1] = ref(String.format("refs/%d", i), i); + refs[i - 1] = ref(String.format("refs/%d", Integer.valueOf(i)), i); } ReftableReader t = read(write(refs)); try (RefCursor rc = t.allRefs()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java index e422767f0..b5e3927bc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java @@ -370,7 +370,7 @@ private long nextUpdateIndex() throws IOException { private String filename(long low, long high) { return String.format("%012x-%012x-%08x", //$NON-NLS-1$ Long.valueOf(low), Long.valueOf(high), - random.nextInt()); + Integer.valueOf(random.nextInt())); } /** From cd12b570ff3a642f633a52a305b79922479e30a8 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Thu, 18 Feb 2021 17:06:27 +0100 Subject: [PATCH 24/35] [GPG] Provide a factory for the BouncyCastleGpgSigner Otherwise client code has no way to ever create an instance without using internal non-API. Change-Id: I6201f98d4b1704a053159967b8adacd98e368522 Signed-off-by: Thomas Wolf --- org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF | 3 +- .../gpg/bc/BouncyCastleGpgSignerFactory.java | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java diff --git a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF index b379a2b47..040ed0818 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -34,5 +34,6 @@ Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)", org.eclipse.jgit.transport;version="[5.11.0,5.12.0)", org.eclipse.jgit.util;version="[5.11.0,5.12.0)", org.slf4j;version="[1.7.0,2.0.0)" -Export-Package: org.eclipse.jgit.gpg.bc.internal;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test", +Export-Package: org.eclipse.jgit.gpg.bc;version="5.11.0", + org.eclipse.jgit.gpg.bc.internal;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test", org.eclipse.jgit.gpg.bc.internal.keys;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test" diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java new file mode 100644 index 000000000..fdd1a2b11 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/BouncyCastleGpgSignerFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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; + +import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner; +import org.eclipse.jgit.lib.GpgSigner; + +/** + * Factory for creating a {@link GpgSigner} based on Bouncy Castle. + * + * @since 5.11 + */ +public final class BouncyCastleGpgSignerFactory { + + private BouncyCastleGpgSignerFactory() { + // No instantiation + } + + /** + * Creates a new {@link GpgSigner}. + * + * @return the {@link GpgSigner} + */ + public static GpgSigner create() { + return new BouncyCastleGpgSigner(); + } +} From a14455dfd7ac61e13f2ea8c7d789463efd8eeb72 Mon Sep 17 00:00:00 2001 From: wh Date: Thu, 17 Dec 2020 18:14:32 +0000 Subject: [PATCH 25/35] dfs: handle short copies `copy` is documented as possibly returning a smaller number of bytes than requested. In practice, this can occur if a block is cached and the reader never pulls in the file to check its size. Bug: 565874 Change-Id: I1e53b3d2f4ab09334178934dc0ef74ea99045cd3 Signed-off-by: wh Signed-off-by: Matthias Sohn --- .../jgit/internal/storage/dfs/DfsPackFile.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index b1e95520c..96ca690c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -607,8 +607,15 @@ private IOException packfileIsTruncated() { private void readFully(long position, byte[] dstbuf, int dstoff, int cnt, DfsReader ctx) throws IOException { - if (ctx.copy(this, position, dstbuf, dstoff, cnt) != cnt) - throw new EOFException(); + while (cnt > 0) { + int copied = ctx.copy(this, position, dstbuf, dstoff, cnt); + if (copied == 0) { + throw new EOFException(); + } + position += copied; + dstoff += copied; + cnt -= copied; + } } ObjectLoader load(DfsReader ctx, long pos) From bdc48aeac756cc0471618b06d793083e63109ee0 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 24 Jan 2021 02:13:43 +0100 Subject: [PATCH 26/35] GPG: handle extended private key format Add detection for the key-value pair format that was available in gpg-agent for some time already and that has become the default since gpg-agent 2.2.20. If a secret key in the .gnupg/private-keys-v1.d directory is found to have this format, extract the human-readable key from it, convert it to the binary serialized form and hand that to BouncyCastle. Encrypted keys in the new format may use AES/OCB. OCB is a patent- encumbered algorithm; although there is a license for open-source software, that may not be good enough and OCB may not be available in Java. It is not available in the default security provider in Java, and it is also not available in the BouncyCastle version included in Eclipse. Implement AES/OCB decryption, throwing a PGPException with a nice message if the algorithm is not available. Include a copy of the normal s-expression parser of BouncyCastle and fix it to properly handle data from such keys: such keys do not contain an internal hash since the AES/OCB cipher includes and checks a MAC already. Bug: 570501 Change-Id: Ifa6391a809a84cfc6ae7c6610af6a79204b4143b Signed-off-by: Thomas Wolf --- .../META-INF/MANIFEST.MF | 4 +- ...B05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc | Bin 0 -> 2432 bytes ...B05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key | Bin 0 -> 2869 bytes ...CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc | Bin 0 -> 2489 bytes ...CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key | Bin 0 -> 3102 bytes ...27FAB884DA3BD402B6E0F5472E108D21033124.asc | Bin 0 -> 1741 bytes ...27FAB884DA3BD402B6E0F5472E108D21033124.key | Bin 0 -> 2182 bytes .../jgit/gpg/bc/internal/keys/faked.key | Bin 0 -> 1044 bytes .../gpg/bc/internal/keys/SecretKeysTest.java | 155 ++++ org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF | 8 +- org.eclipse.jgit.gpg.bc/about.html | 83 +- .../jgit/gpg/bc/internal/BCText.properties | 12 + .../eclipse/jgit/gpg/bc/internal/BCText.java | 12 + .../internal/BouncyCastleGpgKeyLocator.java | 51 +- .../BouncyCastleGpgKeyPassphrasePrompt.java | 7 +- .../bc/internal/BouncyCastleGpgSigner.java | 10 +- .../keys/OCBPBEProtectionRemoverFactory.java | 121 +++ .../gpg/bc/internal/keys/SExprParser.java | 826 ++++++++++++++++++ .../jgit/gpg/bc/internal/keys/SXprUtils.java | 110 +++ .../jgit/gpg/bc/internal/keys/SecretKeys.java | 597 +++++++++++++ 20 files changed, 1902 insertions(+), 94 deletions(-) create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key create mode 100644 org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java create mode 100644 org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java 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 39ece1fcf..57c374795 100644 --- a/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.8 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="[5.11.0,5.12.0)", @@ -17,6 +18,7 @@ Import-Package: org.bouncycastle.jce.provider;version="[1.65.0,2.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.gpg.bc.internal;x-internal:=true +Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true, + org.eclipse.jgit.gpg.bc.internal.keys;x-internal:=true Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", org.hamcrest.library;bundle-version="[1.1.0,2.0.0)" diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc new file mode 100644 index 0000000000000000000000000000000000000000..355462c098ee49972161f031e3c0e2d55ab0d557 GIT binary patch literal 2432 zcmaJ@w-U414(@r1zh`#Hskp`Akb_Myu`?M1CK?P+f4#fK?XDVW<{N30rN93AktnPB z?T@26f1E(Hboj@VlRu($hvwhtUkEa|>P=LECR(m20l50auFNtu*QP{zOf0+wT)p`0 zlOM-!MEd;Hq%|DDrm%W$>e7f1jSGdOTOV%HV-_c_wp0wSQ_doV-3=$iG-i1Xue78LRgOE2Fm*e z%{UyjKrKxq76PUP4mW*M|1-W<1E*tel9 zycLmrg(2Rn@$THboR)QeH2#yFjYCdaZhEnnpxwzptqluiRADjU!)+U~E}FW@yTBKb zu?xC=7Wchq9r7&$*_8pv+#*!Nt5Cz!ZUO(}|H_@%m#VVm>bJKB;Y=^qKtN8*@*$f* zsw3U?90>Nh|GX)k6j!3vA2u0_7o3i)4qyUuc>{U|j=+L$t^Ih2z`E1n5k#Q2MF8{? zxO!XzU_uZ4PkC%bh|vOtE+23U$+BHW-cI-ksN_nAr9?aG)I*98k=~PNw7cRaP-JCbR<8HWU42D|e^b(_ z$&$gimY=h~N(#dHrCysL)#&aAJny5tk0@7CvQWHjs5472>sWO_(>$C!wCrchHOjLo zqzl4jsc!M%9BZfon5Yj^=uNV5?A@$H44ao@EZ9iSxA(08fY5-!a=lI-XV$Y$sgY0xL>lCBvZ>)oOf|ix*|)-5e`~wE;hxTJVOp(vj`5bh0E=kT_|AiW z2#<%YVQj3;l!QDRCMEhTKYQ5Ji()CE(V}bfwjQaUY}=zW@A~|3TAX7Ukl^ya%_Q64 z=9APmD~PnP!g1l?`QWzKnCA~H>is)be+wS>8+)PmY#|9Jo%mVpdS&SI#b0d6JE}Or z=`V@QU(LhJ3T>do)VICny9PTovUk0FFT2a7vBetvSgh2$QlnNox-JD%gRs+6*RBtP zBwU(ZM!5fqhq z4Hc9#g&g7O&Mk*~f|9h`o&2zJ?}Kgt%}IT;5z}n(#dWW1_#T^&pV%;7NhEQsT~dqG zt~s@KDR8FGLmFO>uM%wx5e7{2d8?J$LI#X^$1kDgC#!n}$}1Fo4^5EtM05H*ep87k zy(;6)LU}LcafY*M{w5zY4!{y~)0~M?G9a>nvVDq|ny7fxJ{+Dz;Gifu;ja7HuYyXR zc4vK_Cv&WYZrE=x65>O=j_-gEMXIwB_4c#q6Pum-i#%JSo=E!xN? z;k?z^EotTJ)*tyX=PIp$;hz%-)#CuaMdbdCA^(ExkhnV!6+Kj69Ok|k^ceQmz6Kr@ z>F99vKvS1<-i78}O+sVcV_O++A6e=VH|ewxHqo6co5iE5X`jlh$=fm{gte=%=+6HV zFsXigzCseiu`F!j87IEw8f%)cAoNrDuJD7)ye(8|2sDSgi3<%_u6w(*ta%FS9YarM zPd44i(IFUJ+gC7Sq)1i#b3vFjcz)>1CUSjKsja4kB}*1I(y9kgZDtB3IVeU*d z&jc$Mve4CE-un-~aifb2t7_7g57eAwS>mvF<9EHP5Go z-*#3HNo2|Nr?I!4vC*HW(tQ;Yi4}7D2IHl5QwRWaxPx zddYhTJuB@2qBvC6cxf~a_l;7hr^5`O$d1J!ZrR>{A4vcz6nzIyF)g^U>~+g>Sg3`s zXRw70W+HXyk`Sfg^lOj)8^@+=J2~VFiTGl`V$V&woX>9s$`3oR-dmk*9AK=qz$(Sh g-wK7Kw=a9VJg?OE2l5|Y{@MKJdy;R`|9P$c3tV#zaR2}S literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key new file mode 100644 index 0000000000000000000000000000000000000000..afa459c8389443ae8a3a5efb7891b57356ad6130 GIT binary patch literal 2869 zcmXAryRIa;4Tkf6iUKB?2@FUSMTwh9q?9=I3oNiFFbo*$@deno_g8jsfYaSo68YcA zKY#u4%k%yBpMU+&umAe`)8D@Sc)tJq=daKAzdzqfS)se{=xb`8HOng3ba=n%H?vsW zuyaN$@p$lqQefybL_Gx!6 z+8=wL3IH_P4#@G7>5GqzwOe1*o91?I9~PtS7IgqoWS?zIfju08HuGEDd6@^)SRMy%VQo_RaT`I1t?~y*{UT0~PBOI!-x84=)^Q^vB%BpAXB5>T>wj$Mio?h!I zu+(dxIZ)6B#VxDPo~>)>=x(zvg!Fl4HFNldf!=+=zMam?Ait04x@ki+nt^$$AsWDP zzT8}KRBMdo3*KXAn_X-#dWxcm@*Kcha4Iv# z?mHU($a_z1idI4E^Y}j6Q8p^>slvDJBknjc{PeyYao0kdD+Yc*(zCz#W6Y83SjCiQ z!mYJh>FOLSmc6-b#Uy$5nDoOHGRsXw-b=nhu#JE~RO8fs<=zS1FDJfnNjH-sy94m; zVME(Ax#Yna2n2n`S$*IUUo=1+e2|&zqV%%{o(`z6*0*ny1|3z$K){mLYN+UO@=E~~W=t~(-ed`YutbdGcd{y^gLO08Q0vtF3!ljH6@ zSMUH{B$aUD;mIsY>>z6m0BRF|`bb6Gjn};CCgt#nPEU+w>5VS^Jqs^}tP)8KwkrY$ zM%icgj<_HNFG$Ny;w)f6wex@|^&eU*N^gjvgR_%}wT;^(_S^Z;`vML(D|H3=flmeZ z)oHzK8+Gl$88sVu6nZ0O%I7H;XH}F;6RV+g`d4WKL6y1l&i9yxXiCjtc1d5&j!skY zbI90JU+qUo2d_)^5uJLg!<5aa;F02hNHKUWyw}$GM%5|krD>scX;72?d2q+??;MH9 zK_4Zf2Ekjp+`&lM)w_~QeyJQUyID2JRXj?-v<*>@u_tUTxtS$i~s1Nb5nJYDSB54;;s9S+PICtDJ++ zeK20o6@-IEowX+AZiX7-%JBI@Ow~SWkBN6C8hv|2U$s{WaJGatLb#E@eqXdaF1);Q zUzJ<$!TQXo4kQ0~^Qx3YKukQQfs*>}dU7JiMZg_~fZh2jlBrfIV^w@w8)b4O_((P$ zB5VDs_pQy!GGO93W*JQ^g1saoY0sFIw2twHU6oQ9cfELyR{ORCA@Fj`8X$K$YNi`= z4i_ipxrE7vvjIOFS`G~U4+SZW`iKxMG*Fc~(LaNLfY3(nA>fO_YZNq2QKa%k}))T5>R@1(uDysA|^ROf`Xp zJZI9gwY(!k>YEZ^spFPIm&z_@fd6h_n@e!;o5~LBd(?r*nPX@c1X#jvInw%$BpmT@;Jz;LpFOxV^5-y2xoM# zNlp@o$mHfmiH_i_`wG{slq3BblrOWEKQ04|s4ZFI23!)ah-+NE>54}q4i&#+`c4knlz)Q_-zt`gXF}3tEaCW zDQDdVkIi|r&(QrwRRuYyD`o3W^{I6jO7*N7rs(`7dSK-F*xhSt=8|_Gq`Mh-Wf+sn zO9t2q5d_)4y8`I-7u7|jP&V#fp*FFQFnXHSjWiz@-n>0PVsPVnm z8xn=K3lSLnr0aEGKX5k)p!f69pX-|$I^VZVm{OZVjot&MZ?$O9(QVr5VfGn9Q{4-l z6C2{sSgv@cY4RvG<$w)1%~u-CU9nC4JkAB^Syfe%@EIAk3)5h}5&xjcnS=T6_C7d- zFFDCkhm>Q}@WNLhI068PD}_>cXHs}6T*Frq*~XEICVkc=H9P%S4urJ)&=v+IJ^7DP zO0aW?Uqtzl#QG;I%>(o=z`uhyQb@dC=#falmf-4Ok1czbjt`s>KoZ%|YP!rCbp#sB!?ns0#f5 zoXZ^ONNo=2M?1lD()umEwmVJkJ!rmZlFXt`L!G5(?%6Jm$?--4A95+Z(O_1oW965v z3SmF9^*e~p7Hnhe zeE8X2&1}PjdjW7n@w4~c0uQ>^S;8X3cr(#pHc|<(at~UFU(Y+z`FHuSCL`9%WW?9{ z!SN`Apguy!3`fzA**h*!+uz0=p}kPwb1XV39LvuJmHWM<=nQj?r7e+{QCQOG>+y_G zwv!rUmpd#Nv(!VsPmUf@)qyA;F0fLUC!-LvK&u$P?e{@@E7-$-1!2!bjy_*NUabM! zT_dQjERAW)MjyDZ^)>nVtTyC8X>vX~vfz^AYV1!r_}zlw%@rWmF>nHe7XW)T+AH zdzZG2ov59?6{s`f`6Qd!`Y4QLKKjQR& z$`m53VBdI6?oSOKK^?f$?!uQUWQixdkD9Ft5b)H7&A3v$Nqf4k*vHK#W`s8_PF4gR zQ<-^I=GAc+7xDLT2KFi?FXXh*D|As)e(PHtkv2K{+?JP&X9b2s6(%vI7*`9v|ji$z%3=v@B2DGBWeJHQ~9I?_3msM4jP zNfPbVgbhq}ze%YrUqk2sotLWegJYSfvgkc4b3f$>j^0tl{qWy{7)D6gsimyX5p%FH zEV(0^gsEucW?>(_hH*_tMR71Yk&D_2p+80PC17fi(-%hx12mJbN8IZ1Fg2NIiKJ=! z^#HQGhEbv%3e2gLPSkTl-sd_Lq8+dT3xK48+Bpm*V)u;A|!yuBd*`i}0f9 zeBYSmOF0w>xHm`z)=?530F85hMg7;trxC;<#JCs{`etKO)yPWMv!-YXQdk|n;D)N8 zWrjh|?BUKtXsb4r?04OInKdwp9|$+}!!tmrgSauk@GH6=E%1!LVXdU;)QN1puV;-f zAY7ci=ZewBKqeOw(y$cIY%ghrjF3b|bYgIMdAPat#AS_=2nQDv)spa{qz*qd-q$5K z3s07W;#69#R9hBwUaJKyfSBr~b&;nZ`1?dW%$&)04%_oWaKSSWEuULiMJBWhrb^xK zZ7N^w^U>0Kq^DN%;aW&AQ@wsz9~}W@0el%z1`|K-<-~wk1XS*1g7lOf%C!B#v_(=T?HSVK z;8}T?Onk6x(08kQ#r00bp(I|!(x+097kj~~9TLxSn!FH^O4%+KtILyozKHeuu9aK= zRuvi4JBD`YJZp5$U_PS&lF`l?mb;Kwx`!VZUNlkNX(S(bAQ8DgXZ9KE_3dS b1q|cRJb=HUzaRK3gZ}+?2`2x4ezJc7vvMf! literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key new file mode 100644 index 0000000000000000000000000000000000000000..cef72f623492e81815faf9ba955acd3093aef35e GIT binary patch literal 3102 zcmYk8&8}Uy4Tb0TDGuU^JupC#5=CVuQPQN-zJQxvv_*i#u#EzF`+hrvwwdJm?7by& zasBWw?|=OK`uO$R??2vu`SJeu&tJd&>)&5~ynp`n{g2njZ{NSXKK}OlAd-?9ZMEV> zMpkvZ*Q!OgcA}5+_NsOyw}{SReeAPO(K4s+v!=-?evD^&`dqg)E2WiP#uVAPX0h#2 zi^SXOsNSwc_iV@RoI`jqN7Z8db^G*ou8OBb%-ko(+M}neTjp}!J>@v{tnM*(T=jKt zhU%GjQmM7s%Bd|}=9qPw`UzWelv2}%L~Eik$Evp_^`z@4b?d1mq_5V>YCXJEYqfg! zTz8I=z4h(Yb-K-`-?D8~;IJOts6(rjNCBEVc9(magG}GO z$^z_cd1R05m%iU#z_)jJ_Wqo4yRV)=Mg4k|26gndWyLuyu1~+9+Xg7!J+tj$rgrYz zi)vSjoJ+|iqPd+}?~;~vCohd{h5v17ix0b;dJOb#Y0NnIx!f$^qc#h}eTo_D%pBfr z9bHU$H{0bHC9JEu^sd4T8vGIUx({QYQ}ga|-d?S4v)q&mxl%^kF|O-5)W(B$&Fa-I z=H5Hxn**5WFR$F<6)f$#y}e?er_0)#P^#)MRokO1^F^1drY{lLQ5a&0>N9Q~qqSb@ zTz0pmCH44}b>y_%FsQ@gzJ{!s;(c*#Z~XA#)5mwzQADKh_kUIN_ImyP_5HW6|M>d( zyZ&mQfB)rgpTE3+e|`M;{hwdT>qAtC7eF|A-J_lleDD*HDD~E#K7D|22R-x==Gu$G z2ap-|;nN2Salf!u${h^0T#o>1U1jybSjXMH-3EhQQ?;+`Hqx)&ph3Vvd26F(J*`zv zZ?ua(yy%r|Yn@n})61m!N$Ufc%5r8WmNkcZXuD!7dV%~$uGh7Nj}vK5t581H47Ii2 zUd4R($iWzMmTtBYBXhF|{GeB+Gh2+6jX#vH>P?hdn>B?FnYE@fw=T=j;#_cVnBRtvwLe$^B%`s5NCn*bZyPB zk}mY)64Q%Mit%}ym#noTL*DMvfDjivh*QIBSqtiVy&)B0&YEb2lG?pUZ9JH{>B7o* zL75ry89NKJd2;$;X!10%p;KZ!30m zEOV)(7SUQUsKgjr9d#mFw9^p6Oyr_aLh6Wv*y@_=?R8HuNUb|2nwlHj&_U$R;JbcT zzqq4eFuidlHaWTg?Uc!gP?K^G_OfP0qH%G-M#mbov8fOyo8z4Lu~x*`IR?j=?!2;= z21EB*&Wr=Cz^7yW!mqAkv_b`mI~we{aZl3ApN<3khyKZji?bZ5z7Vd|axmBi4{pOu|PUm~9HLxI`% z49CJ2v>v=+_?14--Eh-&oq>hrB4r*!fdnt9@qpO(k^<%lVPzh6+-n+ zUDU%BL>DymT?uEyp%ozs%f=ZDe$d-CTn&ePd=beKlB_wA9xclb9gTX95et$=+Bh57 zg3q}AVR?JZfvxFT>Mn1gFSbgINt<4!b*DF~oDV0#S#TA0T$UTN(^jBMX9F7qpV_iO zKF)@yc;F52hEw@o#X1W!bikIMJQaMXl8to%)IO}uRL5pB%7*7q5N3it9SU9wOZ3mZ ziM6t_E$*@5(minA{o+vU7C3xCczm&49EmCHHR)z5#rVR|VQ4Ol_PKOYl4?j%n zyw~YTo%oGPIE3bWjogvw=xzvj^9^rL#zj+EdttacVR`VLG9ZXl{st)5ZQF9*5!Iyd;_i` zDnlne&FHKgmu0l&m59E19DsM3a?&8g+4g#tk?lZ4yI7IM!8|O`hgDh0*;tKeB|Vej z!KV>5+~TT9Q@+YL=H&Ly#+Tg`7u971-$%{}E#tRJ58+_#i&o5*(afO$OAmO*tpOUZ z?2ppq3|r*D+5qW0Z2C;QQm7m)>_e>$5EatP@klCHD((8A2*N<{rKoLH_@iH^A%hL2$qHATNUmpp%M?Qmn*$0*zw~P zTB6g$$+|yh4xrW%o&eN;z^VTqIRA2U{+A2rAF6Kr!PYFAeMN0r=l)!}MB_QmrwHaM z_aM7IvB2~|twry(Hy^4BGJqSSeBsdAfN8s^7Ffk~`P9uJ1Oa;teWQksvLo_CLW(x~6(ez_$IPRV#Z z5sP6Z=5F&fK;T`yVkmve*}J68UMBE}-;;h>LCR+ENC#_ZQ>0}KS|9O8j+D{>ep6=L{JYuX+FP+lOs3s>7$);G-;p59B`};TayWTwk`ui0LCjUSF G)PDf)rZx)z literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key new file mode 100644 index 0000000000000000000000000000000000000000..63617c09bfcc83be03424e31ec6ae2bcbf4823c0 GIT binary patch literal 2182 zcmYjTyN+Zx4$bQ;E~b<2paJnEQO-z-N;cge=(P*pg#k}vW?;a7?{gg(n-scQ_fq6} zvHtb^^QY_e^V`=S&!2xhe|i7>_P;+qemviQd;WR7e*6A$z5af^w6=pJYDbwK_VDc$ zF6V5XVWRoC#wg(=^+Ahjxm0vj7gwv`=z?#yH}wi4726|H$H7x#nv!iSIr_NP;wsc1 zQ=0YNVo@vAou#|?XoHo{3Z0>nBfIjJ)h6zaOx$(+aI?q7k$h_J+@eRTRA>!~iO=aA zXV{FLfu|3i7`x5DVUnq{njPHAUXLq}$He4&TIjf^&6;zXSq2RWOIfu^ssRo=Q8_2B z@!_>rwy4!=cHlF~GS|^uBz?9R=0Iln=)pjwn0F&j@k4uW2gtZXJuZ?t zmIsh%i8n&LdPwU5pxrpF(XnJ_L8E#oDACtl9V95`h_e-S+UfTcZ1|_j#udo}WAEL# z5m#@55z5=B1n)YiW*e$(eqP>QpN|W=pw=4U+dulbzJ5M`|NQmy{X73A?_YoZ&-=&o z`}O+z{l6b{y-4{ur+Mqy40OJr$QK5Mjak3Ey^L}+o-W3Gz?8`z-J8AOs3pxgwvl2S z$F*iL+t~vs4#2w?<8NN-XH875Ky&hHu?;0uyRS(ta1~9e*lmn7Q(n_f4bHXH8emx* zUM>Vtt=xUN6HA8*!ElyO>0PGww1D1r7zQd$=y49&21N%{4w~@Nt~2p9<|^Ye;?Tso zf`=v67;-AFgO61Ql;17qsWw}MFryRL%D9NeKrPfmI|Fi0feibBg?QTL9)0fg1#T%4 zMxkVQFo!8dcCCKfVMS4&#YZQfXjL|w$Fc?Ql^tE<^tI(O zf-n@M*E$e0(BPhgW+7JQQL9EyOxbR*9RRPnP0o_(<|2hT22+R4wdD}+hL&^}kG7;! zNBayHMz?|1Ji-T#6g>i-4dtu>rwY>NHeF(_wq0_G%ilcM?urlq)HPv|JfObISVqS| zr)i%c1~1^wdk$a+*kU~{GzoQsE{IPKLTcNVe&^Prb=YDB%s$rHYCYFs0>lyuRP&3(MHGcOx-6V8t#+@>K<1yGz^X9h8%Oju(bse+@%dvATc~gr((#4pmAPa z1L@_6pQ)!KZ(%v`1>w1JCxJXY68Qz6hJanrj>j7Mm~~)HVD6r^hauDiOws|{0468a zL|^M(L3_d(yMxH4;q5TNQZ?5ALqMHwWqKbDG2q;^A%l)Hk^##B`bp0ie4td=2H7_i zNx!=YQqmdAWG5`FZEr?KkROL6@n#|` z8gQKnWdO>6nnBvO>WDDxNIU@EfiJEd$tKZ|#j(aZQwnmxe#9_$?5Eui=#&8ruwM{Q zkOKsJFJuHZ@bQpDy1^S8(iy^z?WPmvIC2Q-1ucOZF_YOHdvzrNPpo3(1Ec&VgATD7 zak(O6uz~v2x|dS4V|a<$Og*t?{QnAg|M>BDtRrX}(fl`)O|~y@Z*PzF?TOzOzx;IF KDg4i2^T%JHAMlv~ literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key new file mode 100644 index 0000000000000000000000000000000000000000..405afad427311d79715cfa036c12c35b636854c9 GIT binary patch literal 1044 zcmah{O>f&c5WV|X5Xhm)!N$z+J2!r27sYN3w7Yl3W@@31Wk_t({QHiwC>A{xf=Kac z=DnH6`K$F|o7-}j$7>&_%e^Syhv)0LiKlsb77zOe@ig-3-Iwg!=Zm=_*E=@nmKIZ zWU)LCa~FqUKJGUEEN#o8+hsZ3i`TYnK3v=7iCEDdM9hqb4 z^T_DSJY28qaT5JBbY8}V#aH3Cb`HZuJT}4QjjZ2}W9Q(Pv7goo`fxe2{u?~ayUnNR zOJid)Spxt_3J{3D0Q9;bTDk7x!@m5zf7tKvXGX1WaR}gowF$-k&wqD5ZP>1Ral4#` z_Q2sr6QG+(^x9X=wwJn)mWs_N&B2IW?7sBw49n1!%;DTlLg3WUlYn4 zwNn%TiZ@<4$kCfrOtl#kNVY|Kimrew6%t@gmI=^Te!!VXI%Mgis+cHHuCfrzqeX;N zYbDGoAS$gjxLOog?^goDW*cy55{nago4`mLBY8|Ra8&duN*6pOHb!f#jva#%tEy0# zz*3TxG$?~bWr>w!u;8rIwlb%5l2ibcMk;M&ma5d8Q>;`1_s9g*8$*e$&MFpWBFZl2 zS~96rs&zS;D8X|pTyzOZTa%sh)wz(vdJ8V8S|dB=9oESAq?1X3tOh9^w~c_Vn3R*I z5JFKVqA`*0WbHICK|yB%PziSXu*f8DlqM0gna{SWv#QljJb8*+%az4_hP N-uyqeE9u?c-48E14>14$ literal 0 HcmV?d00001 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 new file mode 100644 index 000000000..4eecaf3ab --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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 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; +import java.io.InputStream; +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; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +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; + +@RunWith(Parameterized.class) +public class SecretKeysTest { + + @BeforeClass + public static void ensureBC() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + 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; + + TestData(String name, boolean encrypted) { + this.name = name; + this.encrypted = encrypted; + } + + @Override + public String toString() { + return name; + } + } + + @Parameters(name = "{0}") + public static TestData[] initTestData() { + return new TestData[] { + new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false), + new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true), + new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true), + new TestData("faked", false) }; + } + + private static byte[] readTestKey(String filename) throws Exception { + try (InputStream in = new BufferedInputStream( + SecretKeysTest.class.getResourceAsStream(filename))) { + return SecretKeys.keyFromNameValueFormat(in); + } + } + + private static PGPPublicKey readAsc(InputStream in) + throws IOException, PGPException { + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator()); + + Iterator keyRings = pgpPub.getKeyRings(); + while (keyRings.hasNext()) { + PGPPublicKeyRing keyRing = keyRings.next(); + + Iterator keys = keyRing.getPublicKeys(); + if (keys.hasNext()) { + return keys.next(); + } + } + return null; + } + + // Injected by JUnit + @Parameter + public TestData data; + + @Test + public void testKeyRead() throws Exception { + 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) { + PGPPublicKey publicKey = readAsc(pubIn); + // Do a full test trying to load the secret key. + PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() + .build(); + try (InputStream in = new BufferedInputStream(this.getClass() + .getResourceAsStream(data.name + ".key"))) { + PGPSecretKey secretKey = SecretKeys.readSecretKey(in, + calculatorProvider, () -> "nonsense".toCharArray(), + publicKey); + assertNotNull(secretKey); + } catch (PGPException e) { + // Currently we may not be able to load OCB-encrypted keys. + assertTrue(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 040ed0818..afb0ee151 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox;version="[1.65.0,2.0.0)", org.bouncycastle.gpg.keybox.jcajce;version="[1.65.0,2.0.0)", org.bouncycastle.jcajce.interfaces;version="[1.65.0,2.0.0)", + org.bouncycastle.jcajce.util;version="[1.65.0,2.0.0)", org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)", org.bouncycastle.math.ec;version="[1.65.0,2.0.0)", org.bouncycastle.math.field;version="[1.65.0,2.0.0)", @@ -25,14 +26,11 @@ Import-Package: org.bouncycastle.asn1;version="[1.65.0,2.0.0)", org.bouncycastle.openpgp.jcajce;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;version="[1.65.0,2.0.0)", org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)", + org.bouncycastle.util.io;version="[1.65.0,2.0.0)", org.eclipse.jgit.annotations;version="[5.11.0,5.12.0)", org.eclipse.jgit.api.errors;version="[5.11.0,5.12.0)", - org.eclipse.jgit.errors;version="[5.11.0,5.12.0)", - org.eclipse.jgit.lib;version="[5.11.0,5.12.0)", - org.eclipse.jgit.nls;version="[5.11.0,5.12.0)", - org.eclipse.jgit.transport;version="[5.11.0,5.12.0)", - org.eclipse.jgit.util;version="[5.11.0,5.12.0)", org.slf4j;version="[1.7.0,2.0.0)" Export-Package: org.eclipse.jgit.gpg.bc;version="5.11.0", org.eclipse.jgit.gpg.bc.internal;version="5.11.0";x-friends:="org.eclipse.jgit.gpg.bc.test", diff --git a/org.eclipse.jgit.gpg.bc/about.html b/org.eclipse.jgit.gpg.bc/about.html index f971af18d..fc527d5a3 100644 --- a/org.eclipse.jgit.gpg.bc/about.html +++ b/org.eclipse.jgit.gpg.bc/about.html @@ -11,7 +11,7 @@ margin: 0.25in 0.5in 0.25in 0.5in; tab-interval: 0.5in; } - p { + p { margin-left: auto; margin-top: 0.5em; margin-bottom: 0.5em; @@ -36,60 +36,53 @@

Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.

All rights reserved.

-

Redistribution and use in source and binary forms, with or without modification, +

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -

  • Redistributions of source code must retain the above copyright notice, +
    • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    • -
    • Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation +
    • Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    • -
    • Neither the name of the Eclipse Foundation, Inc. nor the names of its - contributors may be used to endorse or promote products derived from +
    • Neither the name of the Eclipse Foundation, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    -

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


    -

    SHA-1 UbcCheck - MIT

    +

    org.eclipse.jgit.gpg.bc.internal.keys.SExprParser - MIT

    -

    Copyright (c) 2017:

    -
    -Marc Stevens -Cryptology Group -Centrum Wiskunde & Informatica -P.O. Box 94079, 1090 GB Amsterdam, Netherlands -marc@marc-stevens.nl -
    -
    -Dan Shumow -Microsoft Research -danshu@microsoft.com -
    -

    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: +

    Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. +(https://www.bouncycastle.org)

    + +

    +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: +

    +

    +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. +

    +

    +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.

    -
    • The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software.
    -

    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.

    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 f2aa014d6..e4b1baba1 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,5 +1,7 @@ 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 @@ -7,10 +9,20 @@ gpgNoKeyring=neither pubring.kbx nor secring.gpg files found 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 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 4753ac138..aedf8a5be 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 @@ -29,6 +29,8 @@ 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; @@ -36,10 +38,20 @@ 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 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/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java index 7f0f32a2a..cf4d3d234 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyLocator.java @@ -30,7 +30,6 @@ import java.util.Iterator; import java.util.Locale; -import org.bouncycastle.gpg.SExprParser; import org.bouncycastle.gpg.keybox.BlobType; import org.bouncycastle.gpg.keybox.KeyBlob; import org.bouncycastle.gpg.keybox.KeyBox; @@ -48,16 +47,15 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPUtil; -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.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; import org.bouncycastle.util.encoders.Hex; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.gpg.bc.internal.keys.KeyGrip; +import org.eclipse.jgit.gpg.bc.internal.keys.SecretKeys; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; @@ -77,17 +75,10 @@ private static class NoOpenPgpKeyException extends Exception { } - /** Thrown if we try to read an encrypted private key without password. */ - private static class EncryptedPgpKeyException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - } - private static final Logger log = LoggerFactory .getLogger(BouncyCastleGpgKeyLocator.class); - private static final Path GPG_DIRECTORY = findGpgDirectory(); + static final Path GPG_DIRECTORY = findGpgDirectory(); private static final Path USER_KEYBOX_PATH = GPG_DIRECTORY .resolve("pubring.kbx"); //$NON-NLS-1$ @@ -154,11 +145,13 @@ public BouncyCastleGpgKeyLocator(String signingKey, private PGPSecretKey attemptParseSecretKey(Path keyFile, PGPDigestCalculatorProvider calculatorProvider, - PBEProtectionRemoverFactory passphraseProvider, - PGPPublicKey publicKey) throws IOException, PGPException { + SecretKeys.PassphraseSupplier passphraseSupplier, + PGPPublicKey publicKey) + throws IOException, PGPException, CanceledException, + UnsupportedCredentialItem, URISyntaxException { try (InputStream in = newInputStream(keyFile)) { - return new SExprParser(calculatorProvider).parseSecretKey( - new BufferedInputStream(in), passphraseProvider, publicKey); + return SecretKeys.readSecretKey(in, calculatorProvider, + passphraseSupplier, publicKey); } } @@ -483,29 +476,17 @@ private BouncyCastleGpgKey findSecretKeyForKeyBoxPublicKey( try { PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() .build(); - PBEProtectionRemoverFactory passphraseProvider = p -> { - throw new EncryptedPgpKeyException(); - }; + clearPrompt = true; PGPSecretKey secretKey = null; try { - // Try without passphrase secretKey = attemptParseSecretKey(keyFile, calculatorProvider, - passphraseProvider, publicKey); - } catch (EncryptedPgpKeyException e) { - // Let's try again with a passphrase - passphraseProvider = new JcePBEProtectionRemoverFactory( - passphrasePrompt.getPassphrase( - publicKey.getFingerprint(), userKeyboxPath)); - clearPrompt = true; - try { - secretKey = attemptParseSecretKey(keyFile, calculatorProvider, - passphraseProvider, publicKey); - } catch (PGPException e1) { - throw new PGPException(MessageFormat.format( - BCText.get().gpgFailedToParseSecretKey, - keyFile.toAbsolutePath()), e); - - } + () -> passphrasePrompt.getPassphrase( + publicKey.getFingerprint(), userKeyboxPath), + publicKey); + } catch (PGPException e) { + throw new PGPException(MessageFormat.format( + BCText.get().gpgFailedToParseSecretKey, + keyFile.toAbsolutePath()), e); } if (secretKey != null) { if (!secretKey.isSigningKey()) { diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java index e47f64f1a..614419598 100644 --- a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgKeyPassphrasePrompt.java @@ -17,8 +17,8 @@ import org.bouncycastle.util.encoders.Hex; import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.errors.UnsupportedCredentialItem; -import org.eclipse.jgit.transport.CredentialItem.CharArrayType; import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; +import org.eclipse.jgit.transport.CredentialItem.Password; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.URIish; @@ -31,7 +31,7 @@ */ class BouncyCastleGpgKeyPassphrasePrompt implements AutoCloseable { - private CharArrayType passphrase; + private Password passphrase; private CredentialsProvider credentialsProvider; @@ -78,8 +78,7 @@ public char[] getPassphrase(byte[] keyFingerprint, Path keyLocation) throws PGPException, CanceledException, UnsupportedCredentialItem, URISyntaxException { if (passphrase == null) { - passphrase = new CharArrayType(BCText.get().credentialPassphrase, - true); + passphrase = new Password(BCText.get().credentialPassphrase); } if (credentialsProvider == null) { 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 9f48e5431..211bd7bd2 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 @@ -49,7 +49,7 @@ import org.eclipse.jgit.util.StringUtils; /** - * GPG Signer using BouncyCastle library + * GPG Signer using the BouncyCastle library. */ public class BouncyCastleGpgSigner extends GpgSigner implements GpgObjectSigner { @@ -97,8 +97,9 @@ public boolean canLocateSigningKey(@Nullable String gpgSigningKey, BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, committer, passphrasePrompt); return gpgKey != null; - } catch (PGPException | IOException | NoSuchAlgorithmException - | NoSuchProviderException | URISyntaxException e) { + } catch (CanceledException e) { + throw e; + } catch (Exception e) { return false; } } @@ -143,7 +144,8 @@ public void signObject(@NonNull ObjectBuilder object, try (BouncyCastleGpgKeyPassphrasePrompt passphrasePrompt = new BouncyCastleGpgKeyPassphrasePrompt( credentialsProvider)) { BouncyCastleGpgKey gpgKey = locateSigningKey(gpgSigningKey, - committer, passphrasePrompt); + committer, + passphrasePrompt); PGPSecretKey secretKey = gpgKey.getSecretKey(); if (secretKey == null) { throw new JGitInternalException( 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 new file mode 100644 index 000000000..68f8a4555 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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. + *

    + * For JGit's needs, this is sufficient, but for a general upstream + * implementation that limitation might not be acceptable. + *

    + */ +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. + *

    + * 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. + *

    + * + * @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 { + 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 new file mode 100644 index 000000000..a9bb22c78 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java @@ -0,0 +1,826 @@ +/* + * Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) + *

    + * 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: + *

    + *

    + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + *

    + *

    + * 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. + *

    + */ +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.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: + *
      + *
    • handle unencrypted DSA, EC, and ElGamal keys (upstream only handles + * unencrypted RSA), and
    • + *
    • handle secret keys using AES/OCB as encryption (those don't have a + * hash).
    • + *
    + */ +@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 + * @throws PGPException + */ + 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()); + 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()); + } + + ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey( + ECNamedCurveTable.getOID(curveName), + new BigInteger(1, qVal)); + ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey + .getPublicKeyPacket().getKey(); + if (!basePubKey.getCurveOID().equals(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 + * @throws PGPException + */ + 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()); + 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 new file mode 100644 index 000000000..220aa285f --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) + *

    + * 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: + *

    + *

    + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + *

    + *

    + * 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. + *

    + */ +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! + *

    + * 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 + *

    + */ +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 new file mode 100644 index 000000000..1542b8cbc --- /dev/null +++ b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +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.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.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 { + + private SecretKeys() { + // No instantiation. + } + + /** + * Something that can supply a passphrase to decrypt an encrypted secret + * key. + */ + public interface PassphraseSupplier { + + /** + * Supplies a passphrase. + * + * @return the passphrase + * @throws PGPException + * if no passphrase can be obtained + * @throws CanceledException + * if the user canceled passphrase entry + * @throws UnsupportedCredentialItem + * if an internal error occurred + * @throws URISyntaxException + * if an internal error occurred + */ + char[] getPassphrase() throws PGPException, CanceledException, + 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. + * + * @param in + * {@link InputStream} to read from, doesn't need to be buffered + * @param calculatorProvider + * for checking digests + * @param passphraseSupplier + * for decrypting encrypted keys + * @param publicKey + * the secret key should be for + * @return the secret key + * @throws IOException + * if the stream cannot be parsed + * @throws PGPException + * if thrown by the underlying S-Expression parser, for instance + * when the passphrase is wrong + * @throws CanceledException + * if thrown by the {@code passphraseSupplier} + * @throws UnsupportedCredentialItem + * if thrown by the {@code passphraseSupplier} + * @throws URISyntaxException + * if thrown by the {@code passphraseSupplier} + */ + public static PGPSecretKey readSecretKey(InputStream in, + PGPDigestCalculatorProvider calculatorProvider, + 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))); + } + SExprParser parser = new SExprParser(calculatorProvider); + byte firstChar = data[0]; + try { + if (firstChar == '(') { + // Binary format. + if (!matches(data, 4, PROTECTED_KEY)) { + // Not encrypted binary format. + return parser.parseSecretKey(in, null, publicKey); + } + // AES/CBC encrypted. + PBEProtectionRemoverFactory 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); + } + } + + /** + * 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; + } + 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))); + } + } + 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 + */ + 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. + *

    + * Package-visible for tests only. + *

    + * + * @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; + default: + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9')) { + return true; + } + return false; + } + } + + private static boolean isHex(int ch) { + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') + || (ch >= 'a' && ch <= 'f'); + } + + 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; + } +} From 7e38792ead0722cf920517408369f1f2d06d22d9 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sat, 20 Feb 2021 19:05:59 +0100 Subject: [PATCH 27/35] Add missing bazel dependency for o.e.j.gpg.bc.test This was missed in 64cbea8a9794047fe576d03ab8a46e4eaf7eabee. Change-Id: I0b2b234b9888a7dd8b7ace624233b141fb7c4394 Signed-off-by: Matthias Sohn --- org.eclipse.jgit.gpg.bc.test/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.jgit.gpg.bc.test/BUILD b/org.eclipse.jgit.gpg.bc.test/BUILD index 59859b283..925536e5d 100644 --- a/org.eclipse.jgit.gpg.bc.test/BUILD +++ b/org.eclipse.jgit.gpg.bc.test/BUILD @@ -16,6 +16,7 @@ junit_tests( "//lib:bcpg", "//lib:bcprov", "//lib:junit", + "//org.eclipse.jgit:jgit", "//org.eclipse.jgit.gpg.bc:gpg-bc", "//org.eclipse.jgit.gpg.bc.test:tst_rsrc", ], From 81a76383a1b92db34a250c68a28d60fadfffd036 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sun, 21 Feb 2021 00:01:22 +0100 Subject: [PATCH 28/35] Update Orbit to S20210216215844 Change-Id: Ic3af137e4aad0e6f7fd32c910766f547562442d6 Signed-off-by: Matthias Sohn --- .../org.eclipse.jgit.target/jgit-4.10.target | 4 +- .../org.eclipse.jgit.target/jgit-4.10.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.11.target | 4 +- .../org.eclipse.jgit.target/jgit-4.11.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.12.target | 4 +- .../org.eclipse.jgit.target/jgit-4.12.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.13.target | 4 +- .../org.eclipse.jgit.target/jgit-4.13.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.14.target | 4 +- .../org.eclipse.jgit.target/jgit-4.14.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.15.target | 4 +- .../org.eclipse.jgit.target/jgit-4.15.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.16.target | 4 +- .../org.eclipse.jgit.target/jgit-4.16.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.17.target | 4 +- .../org.eclipse.jgit.target/jgit-4.17.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.18.target | 4 +- .../org.eclipse.jgit.target/jgit-4.18.tpd | 2 +- .../jgit-4.19-staging.target | 4 +- .../jgit-4.19-staging.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.6.target | 4 +- .../org.eclipse.jgit.target/jgit-4.6.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.7.target | 4 +- .../org.eclipse.jgit.target/jgit-4.7.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.8.target | 4 +- .../org.eclipse.jgit.target/jgit-4.8.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.9.target | 4 +- .../org.eclipse.jgit.target/jgit-4.9.tpd | 2 +- .../orbit/S20210105214148.tpd | 66 ------------------- ...20210203173513.tpd => S20210216215844.tpd} | 4 +- 30 files changed, 44 insertions(+), 110 deletions(-) delete mode 100644 org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd rename org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/{I20210203173513.tpd => S20210216215844.tpd} (98%) diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target index 016d250bf..68378a2b0 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd index 206600afa..fb1ac6b25 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd @@ -1,7 +1,7 @@ target "jgit-4.10" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2018-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target index 1153b2964..18d525d8e 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd index 937ccf57a..0d5628063 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd @@ -1,7 +1,7 @@ target "jgit-4.11" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2019-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target index 7d7cdc046..d72f08d29 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd index a70f829a5..5a024152d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd @@ -1,7 +1,7 @@ target "jgit-4.12" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2019-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target index 559c9eea8..d0e559271 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd index 16c32b8e4..84e5c25ef 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd @@ -1,7 +1,7 @@ target "jgit-4.13" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2019-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target index 0da5f51cf..42278f6ef 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd index afa10ef18..6d793a607 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd @@ -1,7 +1,7 @@ target "jgit-4.14" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2019-12/201912181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target index b93d67e44..0d5166e16 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd index 700d38b33..4ce832bf9 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd @@ -1,7 +1,7 @@ target "jgit-4.15" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2020-03/202003181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target index 28d7be258..b4d53069b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd index 24093d8e1..1b56447ce 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd @@ -1,7 +1,7 @@ target "jgit-4.16" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2020-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target index 34b88e5f0..47fc74be6 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd index f26614afe..367020ce0 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd @@ -1,7 +1,7 @@ target "jgit-4.17" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2020-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target index dc1086298..b393e6075 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd index 716bd93f4..507ddd1dc 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd @@ -1,7 +1,7 @@ target "jgit-4.18" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2020-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target index 3f0b6c5ea..f37692616 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd index a707e5b38..3b1b19c84 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19-staging.tpd @@ -1,7 +1,7 @@ target "jgit-4.19-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/staging/2021-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index 6526d2e69..26715ee18 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd index 42d02c30b..23bf87c07 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd @@ -1,7 +1,7 @@ target "jgit-4.6" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/neon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index e62a31099..64fe05495 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd index 6bd9be5d5..c33e4a39b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd @@ -1,7 +1,7 @@ target "jgit-4.7" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/oxygen/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index a5d9229f3..f7a3a3b26 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd index af5f4fb4a..c40bacdb8 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd @@ -1,7 +1,7 @@ target "jgit-4.8" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/photon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index 69201e9de..4afbe9973 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,7 +1,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd index d9ff5b610..5aa63be64 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd @@ -1,7 +1,7 @@ target "jgit-4.9" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/I20210203173513.tpd" +include "orbit/S20210216215844.tpd" location "https://download.eclipse.org/releases/2018-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd deleted file mode 100644 index 58f2417d9..000000000 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd +++ /dev/null @@ -1,66 +0,0 @@ -target "S20210105214148" with source configurePhase -// see https://download.eclipse.org/tools/orbit/downloads/ - -location "https://download.eclipse.org/tools/orbit/downloads/drops/S20210105214148/repository" { - com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626] - com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626] - com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902] - com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902] - com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] - com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] - javaewah [1.1.7.v20200107-0831,1.1.7.v20200107-0831] - javaewah.source [1.1.7.v20200107-0831,1.1.7.v20200107-0831] - javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] - javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] - net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410] - net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534] - net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534] - net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410] - net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323] - net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323] - org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946] - org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946] - org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422] - org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422] - org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343] - org.apache.commons.compress.source [1.19.0.v20200106-2343,1.19.0.v20200106-2343] - org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502] - org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502] - org.apache.httpcomponents.httpclient [4.5.10.v20200830-2311,4.5.10.v20200830-2311] - org.apache.httpcomponents.httpclient.source [4.5.10.v20200830-2311,4.5.10.v20200830-2311] - org.apache.httpcomponents.httpcore [4.4.12.v20200108-1212,4.4.12.v20200108-1212] - org.apache.httpcomponents.httpcore.source [4.4.12.v20200108-1212,4.4.12.v20200108-1212] - org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] - org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] - org.apache.sshd.osgi [2.4.0.v20200318-1614,2.4.0.v20200318-1614] - org.apache.sshd.osgi.source [2.4.0.v20200318-1614,2.4.0.v20200318-1614] - org.apache.sshd.sftp [2.4.0.v20200319-1547,2.4.0.v20200319-1547] - org.apache.sshd.sftp.source [2.4.0.v20200319-1547,2.4.0.v20200319-1547] - org.assertj [3.14.0.v20200120-1926,3.14.0.v20200120-1926] - org.assertj.source [3.14.0.v20200120-1926,3.14.0.v20200120-1926] - org.bouncycastle.bcpg [1.65.0.v20200527-1955,1.65.0.v20200527-1955] - org.bouncycastle.bcpg.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955] - org.bouncycastle.bcpkix [1.65.0.v20200527-1955,1.65.0.v20200527-1955] - org.bouncycastle.bcpkix.source [1.65.0.v20200527-1955,1.65.0.v20200527-1955] - org.bouncycastle.bcprov [1.65.1.v20200529-1514,1.65.1.v20200529-1514] - org.bouncycastle.bcprov.source [1.65.1.v20200529-1514,1.65.1.v20200529-1514] - org.hamcrest [1.1.0.v20090501071000,1.1.0.v20090501071000] - org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519] - org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519] - org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246] - org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246] - org.junit [4.13.0.v20200204-1500,4.13.0.v20200204-1500] - org.junit.source [4.13.0.v20200204-1500,4.13.0.v20200204-1500] - org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] - org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] - org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642] - org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642] - org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519] - org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519] - org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150] - org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150] - org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042] - org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042] - org.tukaani.xz [1.8.0.v20180207-1613,1.8.0.v20180207-1613] - org.tukaani.xz.source [1.8.0.v20180207-1613,1.8.0.v20180207-1613] -} diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210216215844.tpd similarity index 98% rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210216215844.tpd index a77e21d61..29e5bc800 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/I20210203173513.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210216215844.tpd @@ -1,7 +1,7 @@ -target "I20210203173513" with source configurePhase +target "S20210216215844" with source configurePhase // see https://download.eclipse.org/tools/orbit/downloads/ -location "https://download.eclipse.org/tools/orbit/downloads/drops/I20210203173513/repository" { +location "https://download.eclipse.org/tools/orbit/downloads/drops/S20210216215844/repository" { com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626] com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626] com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902] From 704ccdc096e4f5cf2670c5c58eaf19fe1fdf4df3 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Mon, 22 Feb 2021 09:29:12 +0100 Subject: [PATCH 29/35] GPG: fix reading unprotected old-format secret keys Fix code and add a test case. The old code passed on the original input stream, which has already been consumed. Bug: 570501 Change-Id: I81f60698ce42443df57e59b1d1ab155574136fa8 Signed-off-by: Thomas Wolf --- ...DA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc | Bin 0 -> 1741 bytes ...DA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key | Bin 0 -> 978 bytes .../gpg/bc/internal/keys/SecretKeysTest.java | 26 ++++++++++++------ .../jgit/gpg/bc/internal/keys/SecretKeys.java | 12 ++++---- 4 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc create mode 100644 org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc new file mode 100644 index 0000000000000000000000000000000000000000..f4120199069f82b73b18b0b544022b4c4d93240f GIT binary patch literal 1741 zcmaKtxelvV5Qcl6;`ZpuU@({>#o=(wj@h>kvlz1(%+AxtH%g+UxzkKB8qJLU|I_^Z z`3)Y4vhl+d%^#-6Ycl+yqUZ;&-J$xc`V+%i3mN>4McQ@cArS5~vz2TYcI*;g-s?He zK0O<_CC4}Jd7To;Q2S0@oOdKu%Hts(*nYbz#cA-(IW!*CcFqd2aoNLVH!5G!%6c37 zmB{j9g6kFZG0)lh{Y}lDJUC~+zvEcyaG%P3^7c(*HesQjG>${Wjrb)~5%*N#LVdRs6 zg1l8>`?4^6^i~y% zByo&;Du`Rri}UKEPG|lz^mvK;_FUckRIO=)1D7*j>&!iOx#r-L$Gx8c<|V>t=+14_ zQ_*#!o+lZ66w-ZuUA2I$#22~a*ThQjnQY|xwD@~ET;Fs#b!Sjqw;1~X_uA)4KUQy} zLM|GwcZ*0G$|AgwOWJ_32l9wxBAni#08LjWNc5FW&fqDgpPgcmHl=kXTm=NLSDbVu6${a=JeR%8P zoHWDyKgb}S7x-c<#Y|QW$A|sQsGKPh@&yyD;$uu{W)2*2r_Qf_8KY$o?@Gw&($`uW zZ)*99=NE5`9#p#6D_Q@H{RY<;t;70=!&Qs)RD~ zjQ}2$2LdUQYEb(LiDPf?CpBeeHxI zWX$~d1kUkVenEzPDN}$OArXARf#A2`{FfWyZ!WNZsKU25&!0FRw2fqWt>2s;^*SCP z-NV|xNaGH^?$ebCXGFCbifJx2lW;^8lN+2BbCod$BeTLS6fZ%=WE~q1LDE<@Ao2wz zjIT*JOCO((XquonTvJX>@*xFmgUwNLKVtGY4fO4eL_^v7Q zpB`7Ei(j`{m#uY&xg~w;+&+abhhPfmOA@@)c~jyN<34SGht|o*{s=^G!2Wtg$Pj+> Gr~U*xBQmuB literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key new file mode 100644 index 0000000000000000000000000000000000000000..b8765aaacb050f8515dd971c3fa2a88b4573f9f3 GIT binary patch literal 978 zcmV;@11cWly}*?;YN~$k3qNywTU6 zv@ARVmO=Fs4x@XNBBZuflfH2;YmGPoL{j?1JNJW+z6eij`{X5zIekWqz;8OdTtQ9Jg+OiMHE)_0_`p~WzrN=!ma003Fi-Oq>| z=E=ekSvXhT$_^C^y?5LIR4XbiS!EO7FmhFaPzdiVg>LU_-MJbjdi4hnh^UJ&{xv?6y-3j#>tTax{?kMM$ThPJ|I^K`)^xS4$>m z{)7|x4r8OXYfuC}eA1-9@!-{wxxVvOL%m<9ZMvJ_oit;mxKuQ&4)NGtQj2B5DJU^I za4|ADIsoPAt@YJ)brKIovJ1qhkvuu_wwS`c%_G;HKplw8?w4kYq(^Gm+}d4jkSrKb zIOkNGALVRxA`q&0)|(K~ySzZc(BfUY-a+o#hHkF;A!GgP>=963YY`{{M^}}4la3*i zgEHa3yq-nx-4i%1yk7Pky7f*OVjg};WoI^cSvM&tF*~ftih-=nrQ~N6r4-a5T2(crA zuRh-DHIVGKt|LGQsMt%Crxis?*PTOVSxa_v8+6PucI)?Mohkx`3CZs*Syl5%ptI+&=o|QCr#Ej+#fgA6GtDZhrx A0ssI2 literal 0 HcmV?d00001 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 4eecaf3ab..5e5e30331 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 @@ -76,9 +76,12 @@ private static class TestData { final boolean encrypted; - TestData(String name, boolean encrypted) { + final boolean keyValue; + + TestData(String name, boolean encrypted, boolean keyValue) { this.name = name; this.encrypted = encrypted; + this.keyValue = keyValue; } @Override @@ -90,10 +93,11 @@ public String toString() { @Parameters(name = "{0}") public static TestData[] initTestData() { return new TestData[] { - new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false), - new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true), - new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true), - new TestData("faked", false) }; + new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false), + new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true), + new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true), + new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true), + new TestData("faked", false, true) }; } private static byte[] readTestKey(String filename) throws Exception { @@ -126,9 +130,11 @@ private static PGPPublicKey readAsc(InputStream in) @Test public void testKeyRead() throws Exception { - byte[] bytes = readTestKey(data.name + ".key"); - assertEquals('(', bytes[0]); - assertEquals(')', bytes[bytes.length - 1]); + 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) { @@ -139,7 +145,9 @@ public void testKeyRead() throws Exception { try (InputStream in = new BufferedInputStream(this.getClass() .getResourceAsStream(data.name + ".key"))) { PGPSecretKey secretKey = SecretKeys.readSecretKey(in, - calculatorProvider, () -> "nonsense".toCharArray(), + calculatorProvider, + data.encrypted ? () -> "nonsense".toCharArray() + : null, publicKey); assertNotNull(secretKey); } catch (PGPException e) { 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 1542b8cbc..269a1ba0f 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 @@ -113,13 +113,13 @@ public static PGPSecretKey readSecretKey(InputStream in, try { if (firstChar == '(') { // Binary format. - if (!matches(data, 4, PROTECTED_KEY)) { - // Not encrypted binary format. - return parser.parseSecretKey(in, null, publicKey); + PBEProtectionRemoverFactory decryptor = null; + if (matches(data, 4, PROTECTED_KEY)) { + // AES/CBC encrypted. + decryptor = new JcePBEProtectionRemoverFactory( + passphraseSupplier.getPassphrase(), + calculatorProvider); } - // AES/CBC encrypted. - PBEProtectionRemoverFactory decryptor = new JcePBEProtectionRemoverFactory( - passphraseSupplier.getPassphrase(), calculatorProvider); try (InputStream sIn = new ByteArrayInputStream(data)) { return parser.parseSecretKey(sIn, decryptor, publicKey); } From 64cb7148ac64855feb7f7649d1d168d7c6d37860 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sun, 24 Jan 2021 00:17:13 +0100 Subject: [PATCH 30/35] Fail clone if initial branch doesn't exist in remote repository jgit clone --branch foo did not fail if the remote branch "foo" didn't exist in the remote repository being cloned. Bug: 546580 Change-Id: I55648ad3a39da4a5711dfa8e6d6682bb8190a6d6 Signed-off-by: Matthias Sohn --- .../tst/org/eclipse/jgit/pgm/CloneTest.java | 42 +++++++++++++++++ .../src/org/eclipse/jgit/pgm/Clone.java | 3 ++ .../eclipse/jgit/internal/JGitText.properties | 1 + .../org/eclipse/jgit/api/CloneCommand.java | 1 + .../org/eclipse/jgit/api/FetchCommand.java | 22 ++++++++- .../org/eclipse/jgit/internal/JGitText.java | 1 + .../eclipse/jgit/transport/FetchProcess.java | 30 ++++++++++-- .../org/eclipse/jgit/transport/Transport.java | 47 ++++++++++++++++++- 8 files changed, 139 insertions(+), 8 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java index 2f09b7f12..4cbd61c69 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java @@ -11,7 +11,9 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import java.io.File; @@ -25,6 +27,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.RefSpec; @@ -64,6 +67,45 @@ public void testClone() throws Exception { assertEquals("expected 1 branch", 1, branches.size()); } + @Test + public void testCloneInitialBranch() throws Exception { + createInitialCommit(); + + File gitDir = db.getDirectory(); + String sourceURI = gitDir.toURI().toString(); + File target = createTempDirectory("target"); + String cmd = "git clone --branch master " + sourceURI + " " + + shellQuote(target.getPath()); + String[] result = execute(cmd); + assertArrayEquals(new String[] { + "Cloning into '" + target.getPath() + "'...", "", "" }, result); + + Git git2 = Git.open(target); + List branches = git2.branchList().call(); + assertEquals("expected 1 branch", 1, branches.size()); + + Repository db2 = git2.getRepository(); + ObjectId head = db2.resolve("HEAD"); + assertNotNull(head); + assertNotEquals(ObjectId.zeroId(), head); + ObjectId master = db2.resolve("master"); + assertEquals(head, master); + } + + @Test + public void testCloneInitialBranchMissing() throws Exception { + createInitialCommit(); + + File gitDir = db.getDirectory(); + String sourceURI = gitDir.toURI().toString(); + File target = createTempDirectory("target"); + String cmd = "git clone --branch foo " + sourceURI + " " + + shellQuote(target.getPath()); + Die e = assertThrows(Die.class, () -> execute(cmd)); + assertEquals("Remote branch 'foo' not found in upstream origin", + e.getMessage()); + } + private RevCommit createInitialCommit() throws Exception { JGitTestUtil.writeTrashFile(db, "hello.txt", "world"); git.add().addFilepattern("hello.txt").call(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index fe94b0372..f28915d3f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -18,6 +18,7 @@ import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.InvalidRemoteException; +import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.TextProgressMonitor; @@ -110,6 +111,8 @@ protected void run() throws Exception { db = command.call().getRepository(); if (msgs && db.resolve(Constants.HEAD) == null) outw.println(CLIText.get().clonedEmptyRepository); + } catch (TransportException e) { + throw die(e.getMessage(), e); } catch (InvalidRemoteException e) { throw die(MessageFormat.format(CLIText.get().doesNotExist, sourceUri), e); 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 192462104..6d4a07799 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -568,6 +568,7 @@ refNotResolved=Ref {0} cannot be resolved reftableDirExists=reftable dir exists and is nonempty reftableRecordsMustIncrease=records must be increasing: last {0}, this {1} refUpdateReturnCodeWas=RefUpdate return code was: {0} +remoteBranchNotFound=Remote branch ''{0}'' not found in upstream origin remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index aba86fc36..cf7bc1f26 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -297,6 +297,7 @@ private FetchResult fetch(Repository clonedRepo, URIish u) command.setTagOpt( fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); } + command.setInitialBranch(branch); configure(command); return command.call(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 033dd60c3..90c1515b0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -74,6 +74,8 @@ public class FetchCommand extends TransportCommand { private boolean isForceUpdate; + private String initialBranch; + /** * Callback for status of fetch operation. * @@ -209,7 +211,7 @@ public FetchResult call() throws GitAPIException, InvalidRemoteException, transport.setFetchThin(thin); configure(transport); FetchResult result = transport.fetch(monitor, - applyOptions(refSpecs)); + applyOptions(refSpecs), initialBranch); if (!repo.isBare()) { fetchSubmodules(result); } @@ -487,6 +489,24 @@ public FetchCommand setTagOpt(TagOpt tagOpt) { return this; } + /** + * Set the initial branch + * + * @param branch + * the initial branch to check out when cloning the repository. + * Can be specified as ref name (refs/heads/master), + * branch name (master) or tag name + * (v1.2.3). The default is to use the branch + * pointed to by the cloned repository's HEAD and can be + * requested by passing {@code null} or HEAD. + * @return {@code this} + * @since 5.11 + */ + public FetchCommand setInitialBranch(String branch) { + this.initialBranch = branch; + return this; + } + /** * Register a progress callback. * 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 aaba8d624..af7d50aae 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -596,6 +596,7 @@ public static JGitText get() { /***/ public String reftableDirExists; /***/ public String reftableRecordsMustIncrease; /***/ public String refUpdateReturnCodeWas; + /***/ public String remoteBranchNotFound; /***/ public String remoteConfigHasNoURIAssociated; /***/ public String remoteDoesNotHaveSpec; /***/ public String remoteDoesNotSupportSmartHTTPPush; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index bdebfa607..34bad6e02 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.StringUtils; class FetchProcess { /** Transport we will fetch over. */ @@ -79,7 +80,8 @@ class FetchProcess { toFetch = f; } - void execute(ProgressMonitor monitor, FetchResult result) + void execute(ProgressMonitor monitor, FetchResult result, + String initialBranch) throws NotSupportedException, TransportException { askFor.clear(); localUpdates.clear(); @@ -89,7 +91,7 @@ void execute(ProgressMonitor monitor, FetchResult result) Throwable e1 = null; try { - executeImp(monitor, result); + executeImp(monitor, result, initialBranch); } catch (NotSupportedException | TransportException err) { e1 = err; throw err; @@ -107,9 +109,22 @@ void execute(ProgressMonitor monitor, FetchResult result) } } + private boolean isInitialBranchMissing(Map refsMap, + String initialBranch) { + if (StringUtils.isEmptyOrNull(initialBranch) || refsMap.isEmpty()) { + return false; + } + if (refsMap.containsKey(initialBranch) + || refsMap.containsKey(Constants.R_HEADS + initialBranch) + || refsMap.containsKey(Constants.R_TAGS + initialBranch)) { + return false; + } + return true; + } + private void executeImp(final ProgressMonitor monitor, - final FetchResult result) throws NotSupportedException, - TransportException { + final FetchResult result, String initialBranch) + throws NotSupportedException, TransportException { final TagOpt tagopt = transport.getTagOpt(); String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS; String getHead = null; @@ -126,7 +141,12 @@ private void executeImp(final ProgressMonitor monitor, } conn = transport.openFetch(toFetch, getTags, getHead); try { - result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); + Map refsMap = conn.getRefsMap(); + if (isInitialBranchMissing(refsMap, initialBranch)) { + throw new TransportException(MessageFormat.format( + JGitText.get().remoteBranchNotFound, initialBranch)); + } + result.setAdvertisedRefs(transport.getURI(), refsMap); result.peerUserAgent = conn.getPeerUserAgent(); final Set matched = new HashSet<>(); for (RefSpec spec : toFetch) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 1c998f4e8..5b781ac25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -1231,9 +1231,52 @@ public void setPushOptions(List pushOptions) { * the remote connection could not be established or object * copying (if necessary) failed or update specification was * incorrect. + * @since 5.11 */ public FetchResult fetch(final ProgressMonitor monitor, - Collection toFetch) throws NotSupportedException, + Collection toFetch) + throws NotSupportedException, TransportException { + return fetch(monitor, toFetch, null); + } + + /** + * Fetch objects and refs from the remote repository to the local one. + *

    + * This is a utility function providing standard fetch behavior. Local + * tracking refs associated with the remote repository are automatically + * updated if this transport was created from a + * {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs + * defined. + * + * @param monitor + * progress monitor to inform the user about our processing + * activity. Must not be null. Use + * {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress + * updates are not interesting or necessary. + * @param toFetch + * specification of refs to fetch locally. May be null or the + * empty collection to use the specifications from the + * RemoteConfig. Source for each RefSpec can't be null. + * @param branch + * the initial branch to check out when cloning the repository. + * Can be specified as ref name (refs/heads/master), + * branch name (master) or tag name + * (v1.2.3). The default is to use the branch + * pointed to by the cloned repository's HEAD and can be + * requested by passing {@code null} or HEAD. + * @return information describing the tracking refs updated. + * @throws org.eclipse.jgit.errors.NotSupportedException + * this transport implementation does not support fetching + * objects. + * @throws org.eclipse.jgit.errors.TransportException + * the remote connection could not be established or object + * copying (if necessary) failed or update specification was + * incorrect. + * @since 5.11 + */ + public FetchResult fetch(final ProgressMonitor monitor, + Collection toFetch, String branch) + throws NotSupportedException, TransportException { if (toFetch == null || toFetch.isEmpty()) { // If the caller did not ask for anything use the defaults. @@ -1263,7 +1306,7 @@ public FetchResult fetch(final ProgressMonitor monitor, } final FetchResult result = new FetchResult(); - new FetchProcess(this, toFetch).execute(monitor, result); + new FetchProcess(this, toFetch).execute(monitor, result, branch); local.autoGC(monitor); From cb8924a80d9e07182a056c01acd418a6beddcc0f Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Mon, 25 Jan 2021 01:54:03 +0100 Subject: [PATCH 31/35] init: allow specifying the initial branch name for the new repository Add option --initial-branch/-b to InitCommand and the CLI init command. This is the first step to implement support for the new option init.defaultBranch. Both were added to git in release 2.28. See https://git-scm.com/docs/git-init#Documentation/git-init.txt--bltbranch-namegt Bug: 564794 Change-Id: Ia383b3f90b5549db80f99b2310450a7faf6bce4c Signed-off-by: Matthias Sohn --- .../tst/org/eclipse/jgit/pgm/InitTest.java | 21 ++++++++++ .../jgit/pgm/internal/CLIText.properties | 1 + .../src/org/eclipse/jgit/pgm/Init.java | 8 ++++ .../org/eclipse/jgit/api/InitCommandTest.java | 36 +++++++++++++++- .../src/org/eclipse/jgit/api/InitCommand.java | 23 ++++++++++ .../internal/storage/file/FileRepository.java | 2 +- .../jgit/lib/BaseRepositoryBuilder.java | 42 +++++++++++++++++++ .../src/org/eclipse/jgit/lib/Repository.java | 13 ++++++ 8 files changed, 144 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/InitTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/InitTest.java index 84474e33c..88789d338 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/InitTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/InitTest.java @@ -11,11 +11,14 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import java.io.File; +import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -54,4 +57,22 @@ public void testInitDirectory() throws Exception { assertArrayEquals(expecteds, result); } + @Test + public void testInitDirectoryInitialBranch() throws Exception { + File workDirectory = tempFolder.getRoot(); + File gitDirectory = new File(workDirectory, Constants.DOT_GIT); + + String[] result = execute( + "git init -b main '" + workDirectory.getCanonicalPath() + "'"); + + String[] expecteds = new String[] { + "Initialized empty Git repository in " + + gitDirectory.getCanonicalPath(), + "" }; + assertArrayEquals(expecteds, result); + + try (Repository repo = new FileRepository(gitDirectory)) { + assertEquals("refs/heads/main", repo.getFullBranch()); + } + } } 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 df55eb077..a5142802d 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 @@ -432,6 +432,7 @@ usage_updateRef=reference to update usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository usage_useNameInsteadOfOriginToTrackUpstream=use instead of 'origin' to track upstream usage_checkoutBranchAfterClone=check out named branch instead of remote's HEAD +usage_initialBranch=initial branch in the newly created repository (default 'master') usage_viewCommitHistory=View commit history usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from other branches and commits. usernameFor=Username for {0}: diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java index 7f59ef43d..7a0d96d41 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java @@ -24,6 +24,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; +import org.eclipse.jgit.util.StringUtils; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; @@ -32,6 +33,10 @@ class Init extends TextBuiltin { @Option(name = "--bare", usage = "usage_CreateABareRepository") private boolean bare; + @Option(name = "--initial-branch", aliases = { "-b" }, + metaVar = "metaVar_branchName", usage = "usage_initialBranch") + private String branch; + @Argument(index = 0, metaVar = "metaVar_directory") private String directory; @@ -54,6 +59,9 @@ protected void run() { } Repository repository; try { + if (!StringUtils.isEmptyOrNull(branch)) { + command.setInitialBranch(branch); + } repository = command.call().getRepository(); outw.println(MessageFormat.format( CLIText.get().initializedEmptyGitRepositoryIn, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java index 1c18b5a8b..14c52c207 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.api; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -42,7 +43,23 @@ public void testInitRepository() InitCommand command = new InitCommand(); command.setDirectory(directory); try (Git git = command.call()) { - assertNotNull(git.getRepository()); + Repository r = git.getRepository(); + assertNotNull(r); + assertEquals("refs/heads/master", r.getFullBranch()); + } + } + + @Test + public void testInitRepositoryMainInitialBranch() + throws IOException, JGitInternalException, GitAPIException { + File directory = createTempDirectory("testInitRepository"); + InitCommand command = new InitCommand(); + command.setDirectory(directory); + command.setInitialBranch("main"); + try (Git git = command.call()) { + Repository r = git.getRepository(); + assertNotNull(r); + assertEquals("refs/heads/main", r.getFullBranch()); } } @@ -72,6 +89,23 @@ public void testInitBareRepository() throws IOException, Repository repository = git.getRepository(); assertNotNull(repository); assertTrue(repository.isBare()); + assertEquals("refs/heads/master", repository.getFullBranch()); + } + } + + @Test + public void testInitBareRepositoryMainInitialBranch() + throws IOException, JGitInternalException, GitAPIException { + File directory = createTempDirectory("testInitBareRepository"); + InitCommand command = new InitCommand(); + command.setDirectory(directory); + command.setBare(true); + command.setInitialBranch("main"); + try (Git git = command.call()) { + Repository repository = git.getRepository(); + assertNotNull(repository); + assertTrue(repository.isBare()); + assertEquals("refs/heads/main", repository.getFullBranch()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java index 41fcf29ed..b2f7354ed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java @@ -15,6 +15,7 @@ import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; @@ -38,6 +39,8 @@ public class InitCommand implements Callable { private FS fs; + private String initialBranch = Constants.MASTER; + /** * {@inheritDoc} *

    @@ -87,6 +90,7 @@ public Git call() throws GitAPIException { builder.setWorkTree(new File(dStr)); } } + builder.setInitialBranch(initialBranch); Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) repository.create(bare); @@ -184,4 +188,23 @@ public InitCommand setFs(FS fs) { this.fs = fs; return this; } + + /** + * Set the initial branch of the new repository. If not specified + * ({@code null} or empty), fall back to the default name (currently + * master). + * + * @param branch + * initial branch name of the new repository + * @return {@code this} + * @throws InvalidRefNameException + * if the branch name is not valid + * + * @since 5.11 + */ + public InitCommand setInitialBranch(String branch) + throws InvalidRefNameException { + this.initialBranch = branch; + return this; + } } 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 51ee9e9d1..fecced1ae 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 @@ -243,7 +243,7 @@ && getDirectory().getName().startsWith(".")) //$NON-NLS-1$ RefUpdate head = updateRef(Constants.HEAD); head.disableRefLog(); - head.link(Constants.R_HEADS + Constants.MASTER); + head.link(Constants.R_HEADS + getInitialBranch()); final boolean fileMode; if (getFS().supportsExecute()) { 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 e51995f93..b2242a11c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -28,6 +28,8 @@ import java.util.LinkedList; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.internal.JGitText; @@ -38,6 +40,7 @@ import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; /** @@ -107,6 +110,8 @@ private static File getSymRef(File workTree, File dotGit, FS fs) private File workTree; + private String initialBranch = Constants.MASTER; + /** Directories limiting the search for a Git repository. */ private List ceilingDirectories; @@ -349,6 +354,43 @@ public File getIndexFile() { return indexFile; } + /** + * Set the initial branch of the new repository. If not specified + * ({@code null} or empty), fall back to the default name (currently + * master). + * + * @param branch + * initial branch name of the new repository. If {@code null} or + * empty the configured default branch will be used. + * @return {@code this} + * @throws InvalidRefNameException + * if the branch name is not valid + * + * @since 5.11 + */ + public B setInitialBranch(String branch) throws InvalidRefNameException { + if (StringUtils.isEmptyOrNull(branch)) { + this.initialBranch = Constants.MASTER; + } else { + if (!Repository.isValidRefName(Constants.R_HEADS + branch)) { + throw new InvalidRefNameException(MessageFormat + .format(JGitText.get().branchNameInvalid, branch)); + } + this.initialBranch = branch; + } + return self(); + } + + /** + * Get the initial branch of the new repository. + * + * @return the initial branch of the new repository. + * @since 5.11 + */ + public @NonNull String getInitialBranch() { + return initialBranch; + } + /** * Read standard Git environment variables and configure from those. *

    diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index a7a832c1a..1e8a6c917 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -127,6 +127,8 @@ public static ListenerList getGlobalListenerList() { /** If not bare, the index file caching the working file states. */ private final File indexFile; + private final String initialBranch; + /** * Initialize a new repository instance. * @@ -138,6 +140,7 @@ protected Repository(BaseRepositoryBuilder options) { fs = options.getFS(); workTree = options.getWorkTree(); indexFile = options.getIndexFile(); + initialBranch = options.getInitialBranch(); } /** @@ -1033,6 +1036,16 @@ public String getBranch() throws IOException { return null; } + /** + * Get the initial branch name of a new repository + * + * @return the initial branch name of a new repository + * @since 5.11 + */ + protected @NonNull String getInitialBranch() { + return initialBranch; + } + /** * Objects known to exist but not expressed by {@link #getAllRefs()}. *

    From 927deed5a569bd09ad5d23e815bcc3d68d14de91 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Mon, 25 Jan 2021 02:43:18 +0100 Subject: [PATCH 32/35] init: add config option to set default for the initial branch name We introduced the option --initial-branch= to allow initializing a new repository with a different initial branch. To allow users to override the initial branch name more permanently (i.e. without having to specify the name manually for each 'git init'), introduce the 'init.defaultBranch' option. This option was added to git in 2.28.0. See https://git-scm.com/docs/git-config#Documentation/git-config.txt-initdefaultBranch Bug: 564794 Change-Id: I679b14057a54cd3d19e44460c4a5bd3a368ec848 Signed-off-by: Matthias Sohn --- .../jgit/pgm/internal/CLIText.properties | 2 +- .../org/eclipse/jgit/api/InitCommandTest.java | 52 +++++++++++++++++++ .../src/org/eclipse/jgit/api/InitCommand.java | 13 +++-- .../org/eclipse/jgit/lib/ConfigConstants.java | 13 +++++ 4 files changed, 76 insertions(+), 4 deletions(-) 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 a5142802d..83846ee8e 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 @@ -432,7 +432,7 @@ usage_updateRef=reference to update usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository usage_useNameInsteadOfOriginToTrackUpstream=use instead of 'origin' to track upstream usage_checkoutBranchAfterClone=check out named branch instead of remote's HEAD -usage_initialBranch=initial branch in the newly created repository (default 'master') +usage_initialBranch=initial branch of the newly created repository (default 'master', can be configured via config option init.defaultBranch) usage_viewCommitHistory=View commit history usage_orphan=Create a new orphan branch. The first commit made on this new branch will have no parents and it will be the root of a new history totally disconnected from other branches and commits. usernameFor=Username for {0}: diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java index 14c52c207..48d835ed2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/InitCommandTest.java @@ -22,8 +22,10 @@ import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.junit.MockSystemReader; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; @@ -63,6 +65,56 @@ public void testInitRepositoryMainInitialBranch() } } + @Test + public void testInitRepositoryCustomDefaultBranch() + throws Exception { + File directory = createTempDirectory("testInitRepository"); + InitCommand command = new InitCommand(); + command.setDirectory(directory); + MockSystemReader reader = (MockSystemReader) SystemReader.getInstance(); + StoredConfig c = reader.getUserConfig(); + String old = c.getString(ConfigConstants.CONFIG_INIT_SECTION, null, + ConfigConstants.CONFIG_KEY_DEFAULT_BRANCH); + c.setString(ConfigConstants.CONFIG_INIT_SECTION, null, + ConfigConstants.CONFIG_KEY_DEFAULT_BRANCH, "main"); + try (Git git = command.call()) { + Repository r = git.getRepository(); + assertNotNull(r); + assertEquals("refs/heads/main", r.getFullBranch()); + } finally { + c.setString(ConfigConstants.CONFIG_INIT_SECTION, null, + ConfigConstants.CONFIG_KEY_DEFAULT_BRANCH, old); + } + } + + @Test + public void testInitRepositoryNullInitialBranch() throws Exception { + File directory = createTempDirectory("testInitRepository"); + InitCommand command = new InitCommand(); + command.setDirectory(directory); + command.setInitialBranch("main"); + command.setInitialBranch(null); + try (Git git = command.call()) { + Repository r = git.getRepository(); + assertNotNull(r); + assertEquals("refs/heads/master", r.getFullBranch()); + } + } + + @Test + public void testInitRepositoryEmptyInitialBranch() throws Exception { + File directory = createTempDirectory("testInitRepository"); + InitCommand command = new InitCommand(); + command.setDirectory(directory); + command.setInitialBranch("main"); + command.setInitialBranch(""); + try (Git git = command.call()) { + Repository r = git.getRepository(); + assertNotNull(r); + assertEquals("refs/heads/master", r.getFullBranch()); + } + } + @Test public void testInitNonEmptyRepository() throws IOException, JGitInternalException, GitAPIException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java index b2f7354ed..240290f4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java @@ -17,11 +17,14 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; /** @@ -39,7 +42,7 @@ public class InitCommand implements Callable { private FS fs; - private String initialBranch = Constants.MASTER; + private String initialBranch; /** * {@inheritDoc} @@ -90,12 +93,16 @@ public Git call() throws GitAPIException { builder.setWorkTree(new File(dStr)); } } - builder.setInitialBranch(initialBranch); + builder.setInitialBranch(StringUtils.isEmptyOrNull(initialBranch) + ? SystemReader.getInstance().getUserConfig().getString( + ConfigConstants.CONFIG_INIT_SECTION, null, + ConfigConstants.CONFIG_KEY_DEFAULT_BRANCH) + : initialBranch); Repository repository = builder.build(); if (!repository.getObjectDatabase().exists()) repository.create(bare); return new Git(repository, true); - } catch (IOException e) { + } catch (IOException | ConfigInvalidException e) { throw new JGitInternalException(e.getMessage(), e); } } 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 7381c905b..03c1ef904 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -715,4 +715,17 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_VERSION = "version"; + /** + * The "init" section + * + * @since 5.11 + */ + public static final String CONFIG_INIT_SECTION = "init"; + + /** + * The "defaultBranch" key + * + * @since 5.11 + */ + public static final String CONFIG_KEY_DEFAULT_BRANCH = "defaultbranch"; } From 4e745c57f7612123bc58d3ff96f95c472f9edc94 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 23 Feb 2021 13:11:56 +0100 Subject: [PATCH 33/35] FastIgnoreRule: include bad pattern in log message When a .gitignore pattern cannot be parsed include the pattern in the log message. Just reporting "not closed bracket" isn't helpful if the user doesn't know in which pattern the problem occurred. Even better would be to include the full path of the .gitignore file that contained the offending pattern. This is not implemented in this change; it may need new API and needs more thought. Bug: 571143 Change-Id: Id5b16d9cf550544ba3ad409a02041946fa8516ab Signed-off-by: Thomas Wolf --- .../org/eclipse/jgit/internal/JGitText.properties | 1 + .../src/org/eclipse/jgit/ignore/FastIgnoreRule.java | 7 ++++++- .../src/org/eclipse/jgit/internal/JGitText.java | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) 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 6d4a07799..9ec2c7d74 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -30,6 +30,7 @@ badEntryDelimiter=Bad entry delimiter badEntryName=Bad entry name: {0} badEscape=Bad escape: {0} badGroupHeader=Bad group header +badIgnorePattern=Cannot parse .gitignore pattern ''{0}'' badObjectType=Bad object type: {0} badRef=Bad ref: {0}: {1} badSectionEntry=Bad section entry: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index d7e4f79d2..8b35406e3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -14,8 +14,11 @@ import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace; +import java.text.MessageFormat; + import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.internal.PathMatcher; +import org.eclipse.jgit.internal.JGitText; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,7 +99,9 @@ public FastIgnoreRule(String pattern) { Character.valueOf(PATH_SEPARATOR), dirOnly); } catch (InvalidPatternException e) { m = NO_MATCH; - LOG.error(e.getMessage(), e); + LOG.error(MessageFormat.format( + JGitText.get().badIgnorePattern, + e.getPattern()), e); } this.matcher = m; } 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 af7d50aae..cf915afdc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -58,6 +58,7 @@ public static JGitText get() { /***/ public String badEntryName; /***/ public String badEscape; /***/ public String badGroupHeader; + /***/ public String badIgnorePattern; /***/ public String badObjectType; /***/ public String badRef; /***/ public String badSectionEntry; From 29697d86c5f66983feda94623477688211d06bde Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 23 Feb 2021 18:10:08 +0100 Subject: [PATCH 34/35] IgnoreNode: include path to file for invalid .gitignore patterns Include the full file path of the .gitignore file and the line number of the invalid pattern. Also include the pattern itself. .gitignore files inside the repository are reported with their repository-relative path; files outside (from git config core.excludesFile or .git/info/exclude) are reported with their full absolute path. Bug: 571143 Change-Id: Ibe5969679bc22cff923c62e3ab9801d90d6d06d1 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/internal/JGitText.properties | 1 + .../eclipse/jgit/ignore/FastIgnoreRule.java | 38 ++++++++------- .../org/eclipse/jgit/ignore/IgnoreNode.java | 46 +++++++++++++++++-- .../org/eclipse/jgit/internal/JGitText.java | 1 + .../jgit/treewalk/WorkingTreeIterator.java | 20 +++++--- 5 files changed, 80 insertions(+), 26 deletions(-) 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 9ec2c7d74..c00203dd0 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -31,6 +31,7 @@ badEntryName=Bad entry name: {0} badEscape=Bad escape: {0} badGroupHeader=Bad group header badIgnorePattern=Cannot parse .gitignore pattern ''{0}'' +badIgnorePatternFull=File {0} line {1}: cannot parse pattern ''{2}'': {3} badObjectType=Bad object type: {0} badRef=Bad ref: {0}: {1} badSectionEntry=Bad section entry: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index 8b35406e3..9dd565ff0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014, Andrey Loskutov and others + * Copyright (C) 2014, 2021 Andrey Loskutov 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 @@ -39,11 +39,11 @@ public class FastIgnoreRule { */ public static final char PATH_SEPARATOR = '/'; - private final IMatcher matcher; + private IMatcher matcher; - private final boolean inverse; + private boolean inverse; - private final boolean dirOnly; + private boolean dirOnly; /** * Constructor for FastIgnoreRule @@ -55,8 +55,23 @@ public class FastIgnoreRule { * (comment), this rule doesn't match anything. */ public FastIgnoreRule(String pattern) { - if (pattern == null) + this(); + try { + parse(pattern); + } catch (InvalidPatternException e) { + LOG.error(MessageFormat.format(JGitText.get().badIgnorePattern, + e.getPattern()), e); + } + } + + FastIgnoreRule() { + matcher = IMatcher.NO_MATCH; + } + + void parse(String pattern) throws InvalidPatternException { + if (pattern == null) { throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$ + } if (pattern.length() == 0) { dirOnly = false; inverse = false; @@ -93,17 +108,8 @@ public FastIgnoreRule(String pattern) { return; } } - IMatcher m; - try { - m = PathMatcher.createPathMatcher(pattern, - Character.valueOf(PATH_SEPARATOR), dirOnly); - } catch (InvalidPatternException e) { - m = NO_MATCH; - LOG.error(MessageFormat.format( - JGitText.get().badIgnorePattern, - e.getPattern()), e); - } - this.matcher = m; + this.matcher = PathMatcher.createPathMatcher(pattern, + Character.valueOf(PATH_SEPARATOR), dirOnly); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java index 1a1b2d302..4e7f126a6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Red Hat Inc. and others + * Copyright (C) 2010, 2021 Red Hat Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -15,11 +15,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.internal.JGitText; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a bundle of ignore rules inherited from a base directory. @@ -27,6 +32,9 @@ * This class is not thread safe, it maintains state about the last match. */ public class IgnoreNode { + + private static final Logger LOG = LoggerFactory.getLogger(IgnoreNode.class); + /** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */ public enum MatchResult { /** The file is not ignored, due to a rule saying its not ignored. */ @@ -54,7 +62,7 @@ public enum MatchResult { * Create an empty ignore node with no rules. */ public IgnoreNode() { - rules = new ArrayList<>(); + this(new ArrayList<>()); } /** @@ -77,15 +85,47 @@ public IgnoreNode(List rules) { * Error thrown when reading an ignore file. */ public void parse(InputStream in) throws IOException { + parse(null, in); + } + + /** + * Parse files according to gitignore standards. + * + * @param sourceName + * identifying the source of the stream + * @param in + * input stream holding the standard ignore format. The caller is + * responsible for closing the stream. + * @throws java.io.IOException + * Error thrown when reading an ignore file. + * @since 5.11 + */ + public void parse(String sourceName, InputStream in) throws IOException { BufferedReader br = asReader(in); String txt; + int lineNumber = 1; while ((txt = br.readLine()) != null) { if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$ - FastIgnoreRule rule = new FastIgnoreRule(txt); + FastIgnoreRule rule = new FastIgnoreRule(); + try { + rule.parse(txt); + } catch (InvalidPatternException e) { + if (sourceName != null) { + LOG.error(MessageFormat.format( + JGitText.get().badIgnorePatternFull, sourceName, + Integer.toString(lineNumber), e.getPattern(), + e.getLocalizedMessage()), e); + } else { + LOG.error(MessageFormat.format( + JGitText.get().badIgnorePattern, + e.getPattern()), e); + } + } if (!rule.isEmpty()) { rules.add(rule); } } + lineNumber++; } } 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 cf915afdc..9d215ca45 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -59,6 +59,7 @@ public static JGitText get() { /***/ public String badEscape; /***/ public String badGroupHeader; /***/ public String badIgnorePattern; + /***/ public String badIgnorePatternFull; /***/ public String badObjectType; /***/ public String badRef; /***/ public String badSectionEntry; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 6faf42bcf..55b7d6279 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -2,7 +2,7 @@ * Copyright (C) 2008, Shawn O. Pearce * Copyright (C) 2010, Christian Halstrick * Copyright (C) 2010, Matthias Sohn - * Copyright (C) 2012-2020, Robin Rosenberg and others + * Copyright (C) 2012-2021, Robin Rosenberg and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -800,7 +800,10 @@ protected void init(Entry[] list) { if (Constants.DOT_GIT.equals(name)) continue; if (Constants.DOT_GIT_IGNORE.equals(name)) - ignoreNode = new PerDirectoryIgnoreNode(e); + ignoreNode = new PerDirectoryIgnoreNode( + TreeWalk.pathOf(path, 0, pathOffset) + + Constants.DOT_GIT_IGNORE, + e); if (Constants.DOT_GIT_ATTRIBUTES.equals(name)) attributesNode = new PerDirectoryAttributesNode(e); if (i != o) @@ -1274,17 +1277,20 @@ public String toString() { /** Magic type indicating we know rules exist, but they aren't loaded. */ private static class PerDirectoryIgnoreNode extends IgnoreNode { - final Entry entry; + protected final Entry entry; - PerDirectoryIgnoreNode(Entry entry) { + private final String name; + + PerDirectoryIgnoreNode(String name, Entry entry) { super(Collections. emptyList()); + this.name = name; this.entry = entry; } IgnoreNode load() throws IOException { IgnoreNode r = new IgnoreNode(); try (InputStream in = entry.openInputStream()) { - r.parse(in); + r.parse(name, in); } return r.getRules().isEmpty() ? null : r; } @@ -1295,7 +1301,7 @@ private static class RootIgnoreNode extends PerDirectoryIgnoreNode { final Repository repository; RootIgnoreNode(Entry entry, Repository repository) { - super(entry); + super(entry != null ? entry.getName() : null, entry); this.repository = repository; } @@ -1329,7 +1335,7 @@ private static void loadRulesFromFile(IgnoreNode r, File exclude) throws FileNotFoundException, IOException { if (FS.DETECTED.exists(exclude)) { try (FileInputStream in = new FileInputStream(exclude)) { - r.parse(in); + r.parse(exclude.getAbsolutePath(), in); } } } From 9bfb0f3a4ec856dcbebb477a1ee8803a3c47c194 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 23 Feb 2021 22:17:07 +0100 Subject: [PATCH 35/35] [releng] japicmp: update last release version The baseline for the 5.11 release is 5.10.0.202012080955-r. Change-Id: Ied9b42dc58ba981e5586fa58d1b3e70a39c78a10 Signed-off-by: Thomas Wolf --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7e3acbcf3..e7d35ff8c 100644 --- a/pom.xml +++ b/pom.xml @@ -151,7 +151,7 @@ 1.8 ${project.build.directory}/META-INF/MANIFEST.MF - 5.9.0.202009080501-r + 5.10.0.202012080955-r 2.6.0 0.1.55 1.1.1