diff --git a/.bazelversion b/.bazelversion index 8faff82c7..fcdb2e109 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -4.0.0rc2 +4.0.0 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..224968a26 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( @@ -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/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..925536e5d 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,23 @@ junit_tests( srcs = glob(["tst/**/*.java"]), tags = ["bc"], deps = [ + "//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", ], ) + +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..57c374795 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,18 @@ 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)" -Export-Package: org.eclipse.jgit.gpg.bc.internal;x-internal:=true +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)", + 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, + 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/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/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 000000000..355462c09 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.asc differ 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 000000000..afa459c83 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A.key differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc new file mode 100644 index 000000000..362e2108e Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.asc differ 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 000000000..cef72f623 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9.key differ 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 000000000..f41201990 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.asc differ 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 000000000..b8765aaac Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11.key differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc new file mode 100644 index 000000000..c6e0408b3 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.asc differ 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 000000000..63617c09b Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/F727FAB884DA3BD402B6E0F5472E108D21033124.key differ 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 000000000..8427cfcc0 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool256.asc differ 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 000000000..bdb20fe93 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool384.asc differ 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 000000000..5b4bca2c6 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/brainpool512.asc differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc new file mode 100644 index 000000000..db067325c Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/dsa-elgamal.asc differ 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 000000000..636a5a95f Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/ed25519.asc differ 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 000000000..405afad42 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/faked.key differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc new file mode 100644 index 000000000..fd1509e9b Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp256.asc differ 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 000000000..b2b59959e Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp384.asc differ 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 000000000..db18f81a2 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/nistp521.asc differ 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 000000000..e74df7a2a Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/rsa.asc differ 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 000000000..837f8a867 Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/secp256k1.asc differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc new file mode 100644 index 000000000..d531d7a8b Binary files /dev/null and b/org.eclipse.jgit.gpg.bc.test/tst-rsrc/org/eclipse/jgit/gpg/bc/internal/keys/x25519.asc differ diff --git a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java new file mode 100644 index 000000000..e30080258 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/KeyGrip25519Test.java @@ -0,0 +1,61 @@ +/* + * 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 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.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..5e5e30331 --- /dev/null +++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java @@ -0,0 +1,163 @@ +/* + * 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; + + final boolean keyValue; + + TestData(String name, boolean encrypted, boolean keyValue) { + this.name = name; + this.encrypted = encrypted; + this.keyValue = keyValue; + } + + @Override + public String toString() { + return name; + } + } + + @Parameters(name = "{0}") + public static TestData[] initTestData() { + return new TestData[] { + new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false), + new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true), + new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true), + new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true), + new TestData("faked", false, true) }; + } + + 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 { + 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) { + 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, + data.encrypted ? () -> "nonsense".toCharArray() + : null, + 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 655dcca80..afb0ee151 100644 --- a/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF @@ -8,22 +8,30 @@ 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.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)", 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;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.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/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/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..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,11 +1,36 @@ +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? +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 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 +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/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(); + } +} 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..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 @@ -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; @@ -18,16 +27,41 @@ 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; /***/ public String gpgNoKeyring; /***/ 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; + /***/ public String signatureNoPublicKey; + /***/ 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 1389f8bd1..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 @@ -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,25 +14,22 @@ 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; 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; import org.bouncycastle.gpg.keybox.KeyBlob; import org.bouncycastle.gpg.keybox.KeyBox; @@ -50,15 +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; @@ -78,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$ @@ -155,16 +145,13 @@ public BouncyCastleGpgKeyLocator(String signingKey, private PGPSecretKey attemptParseSecretKey(Path keyFile, PGPDigestCalculatorProvider calculatorProvider, - PBEProtectionRemoverFactory passphraseProvider, - PGPPublicKey publicKey) { + 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); - } catch (IOException | PGPException | ClassCastException e) { - if (log.isDebugEnabled()) - log.debug("Ignoring unreadable file '{}': {}", keyFile, //$NON-NLS-1$ - e.getMessage(), e); - return null; + return SecretKeys.readSecretKey(in, calculatorProvider, + passphraseSupplier, publicKey); } } @@ -247,16 +234,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 +273,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 +289,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 +301,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 +360,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 +384,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 @@ -444,67 +457,59 @@ 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(); + clearPrompt = true; + PGPSecretKey secretKey = null; + try { + secretKey = attemptParseSecretKey(keyFile, calculatorProvider, + () -> passphrasePrompt.getPassphrase( + publicKey.getFingerprint(), userKeyboxPath), + publicKey); + } catch (PGPException e) { + throw new PGPException(MessageFormat.format( + BCText.get().gpgFailedToParseSecretKey, + keyFile.toAbsolutePath()), e); } - 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 (secretKey != null) { + if (!secretKey.isSigningKey()) { + throw new PGPException(MessageFormat.format( + BCText.get().gpgNotASigningKey, signingKey)); } - if (attempts > 0 || allPaths.isEmpty()) { - break; - } - // allPaths contains only the encrypted keys now. - passphraseProvider = new JcePBEProtectionRemoverFactory( - passphrasePrompt.getPassphrase( - publicKey.getFingerprint(), userKeyboxPath)); + clearPrompt = false; + return new BouncyCastleGpgKey(secretKey, userKeyboxPath); } - - passphrasePrompt.clear(); 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(); + } } } @@ -562,6 +567,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 +579,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 +598,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 +644,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 +656,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/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/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 449c4a487..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 @@ -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 @@ -34,18 +34,22 @@ 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.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; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.util.StringUtils; /** - * GPG Signer using BouncyCastle library + * GPG Signer using the BouncyCastle library. */ public class BouncyCastleGpgSigner extends GpgSigner implements GpgObjectSigner { @@ -70,13 +74,32 @@ 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, committer, passphrasePrompt); return gpgKey != null; - } catch (PGPException | IOException | NoSuchAlgorithmException - | NoSuchProviderException | URISyntaxException e) { + } catch (CanceledException e) { + throw e; + } catch (Exception e) { return false; } } @@ -101,17 +124,28 @@ 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, - committer, passphrasePrompt); + committer, + passphrasePrompt); PGPSecretKey secretKey = gpgKey.getSecretKey(); if (secretKey == null) { throw new JGitInternalException( @@ -178,7 +212,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.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; + } + +} 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..269a1ba0f --- /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. + PBEProtectionRemoverFactory decryptor = null; + if (matches(data, 4, PROTECTED_KEY)) { + // AES/CBC encrypted. + decryptor = new JcePBEProtectionRemoverFactory( + passphraseSupplier.getPassphrase(), + calculatorProvider); + } + try (InputStream sIn = new ByteArrayInputStream(data)) { + return parser.parseSecretKey(sIn, decryptor, publicKey); + } + } + // Assume it's the new key-value format. + try (ByteArrayInputStream keyIn = new ByteArrayInputStream(data)) { + byte[] rawData = keyFromNameValueFormat(keyIn); + if (!matches(rawData, 1, PROTECTED_KEY)) { + // Not encrypted human-readable format. + try (InputStream sIn = new ByteArrayInputStream( + convertSexpression(rawData))) { + return parser.parseSecretKey(sIn, null, publicKey); + } + } + // An encrypted key from a key-value file. Most likely AES/OCB + // encrypted. + boolean isOCB[] = { false }; + byte[] sExp = convertSexpression(rawData, isOCB); + PBEProtectionRemoverFactory decryptor; + if (isOCB[0]) { + decryptor = new OCBPBEProtectionRemoverFactory( + passphraseSupplier.getPassphrase(), + calculatorProvider, getAad(sExp)); + } else { + decryptor = new JcePBEProtectionRemoverFactory( + passphraseSupplier.getPassphrase(), + calculatorProvider); + } + try (InputStream sIn = new ByteArrayInputStream(sExp)) { + return parser.parseSecretKey(sIn, decryptor, publicKey); + } + } + } catch (IOException e) { + throw new PGPException(e.getLocalizedMessage(), e); + } + } + + /** + * 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; + } +} 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.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.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); } } 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.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); 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..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 afe7936c2..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 b7de53dfe..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 3f9d17daf..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 9b8490888..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 b533aa178..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 45c665a15..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 a3027bc17..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 578fe234b..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 567767cee..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 9cc4290f6..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 b7a8c403e..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 8084e343b..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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 d51e0235a..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,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -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..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/S20210105214148.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/S20210216215844.tpd similarity index 84% rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210216215844.tpd index 58f2417d9..29e5bc800 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210105214148.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210216215844.tpd @@ -1,7 +1,7 @@ -target "S20210105214148" 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/S20210105214148/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] @@ -26,16 +26,16 @@ location "https://download.eclipse.org/tools/orbit/downloads/drops/S202101052141 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.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.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.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] 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/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.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 afa253eeb..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 @@ -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,11 +426,13 @@ 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 usage_useNameInsteadOfOriginToTrackUpstream=use instead of 'origin' to track upstream usage_checkoutBranchAfterClone=check out named branch instead of remote's HEAD +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.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.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.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.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/org.eclipse.jgit.test/BUILD b/org.eclipse.jgit.test/BUILD index f12646e85..c9b5d3726 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", @@ -44,8 +44,6 @@ EXCLUDED = [ PKG + "api/SecurityManagerMissingPermissionsTest.java", ] -RESOURCES = glob(["resources/**"]) - tests(tests = glob( ["tst/**/*.java"], exclude = HELPERS + DATA + EXCLUDED, 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..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 @@ -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; @@ -21,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; @@ -42,7 +45,73 @@ 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()); + } + } + + @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()); } } @@ -72,6 +141,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.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/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/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.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.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.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/.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/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index a8b2e563f..c00203dd0 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,8 @@ badEntryDelimiter=Bad entry delimiter 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} @@ -568,6 +570,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 @@ -622,6 +625,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 +768,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/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/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/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/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/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java index 41fcf29ed..240290f4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java @@ -15,12 +15,16 @@ 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.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; /** @@ -38,6 +42,8 @@ public class InitCommand implements Callable { private FS fs; + private String initialBranch; + /** * {@inheritDoc} *

    @@ -87,11 +93,16 @@ public Git call() throws GitAPIException { builder.setWorkTree(new File(dStr)); } } + 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); } } @@ -184,4 +195,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/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/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/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/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/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index 4059b16b3..ce3ad2239 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -9,12 +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.PrintStream; -import java.io.UnsupportedEncodingException; +import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.concurrent.Callable; import org.eclipse.jgit.api.errors.AbortedByHookException; @@ -35,21 +33,21 @@ * 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; /** * The output stream to be used by the hook. */ - protected final PrintStream outputStream; + private final OutputStream outputStream; /** * The error stream to be used by the hook. */ - protected final PrintStream errorStream; + private final OutputStream errorStream; /** * Constructor for GitHook. @@ -63,7 +61,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 +77,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 +135,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 +145,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; } @@ -156,33 +154,47 @@ protected PrintStream 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()); - 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( - new String(errorByteArray.toByteArray(), UTF_8), - 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. + } + } 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..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 @@ -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; @@ -36,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 @@ -52,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; @@ -90,15 +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(e.getMessage(), 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 07fb59ddf..9d215ca45 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 @@ -58,6 +58,8 @@ public static JGitText get() { /***/ public String badEntryName; /***/ public String badEscape; /***/ public String badGroupHeader; + /***/ public String badIgnorePattern; + /***/ public String badIgnorePatternFull; /***/ public String badObjectType; /***/ public String badRef; /***/ public String badSectionEntry; @@ -596,6 +598,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; @@ -650,6 +653,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 +796,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/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) 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/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java index db454b92b..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 @@ -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), + Integer.valueOf(random.nextInt())); } /** 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..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()) { @@ -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); 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/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. 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)) { 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/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 954a75cb2..03c1ef904 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"; @@ -707,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"; } 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; } 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/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()}. *

    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; 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; } 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/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); 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 { 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); } } } 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); 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(); + } +} diff --git a/pom.xml b/pom.xml index e2aa7cabd..e7d35ff8c 100644 --- a/pom.xml +++ b/pom.xml @@ -151,8 +151,8 @@ 1.8 ${project.build.directory}/META-INF/MANIFEST.MF - 5.9.0.202009080501-r - 2.4.0 + 5.10.0.202012080955-r + 2.6.0 0.1.55 1.1.1 1.1.7 @@ -162,10 +162,10 @@ 1.19 4.3.1 3.1.0 - 9.4.35.v20201120 + 9.4.36.v20210114 0.14.4 - 4.5.10 - 4.4.12 + 4.5.13 + 4.4.14 1.7.30 1.2.15 3.2.0