From c2204bb6835e4e6dc666bb34eaea910fb1484092 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Wed, 13 Oct 2021 00:28:38 +0200 Subject: [PATCH 01/24] Make the buffer size for text/binary detection configurable The various streams used in JGit for text/binary and CR-LF detection used different buffer sizes. Most used 8000, but one used 8KiB, and one used 8096 (SIC!) bytes. Considering only the first 8kB of a file/blob is not sufficient; it may give behavior incompatible with C git. C git considers the whole blob; since it uses memory-mapped files it can do so with acceptable performance. Doing this in JGit would most likely incur a noticeable performance penalty. But 8kB is a bit small; in the file in bug 576971 the limit was hit before the first CR-LF, which occurred on line 155 at offset 9759 in the file. Make RawText.FIRST_FEW_BYTES only a default and minimum setting, and set it to 8KiB. Make the actual buffer size configurable: provide static methods getBufferSize() and setBuffersize(), and use getBufferSize() throughout instead of the constant. This enables users of the JGit library to set their own possibly larger buffer size. Bug: 576971 Change-Id: I447762c9a5147a521f73d2864ba59ed89f555d54 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/diff/DiffFormatterTest.java | 16 ++--- .../org/eclipse/jgit/merge/MergerTest.java | 8 ++- .../jgit/util/io/AutoCRLFInputStreamTest.java | 4 +- .../util/io/AutoCRLFOutputStreamTest.java | 4 +- .../src/org/eclipse/jgit/diff/RawText.java | 67 +++++++++++++++---- .../jgit/util/io/AutoCRLFInputStream.java | 9 ++- .../jgit/util/io/AutoCRLFOutputStream.java | 9 ++- .../jgit/util/io/AutoLFInputStream.java | 11 +-- .../jgit/util/io/AutoLFOutputStream.java | 12 ++-- 9 files changed, 94 insertions(+), 46 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java index b694f4aaf..a093cc78d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterTest.java @@ -608,7 +608,7 @@ public void testDiffAutoCrlfSmallFile() throws Exception { public void testDiffAutoCrlfMediumFile() throws Exception { String content = mediumCrLfString(); String expectedDiff = "diff --git a/test.txt b/test.txt\n" - + "index 215c502..c10f08c 100644\n" // + + "index 6d9ffed..50d7b5a 100644\n" // + "--- a/test.txt\n" // + "+++ b/test.txt\n" // + "@@ -1,4 +1,5 @@\n" // @@ -624,7 +624,7 @@ public void testDiffAutoCrlfMediumFile() throws Exception { public void testDiffAutoCrlfLargeFile() throws Exception { String content = largeCrLfString(); String expectedDiff = "diff --git a/test.txt b/test.txt\n" - + "index 7014942..c0487a7 100644\n" // + + "index d6399a1..de26ce5 100644\n" // + "--- a/test.txt\n" // + "+++ b/test.txt\n" // + "@@ -1,4 +1,5 @@\n" @@ -665,9 +665,9 @@ private void doAutoCrLfTest(String content, String expectedDiff) private static String largeCrLfString() { String line = "012345678901234567890123456789012345678901234567\r\n"; - StringBuilder builder = new StringBuilder( - 2 * RawText.FIRST_FEW_BYTES); - while (builder.length() < 2 * RawText.FIRST_FEW_BYTES) { + int bufferSize = RawText.getBufferSize(); + StringBuilder builder = new StringBuilder(2 * bufferSize); + while (builder.length() < 2 * bufferSize) { builder.append(line); } return builder.toString(); @@ -677,9 +677,9 @@ private static String mediumCrLfString() { // Create a CR-LF string longer than RawText.FIRST_FEW_BYTES whose // canonical representation is shorter than RawText.FIRST_FEW_BYTES. String line = "01234567\r\n"; // 10 characters - StringBuilder builder = new StringBuilder( - RawText.FIRST_FEW_BYTES + line.length()); - while (builder.length() <= RawText.FIRST_FEW_BYTES) { + int bufferSize = RawText.getBufferSize(); + StringBuilder builder = new StringBuilder(bufferSize + line.length()); + while (builder.length() <= bufferSize) { builder.append(line); } return builder.toString(); 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 6cbb4a89b..dd8573d2b 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 @@ -33,6 +33,7 @@ import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -826,6 +827,8 @@ public void checkContentMergeLargeBinaries(MergeStrategy strategy) throws Except RevCommit sideCommit = git.commit().setAll(true) .setMessage("modified file l 1500").call(); + int originalBufferSize = RawText.getBufferSize(); + int smallBufferSize = RawText.setBufferSize(8000); try (ObjectInserter ins = db.newObjectInserter()) { // Check that we don't read the large blobs. ObjectInserter forbidInserter = new ObjectInserter.Filter() { @@ -836,7 +839,8 @@ protected ObjectInserter delegate() { @Override public ObjectReader newReader() { - return new BigReadForbiddenReader(super.newReader(), 8000); + return new BigReadForbiddenReader(super.newReader(), + smallBufferSize); } }; @@ -844,6 +848,8 @@ public ObjectReader newReader() { (ResolveMerger) strategy.newMerger(forbidInserter, db.getConfig()); boolean noProblems = merger.merge(masterCommit, sideCommit); assertFalse(noProblems); + } finally { + RawText.setBufferSize(originalBufferSize); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java index ae8c7ec7a..cd4e50339 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; +import org.eclipse.jgit.diff.RawText; import org.junit.Assert; import org.junit.Test; @@ -38,7 +39,8 @@ public void test() throws IOException { @Test public void testBoundary() throws IOException { - for (int i = AutoCRLFInputStream.BUFFER_SIZE - 10; i < AutoCRLFInputStream.BUFFER_SIZE + 10; i++) { + int boundary = RawText.getBufferSize(); + for (int i = boundary - 10; i < boundary + 10; i++) { String s1 = Strings.repeat("a", i); assertNoCrLf(s1, s1); String s2 = Strings.repeat("\0", i); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java index db2f6da31..150df0845 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.io.OutputStream; +import org.eclipse.jgit.diff.RawText; import org.junit.Assert; import org.junit.Test; @@ -40,7 +41,8 @@ public void test() throws IOException { @Test public void testBoundary() throws IOException { - for (int i = AutoCRLFOutputStream.BUFFER_SIZE - 10; i < AutoCRLFOutputStream.BUFFER_SIZE + 10; i++) { + int bufferSize = RawText.getBufferSize(); + for (int i = bufferSize - 10; i < bufferSize + 10; i++) { String s1 = Strings.repeat("a", i); assertNoCrLf(s1, s1); String s2 = Strings.repeat("\0", i); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java index d09da019d..914fa5f6f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java @@ -17,6 +17,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.errors.LargeObjectException; @@ -38,11 +39,20 @@ * they are converting from "line number" to "element index". */ public class RawText extends Sequence { + /** A RawText of length 0 */ public static final RawText EMPTY_TEXT = new RawText(new byte[0]); - /** Number of bytes to check for heuristics in {@link #isBinary(byte[])} */ - static final int FIRST_FEW_BYTES = 8000; + /** + * Default and minimum for {@link #BUFFER_SIZE}. + */ + private static final int FIRST_FEW_BYTES = 8 * 1024; + + /** + * Number of bytes to check for heuristics in {@link #isBinary(byte[])}. + */ + private static final AtomicInteger BUFFER_SIZE = new AtomicInteger( + FIRST_FEW_BYTES); /** The file content for this sequence. */ protected final byte[] content; @@ -247,6 +257,33 @@ public static boolean isBinary(byte[] raw) { return isBinary(raw, raw.length); } + /** + * Obtains the buffer size to use for analyzing whether certain content is + * text or binary, or what line endings are used if it's text. + * + * @return the buffer size, by default {@link #FIRST_FEW_BYTES} bytes + * @since 6.0 + */ + public static int getBufferSize() { + return BUFFER_SIZE.get(); + } + + /** + * Sets the buffer size to use for analyzing whether certain content is text + * or binary, or what line endings are used if it's text. If the given + * {@code bufferSize} is smaller than {@link #FIRST_FEW_BYTES} set the + * buffer size to {@link #FIRST_FEW_BYTES}. + * + * @param bufferSize + * Size to set + * @return the size actually set + * @since 6.0 + */ + public static int setBufferSize(int bufferSize) { + int newSize = Math.max(FIRST_FEW_BYTES, bufferSize); + return BUFFER_SIZE.updateAndGet(curr -> newSize); + } + /** * Determine heuristically whether the bytes contained in a stream * represents binary (as opposed to text) content. @@ -263,7 +300,7 @@ public static boolean isBinary(byte[] raw) { * if input stream could not be read */ public static boolean isBinary(InputStream raw) throws IOException { - final byte[] buffer = new byte[FIRST_FEW_BYTES]; + final byte[] buffer = new byte[getBufferSize()]; int cnt = 0; while (cnt < buffer.length) { final int n = raw.read(buffer, cnt, buffer.length - cnt); @@ -287,13 +324,16 @@ public static boolean isBinary(InputStream raw) throws IOException { * @return true if raw is likely to be a binary file, false otherwise */ public static boolean isBinary(byte[] raw, int length) { - // Same heuristic as C Git - if (length > FIRST_FEW_BYTES) - length = FIRST_FEW_BYTES; - for (int ptr = 0; ptr < length; ptr++) - if (raw[ptr] == '\0') + // Same heuristic as C Git (except for the buffer size) + int maxLength = getBufferSize(); + if (length > maxLength) { + length = maxLength; + } + for (int ptr = 0; ptr < length; ptr++) { + if (raw[ptr] == '\0') { return true; - + } + } return false; } @@ -329,7 +369,7 @@ public static boolean isCrLfText(byte[] raw) { * @since 5.3 */ public static boolean isCrLfText(InputStream raw) throws IOException { - byte[] buffer = new byte[FIRST_FEW_BYTES]; + byte[] buffer = new byte[getBufferSize()]; int cnt = 0; while (cnt < buffer.length) { int n = raw.read(buffer, cnt, buffer.length - cnt); @@ -409,15 +449,16 @@ public static RawText load(ObjectLoader ldr, int threshold) throw new BinaryBlobException(); } - if (sz <= FIRST_FEW_BYTES) { - byte[] data = ldr.getCachedBytes(FIRST_FEW_BYTES); + int bufferSize = getBufferSize(); + if (sz <= bufferSize) { + byte[] data = ldr.getCachedBytes(bufferSize); if (isBinary(data)) { throw new BinaryBlobException(); } return new RawText(data); } - byte[] head = new byte[FIRST_FEW_BYTES]; + byte[] head = new byte[bufferSize]; try (InputStream stream = ldr.openStream()) { int off = 0; int left = head.length; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java index 9da890343..1b03d097b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java @@ -21,16 +21,15 @@ * * Existing CRLF are not expanded to CRCRLF, but retained as is. * - * Optionally, a binary check on the first 8000 bytes is performed and in case - * of binary files, canonicalization is turned off (for the complete file). + * Optionally, a binary check on the first {@link RawText#getBufferSize()} bytes + * is performed and in case of binary files, canonicalization is turned off (for + * the complete file). */ public class AutoCRLFInputStream extends InputStream { - static final int BUFFER_SIZE = 8096; - private final byte[] single = new byte[1]; - private final byte[] buf = new byte[BUFFER_SIZE]; + private final byte[] buf = new byte[RawText.getBufferSize()]; private final InputStream in; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java index 97fe01e5d..05e271feb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java @@ -20,18 +20,17 @@ * * Existing CRLF are not expanded to CRCRLF, but retained as is. * - * A binary check on the first 8000 bytes is performed and in case of binary - * files, canonicalization is turned off (for the complete file). + * A binary check on the first {@link RawText#getBufferSize()} bytes is + * performed and in case of binary files, canonicalization is turned off (for + * the complete file). */ public class AutoCRLFOutputStream extends OutputStream { - static final int BUFFER_SIZE = 8000; - private final OutputStream out; private int buf = -1; - private byte[] binbuf = new byte[BUFFER_SIZE]; + private byte[] binbuf = new byte[RawText.getBufferSize()]; private byte[] onebytebuf = new byte[1]; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java index 0e335a9dc..b6d1848b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java @@ -25,10 +25,11 @@ * Existing single CR are not changed to LF but are retained as is. *

*

- * Optionally, a binary check on the first 8kB is performed and in case of - * binary files, canonicalization is turned off (for the complete file). If - * binary checking determines that the input is CR/LF-delimited text and the - * stream has been created for checkout, canonicalization is also turned off. + * Optionally, a binary check on the first {@link RawText#getBufferSize()} bytes + * is performed and in case of binary files, canonicalization is turned off (for + * the complete file). If binary checking determines that the input is + * CR/LF-delimited text and the stream has been created for checkout, + * canonicalization is also turned off. *

* * @since 4.3 @@ -64,7 +65,7 @@ public enum StreamFlag { private final byte[] single = new byte[1]; - private final byte[] buf = new byte[8 * 1024]; + private final byte[] buf = new byte[RawText.getBufferSize()]; private final InputStream in; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java index 195fdb421..e08a53f50 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java @@ -22,23 +22,21 @@ * Existing single CR are not changed to LF, but retained as is. *

*

- * A binary check on the first 8000 bytes is performed and in case of binary - * files, canonicalization is turned off (for the complete file). If the binary - * check determines that the input is not binary but text with CR/LF, - * canonicalization is also turned off. + * A binary check on the first {@link RawText#getBufferSize()} bytes is + * performed and in case of binary files, canonicalization is turned off (for + * the complete file). If the binary check determines that the input is not + * binary but text with CR/LF, canonicalization is also turned off. *

* * @since 4.3 */ public class AutoLFOutputStream extends OutputStream { - static final int BUFFER_SIZE = 8000; - private final OutputStream out; private int buf = -1; - private byte[] binbuf = new byte[BUFFER_SIZE]; + private byte[] binbuf = new byte[RawText.getBufferSize()]; private byte[] onebytebuf = new byte[1]; From 3444a3be8c8a567f944fd7b81838e615852d787a Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 30 Oct 2021 19:37:44 +0200 Subject: [PATCH 02/24] Factor out parsing git-style size numbers to StringUtils Move the code to parse numbers with an optional 'k', 'm', or 'g' suffix from the config file handling to StringUtils. This enables me to re-use it in EGit, which has duplicate code in StorageSizeFieldEditor. As this is generally useful functionality, providing it in the library makes sense. Change-Id: I86e4f5f62e14f99b35726b198ba3bbf1669418d9 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/util/StringUtilsTest.java | 83 +++++++++++ .../eclipse/jgit/internal/JGitText.properties | 1 + .../org/eclipse/jgit/internal/JGitText.java | 1 + .../src/org/eclipse/jgit/lib/Config.java | 18 +-- .../jgit/lib/DefaultTypedConfigGetter.java | 27 +--- .../org/eclipse/jgit/util/StringUtils.java | 134 ++++++++++++++++++ 6 files changed, 226 insertions(+), 38 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java index 82c0afec5..aa7247e10 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -70,4 +71,86 @@ public void testReplaceLineBreaks() { assertEquals("a b c d", StringUtils.replaceLineBreaksWithSpace("a\r\nb\nc d")); } + + @Test + public void testFormatWithSuffix() { + assertEquals("1023", StringUtils.formatWithSuffix(1023)); + assertEquals("1k", StringUtils.formatWithSuffix(1024)); + assertEquals("1025", StringUtils.formatWithSuffix(1025)); + assertEquals("1048575", StringUtils.formatWithSuffix(1024 * 1024 - 1)); + assertEquals("1m", StringUtils.formatWithSuffix(1024 * 1024)); + assertEquals("1048577", StringUtils.formatWithSuffix(1024 * 1024 + 1)); + assertEquals("1073741823", + StringUtils.formatWithSuffix(1024 * 1024 * 1024 - 1)); + assertEquals("1g", StringUtils.formatWithSuffix(1024 * 1024 * 1024)); + assertEquals("1073741825", + StringUtils.formatWithSuffix(1024 * 1024 * 1024 + 1)); + assertEquals("3k", StringUtils.formatWithSuffix(3 * 1024)); + assertEquals("3m", StringUtils.formatWithSuffix(3 * 1024 * 1024)); + assertEquals("2050k", + StringUtils.formatWithSuffix(2 * 1024 * 1024 + 2048)); + assertEquals("3g", + StringUtils.formatWithSuffix(3L * 1024 * 1024 * 1024)); + assertEquals("3000", StringUtils.formatWithSuffix(3000)); + assertEquals("3000000", StringUtils.formatWithSuffix(3_000_000)); + assertEquals("1953125k", StringUtils.formatWithSuffix(2_000_000_000)); + assertEquals("2000000010", StringUtils.formatWithSuffix(2_000_000_010)); + assertEquals("3000000000", + StringUtils.formatWithSuffix(3_000_000_000L)); + } + + @Test + public void testParseWithSuffix() { + assertEquals(1024, StringUtils.parseIntWithSuffix("1k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1 k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1 k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix(" \t1 k \n", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1k", false)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1K", false)); + assertEquals(1024 * 1024, StringUtils.parseIntWithSuffix("1m", false)); + assertEquals(1024 * 1024, StringUtils.parseIntWithSuffix("1M", false)); + assertEquals(-1024 * 1024, + StringUtils.parseIntWithSuffix("-1M", false)); + assertEquals(1_000_000, + StringUtils.parseIntWithSuffix(" 1000000\r\n", false)); + assertEquals(1024 * 1024 * 1024, + StringUtils.parseIntWithSuffix("1g", false)); + assertEquals(1024 * 1024 * 1024, + StringUtils.parseIntWithSuffix("1G", false)); + assertEquals(3L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("3g", false)); + assertEquals(3L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("3G", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseIntWithSuffix("2G", false)); + assertEquals(2L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("2G", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("-1m", true)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("-1000", true)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix(" \t \n", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("k", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("m", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("g", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("1T", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("1t", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("Nonumber", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("0x001f", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("beef", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("8000000000000000000G", + false)); + } } 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 74762a902..ee97c265e 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -793,6 +793,7 @@ 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 +valueExceedsRange=Value ''{0}'' exceeds the range of {1} verifySignatureBad=BAD signature from "{0}" verifySignatureExpired=Expired signature from "{0}" verifySignatureGood=Good signature from "{0}" 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 3d5d0607e..f7ebe4f40 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -821,6 +821,7 @@ public static JGitText get() { /***/ public String URINotSupported; /***/ public String userConfigInvalid; /***/ public String validatingGitModules; + /***/ public String valueExceedsRange; /***/ public String verifySignatureBad; /***/ public String verifySignatureExpired; /***/ public String verifySignatureGood; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index a369026c9..1ce3e312e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -42,6 +42,7 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; /** * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file. @@ -50,9 +51,6 @@ public class Config { private static final String[] EMPTY_STRING_ARRAY = {}; - static final long KiB = 1024; - static final long MiB = 1024 * KiB; - static final long GiB = 1024 * MiB; private static final int MAX_DEPTH = 10; private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter(); @@ -765,18 +763,8 @@ public void setInt(final String section, final String subsection, */ public void setLong(final String section, final String subsection, final String name, final long value) { - final String s; - - if (value >= GiB && (value % GiB) == 0) - s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$ - else if (value >= MiB && (value % MiB) == 0) - s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$ - else if (value >= KiB && (value % KiB) == 0) - s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$ - else - s = String.valueOf(value); - - setString(section, subsection, name, s); + setString(section, subsection, name, + StringUtils.formatWithSuffix(value)); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java index cc0b995f1..9f96bce25 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java @@ -126,30 +126,11 @@ public long getLong(Config config, String section, String subsection, if (str == null) { return defaultValue; } - String n = str.trim(); - if (n.length() == 0) { - return defaultValue; - } - long mul = 1; - switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) { - case 'g': - mul = Config.GiB; - break; - case 'm': - mul = Config.MiB; - break; - case 'k': - mul = Config.KiB; - break; - } - if (mul > 1) { - n = n.substring(0, n.length() - 1).trim(); - } - if (n.length() == 0) { - return defaultValue; - } try { - return mul * Long.parseLong(n); + return StringUtils.parseLongWithSuffix(str, false); + } catch (StringIndexOutOfBoundsException e) { + // Empty + return defaultValue; } catch (NumberFormatException nfe) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidIntegerValue, section, name, str), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java index 61de65cac..b77fb920e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java @@ -13,12 +13,20 @@ import java.text.MessageFormat; import java.util.Collection; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.internal.JGitText; /** * Miscellaneous string comparison utility methods. */ public final class StringUtils { + + private static final long KiB = 1024; + + private static final long MiB = 1024 * KiB; + + private static final long GiB = 1024 * MiB; + private static final char[] LC; static { @@ -307,4 +315,130 @@ public static String replaceLineBreaksWithSpace(String in) { } return new String(buf, 0, o); } + + /** + * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' + * indicating KiB, MiB, and GiB, respectively. The suffix may follow the + * number with optional separation by one or more blanks. + * + * @param value + * {@link String} to parse; with leading and trailing whitespace + * ignored + * @param positiveOnly + * {@code true} to only accept positive numbers, {@code false} to + * allow negative numbers, too + * @return the value parsed + * @throws NumberFormatException + * if the {@value} is not parseable, or beyond the range of + * {@link Long} + * @throws StringIndexOutOfBoundsException + * if the string is empty or contains only whitespace, or + * contains only the letter 'k', 'm', or 'g' + * @since 6.0 + */ + public static long parseLongWithSuffix(@NonNull String value, + boolean positiveOnly) + throws NumberFormatException, StringIndexOutOfBoundsException { + String n = value.strip(); + if (n.isEmpty()) { + throw new StringIndexOutOfBoundsException(); + } + long mul = 1; + switch (n.charAt(n.length() - 1)) { + case 'g': + case 'G': + mul = GiB; + break; + case 'm': + case 'M': + mul = MiB; + break; + case 'k': + case 'K': + mul = KiB; + break; + default: + break; + } + if (mul > 1) { + n = n.substring(0, n.length() - 1).trim(); + } + if (n.isEmpty()) { + throw new StringIndexOutOfBoundsException(); + } + long number; + if (positiveOnly) { + number = Long.parseUnsignedLong(n); + if (number < 0) { + throw new NumberFormatException( + MessageFormat.format(JGitText.get().valueExceedsRange, + value, Long.class.getSimpleName())); + } + } else { + number = Long.parseLong(n); + } + if (mul == 1) { + return number; + } + try { + return Math.multiplyExact(mul, number); + } catch (ArithmeticException e) { + throw new NumberFormatException(e.getLocalizedMessage()); + } + } + + /** + * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' + * indicating KiB, MiB, and GiB, respectively. The suffix may follow the + * number with optional separation by blanks. + * + * @param value + * {@link String} to parse; with leading and trailing whitespace + * ignored + * @param positiveOnly + * {@code true} to only accept positive numbers, {@code false} to + * allow negative numbers, too + * @return the value parsed + * @throws NumberFormatException + * if the {@value} is not parseable or beyond the range of + * {@link Integer} + * @throws StringIndexOutOfBoundsException + * if the string is empty or contains only whitespace, or + * contains only the letter 'k', 'm', or 'g' + * @since 6.0 + */ + public static int parseIntWithSuffix(@NonNull String value, + boolean positiveOnly) + throws NumberFormatException, StringIndexOutOfBoundsException { + try { + return Math.toIntExact(parseLongWithSuffix(value, positiveOnly)); + } catch (ArithmeticException e) { + throw new NumberFormatException( + MessageFormat.format(JGitText.get().valueExceedsRange, + value, Integer.class.getSimpleName())); + } + } + + /** + * Formats an integral value as a decimal number with 'k', 'm', or 'g' + * suffix if it is an exact multiple of 1024, otherwise returns the value + * representation as a decimal number without suffix. + * + * @param value + * Value to format + * @return the value's String representation + * @since 6.0 + */ + public static String formatWithSuffix(long value) { + if (value >= GiB && (value % GiB) == 0) { + return String.valueOf(value / GiB) + 'g'; + } + if (value >= MiB && (value % MiB) == 0) { + return String.valueOf(value / MiB) + 'm'; + } + if (value >= KiB && (value % KiB) == 0) { + return String.valueOf(value / KiB) + 'k'; + } + return String.valueOf(value); + } } From 83eddaf7fda22ca64e9c3852df67ccef0dacdaf5 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 31 Oct 2021 01:35:52 +0200 Subject: [PATCH 03/24] Binary and CR-LF detection: lone CRs -> binary C git considers not only files containing NUL bytes as binary but also files containing lone CRs. Implement this also for JGit. C git additionally counts printable vs. non-printable characters and considers files that have non_printable_count > printable_count / 128 also as binary. This is not implemented because such counting probably only makes sense if one looks at the full file or blob content. The Auto[CR]LF* streams in JGit look only at the first few KiB of a stream in order not to buffer too much. For the C git implementation, see [1]. [1] https://github.com/git/git/blob/7e27bd589d/convert.c#L35 Bug: 576971 Change-Id: Ia169b59bdbf1477f32ee2014eeb8406f81d4b1ab Signed-off-by: Thomas Wolf --- .../jgit/pgm/debug/DiffAlgorithms.java | 4 +- .../jgit/pgm/debug/TextHashFunctions.java | 2 +- .../jgit/api/EolStreamTypeUtilTest.java | 87 +++++++------ .../jgit/util/io/AutoCRLFInputStreamTest.java | 4 +- .../util/io/AutoCRLFOutputStreamTest.java | 2 +- .../src/org/eclipse/jgit/diff/RawText.java | 120 +++++++++++++++--- .../eclipse/jgit/diff/SimilarityIndex.java | 5 +- .../jgit/treewalk/WorkingTreeIterator.java | 3 +- .../org/eclipse/jgit/util/RawParseUtils.java | 29 ++--- .../jgit/util/io/AutoCRLFInputStream.java | 2 +- .../jgit/util/io/AutoCRLFOutputStream.java | 19 +-- .../jgit/util/io/AutoLFInputStream.java | 4 +- .../jgit/util/io/AutoLFOutputStream.java | 10 +- 13 files changed, 191 insertions(+), 100 deletions(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java index cd5d8f1bf..a63387c24 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java @@ -173,7 +173,7 @@ private void run(Repository repo) throws Exception { } catch (LargeObjectException tooBig) { continue; } - if (RawText.isBinary(raw0)) + if (RawText.isBinary(raw0, raw0.length, true)) continue; byte[] raw1; @@ -183,7 +183,7 @@ private void run(Repository repo) throws Exception { } catch (LargeObjectException tooBig) { continue; } - if (RawText.isBinary(raw1)) + if (RawText.isBinary(raw1, raw1.length, true)) continue; RawText txt0 = new RawText(raw0); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java index f777f277f..1ca3034f4 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java @@ -286,7 +286,7 @@ private void run(Repository repo) throws Exception { continue; } - if (RawText.isBinary(raw)) + if (RawText.isBinary(raw, raw.length, true)) continue; RawText txt = new RawText(raw); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java index 673aa1e9c..f8a663291 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/EolStreamTypeUtilTest.java @@ -81,7 +81,8 @@ public void testCheckoutCRLF() throws Exception { testCheckout(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n"); testCheckout(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n"); - testCheckout(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r"); + testCheckout(TEXT_CRLF, null, "\n\r", "\r\n\r"); + testCheckout(null, AUTO_CRLF, "\n\r", "\n\r"); // Lone CR testCheckout(null, AUTO_CRLF, "\n\r\n", "\n\r\n"); testCheckout(TEXT_CRLF, null, "\n\r\n", "\r\n\r\n"); @@ -89,7 +90,8 @@ public void testCheckoutCRLF() throws Exception { testCheckout(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n"); testCheckout(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r"); - testCheckout(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckout(TEXT_CRLF, null, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckout(null, AUTO_CRLF, "a\n\rb\n\r", "a\n\rb\n\r"); // Lone CR testCheckout(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n"); } @@ -199,7 +201,8 @@ public void testCheckinLF() throws Exception { testCheckin(TEXT_LF, AUTO_LF, "\n\r", "\n\r"); testCheckin(TEXT_LF, AUTO_LF, "\n\r\n", "\n\n"); - testCheckin(TEXT_LF, AUTO_LF, "\r\n\r", "\n\r"); + testCheckin(TEXT_LF, null, "\r\n\r", "\n\r"); + testCheckin(null, AUTO_LF, "\r\n\r", "\r\n\r"); // Lone CR testCheckin(TEXT_LF, AUTO_LF, "a\nb\n", "a\nb\n"); testCheckin(TEXT_LF, AUTO_LF, "a\rb\r", "a\rb\r"); @@ -214,14 +217,16 @@ public void testCheckinCRLF() throws Exception { testCheckin(TEXT_CRLF, AUTO_CRLF, "\n", "\r\n"); testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n", "\r\n"); - testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r", "\r\n\r"); + testCheckin(TEXT_CRLF, null, "\n\r", "\r\n\r"); + testCheckin(null, AUTO_CRLF, "\n\r", "\n\r"); // Lone CR testCheckin(TEXT_CRLF, AUTO_CRLF, "\n\r\n", "\r\n\r\n"); testCheckin(TEXT_CRLF, AUTO_CRLF, "\r\n\r", "\r\n\r"); testCheckin(TEXT_CRLF, AUTO_CRLF, "a\nb\n", "a\r\nb\r\n"); testCheckin(TEXT_CRLF, AUTO_CRLF, "a\rb\r", "a\rb\r"); - testCheckin(TEXT_CRLF, AUTO_CRLF, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckin(TEXT_CRLF, null, "a\n\rb\n\r", "a\r\n\rb\r\n\r"); + testCheckin(null, AUTO_CRLF, "a\n\rb\n\r", "a\n\rb\n\r"); // Lone CR testCheckin(TEXT_CRLF, AUTO_CRLF, "a\r\nb\r\n", "a\r\nb\r\n"); } @@ -257,47 +262,55 @@ private void testCheckin(EolStreamType streamTypeText, byte[] inputBytes = input.getBytes(UTF_8); byte[] expectedConversionBytes = expectedConversion.getBytes(UTF_8); - // test using input text and assuming it was declared TEXT - try (InputStream in = EolStreamTypeUtil.wrapInputStream( - new ByteArrayInputStream(inputBytes), - streamTypeText)) { - byte[] b = new byte[1024]; - int len = IO.readFully(in, b, 0); - assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + if (streamTypeText != null) { + // test using input text and assuming it was declared TEXT + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), streamTypeText)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, + Arrays.copyOf(b, len)); + } } - // test using input text and assuming it was declared AUTO, using binary - // detection - try (InputStream in = EolStreamTypeUtil.wrapInputStream( - new ByteArrayInputStream(inputBytes), - streamTypeWithBinaryCheck)) { - byte[] b = new byte[1024]; - int len = IO.readFully(in, b, 0); - assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + if (streamTypeWithBinaryCheck != null) { + // test using input text and assuming it was declared AUTO, using + // binary detection + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), + streamTypeWithBinaryCheck)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, + Arrays.copyOf(b, len)); + } } - // now pollute input text with some binary bytes inputBytes = extendWithBinaryData(inputBytes); expectedConversionBytes = extendWithBinaryData(expectedConversionBytes); - // again, test using input text and assuming it was declared TEXT - try (InputStream in = EolStreamTypeUtil.wrapInputStream( - new ByteArrayInputStream(inputBytes), streamTypeText)) { - byte[] b = new byte[1024]; - int len = IO.readFully(in, b, 0); - assertArrayEquals(expectedConversionBytes, Arrays.copyOf(b, len)); + if (streamTypeText != null) { + // again, test using input text and assuming it was declared TEXT + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), streamTypeText)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + assertArrayEquals(expectedConversionBytes, + Arrays.copyOf(b, len)); + } } - // again, test using input text and assuming it was declared AUTO, using - // binary - // detection - try (InputStream in = EolStreamTypeUtil.wrapInputStream( - new ByteArrayInputStream(inputBytes), - streamTypeWithBinaryCheck)) { - byte[] b = new byte[1024]; - int len = IO.readFully(in, b, 0); - // expect no conversion - assertArrayEquals(inputBytes, Arrays.copyOf(b, len)); + if (streamTypeWithBinaryCheck != null) { + // again, test using input text and assuming it was declared AUTO, + // using binary detection + try (InputStream in = EolStreamTypeUtil.wrapInputStream( + new ByteArrayInputStream(inputBytes), + streamTypeWithBinaryCheck)) { + byte[] b = new byte[1024]; + int len = IO.readFully(in, b, 0); + // expect no conversion + assertArrayEquals(inputBytes, Arrays.copyOf(b, len)); + } } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java index cd4e50339..94429924b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFInputStreamTest.java @@ -30,10 +30,10 @@ public void test() throws IOException { assertNoCrLf("\r\n", "\n"); assertNoCrLf("\r\n", "\r\n"); assertNoCrLf("\r\r", "\r\r"); - assertNoCrLf("\r\n\r", "\n\r"); + assertNoCrLf("\n\r", "\n\r"); // Lone CR assertNoCrLf("\r\n\r\r", "\r\n\r\r"); assertNoCrLf("\r\n\r\n", "\r\n\r\n"); - assertNoCrLf("\r\n\r\n\r", "\n\r\n\r"); + assertNoCrLf("\n\r\n\r", "\n\r\n\r"); // Lone CR assertNoCrLf("\0\n", "\0\n"); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java index 150df0845..791727f73 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/AutoCRLFOutputStreamTest.java @@ -32,7 +32,7 @@ public void test() throws IOException { assertNoCrLf("\r\n", "\n"); assertNoCrLf("\r\n", "\r\n"); assertNoCrLf("\r\r", "\r\r"); - assertNoCrLf("\r\n\r", "\n\r"); + assertNoCrLf("\n\r", "\n\r"); // Lone CR assertNoCrLf("\r\n\r\r", "\r\n\r\r"); assertNoCrLf("\r\n\r\n", "\r\n\r\n"); assertNoCrLf("\n\r\n\r", "\n\r\n\r"); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java index 914fa5f6f..aeb3c4563 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java @@ -245,18 +245,6 @@ private int getEnd(int i) { return lines.get(i + 2); } - /** - * Determine heuristically whether a byte array represents binary (as - * opposed to text) content. - * - * @param raw - * the raw file content. - * @return true if raw is likely to be a binary file, false otherwise - */ - public static boolean isBinary(byte[] raw) { - return isBinary(raw, raw.length); - } - /** * Obtains the buffer size to use for analyzing whether certain content is * text or binary, or what line endings are used if it's text. @@ -308,7 +296,19 @@ public static boolean isBinary(InputStream raw) throws IOException { break; cnt += n; } - return isBinary(buffer, cnt); + return isBinary(buffer, cnt, cnt < buffer.length); + } + + /** + * Determine heuristically whether a byte array represents binary (as + * opposed to text) content. + * + * @param raw + * the raw file content. + * @return true if raw is likely to be a binary file, false otherwise + */ + public static boolean isBinary(byte[] raw) { + return isBinary(raw, raw.length); } /** @@ -324,19 +324,63 @@ public static boolean isBinary(InputStream raw) throws IOException { * @return true if raw is likely to be a binary file, false otherwise */ public static boolean isBinary(byte[] raw, int length) { - // Same heuristic as C Git (except for the buffer size) + return isBinary(raw, length, false); + } + + /** + * Determine heuristically whether a byte array represents binary (as + * opposed to text) content. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. This should be + * {@code raw.length} unless {@code raw} was over-allocated by + * the caller. + * @param complete + * whether {@code raw} contains the whole data + * @return true if raw is likely to be a binary file, false otherwise + * @since 6.0 + */ + public static boolean isBinary(byte[] raw, int length, boolean complete) { + // Similar heuristic as C Git. Differences: + // - limited buffer size; may be only the beginning of a large blob + // - no counting of printable vs. non-printable bytes < 0x20 and 0x7F int maxLength = getBufferSize(); if (length > maxLength) { length = maxLength; } + byte last = 'x'; // Just something inconspicuous. for (int ptr = 0; ptr < length; ptr++) { - if (raw[ptr] == '\0') { + byte curr = raw[ptr]; + if (isBinary(curr, last)) { return true; } + last = curr; + } + if (complete) { + // Buffer contains everything... + return last == '\r'; // ... so this must be a lone CR } return false; } + /** + * Determines from the last two bytes read from a source if it looks like + * binary content. + * + * @param curr + * the last byte, read after {@code prev} + * @param prev + * the previous byte, read before {@code last} + * @return {@code true} if either byte is NUL, or if prev is CR and curr is + * not LF, {@code false} otherwise + * @since 6.0 + */ + public static boolean isBinary(byte curr, byte prev) { + return curr == '\0' || curr != '\n' && prev == '\r' || prev == '\0'; + } + /** * Determine heuristically whether a byte array represents text content * using CR-LF as line separator. @@ -394,13 +438,44 @@ public static boolean isCrLfText(InputStream raw) throws IOException { * @since 5.3 */ public static boolean isCrLfText(byte[] raw, int length) { + return isCrLfText(raw, length, false); + } + + /** + * Determine heuristically whether a byte array represents text content + * using CR-LF as line separator. + * + * @param raw + * the raw file content. + * @param length + * number of bytes in {@code raw} to evaluate. + * @return {@code true} if raw is likely to be CR-LF delimited text, + * {@code false} otherwise + * @param complete + * whether {@code raw} contains the whole data + * @since 6.0 + */ + public static boolean isCrLfText(byte[] raw, int length, boolean complete) { boolean has_crlf = false; - for (int ptr = 0; ptr < length - 1; ptr++) { - if (raw[ptr] == '\0') { - return false; // binary - } else if (raw[ptr] == '\r' && raw[ptr + 1] == '\n') { + byte last = 'x'; // Just something inconspicuous + for (int ptr = 0; ptr < length; ptr++) { + byte curr = raw[ptr]; + if (isBinary(curr, last)) { + return false; + } + if (curr == '\n' && last == '\r') { has_crlf = true; } + last = curr; + } + if (last == '\r') { + if (complete) { + // Lone CR: it's binary after all. + return false; + } + // Tough call. If the next byte, which we don't have, would be a + // '\n', it'd be a CR-LF text, otherwise it'd be binary. Just decide + // based on what we already scanned; it wasn't binary until now. } return has_crlf; } @@ -452,7 +527,7 @@ public static RawText load(ObjectLoader ldr, int threshold) int bufferSize = getBufferSize(); if (sz <= bufferSize) { byte[] data = ldr.getCachedBytes(bufferSize); - if (isBinary(data)) { + if (isBinary(data, data.length, true)) { throw new BinaryBlobException(); } return new RawText(data); @@ -462,6 +537,7 @@ public static RawText load(ObjectLoader ldr, int threshold) try (InputStream stream = ldr.openStream()) { int off = 0; int left = head.length; + byte last = 'x'; // Just something inconspicuous while (left > 0) { int n = stream.read(head, off, left); if (n < 0) { @@ -470,9 +546,11 @@ public static RawText load(ObjectLoader ldr, int threshold) left -= n; while (n > 0) { - if (head[off] == '\0') { + byte curr = head[off]; + if (isBinary(curr, last)) { throw new BinaryBlobException(); } + last = curr; off++; n--; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java index 661369b86..34581aefb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java @@ -108,7 +108,8 @@ static boolean isBinary(ObjectLoader obj) throws IOException { return RawText.isBinary(in1); } } - return RawText.isBinary(obj.getCachedBytes()); + byte[] raw = obj.getCachedBytes(); + return RawText.isBinary(raw, raw.length, true); } void hash(ObjectLoader obj) throws MissingObjectException, IOException, @@ -132,7 +133,7 @@ private void hashLargeObject(ObjectLoader obj) throws IOException, } void hash(byte[] raw, int ptr, int end) throws TableFullException { - final boolean text = !RawText.isBinary(raw); + final boolean text = !RawText.isBinary(raw, raw.length, true); hashedCnt = 0; while (ptr < end) { int hash = 5381; 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 0b7c0a9e4..50ce15ebc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -1513,7 +1513,8 @@ private boolean hasCrLfInIndex(DirCacheIterator dirCache) { ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB); try { - return RawText.isCrLfText(loader.getCachedBytes()); + byte[] raw = loader.getCachedBytes(); + return RawText.isCrLfText(raw, raw.length, true); } catch (LargeObjectException e) { try (InputStream in = loader.openStream()) { return RawText.isCrLfText(in); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index 93bf84719..0e8e9b3d8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -30,6 +30,7 @@ import java.util.Map; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.PersonIdent; @@ -679,36 +680,30 @@ public static final IntList lineMap(byte[] buf, int ptr, int end) { * 1 past the end of the content within buf. * @return a line map indicating the starting position of each line. * @throws BinaryBlobException - * if a NUL byte is found. + * if a NUL byte or a lone CR is found. * @since 5.0 */ public static final IntList lineMapOrBinary(byte[] buf, int ptr, int end) throws BinaryBlobException { - IntList map = lineMapOrNull(buf, ptr, end); - if (map == null) { - throw new BinaryBlobException(); - } - return map; - } - - @Nullable - private static IntList lineMapOrNull(byte[] buf, int ptr, int end) { // Experimentally derived from multiple source repositories // the average number of bytes/line is 36. Its a rough guess // to initially size our map close to the target. IntList map = new IntList((end - ptr) / 36); map.add(Integer.MIN_VALUE); - boolean foundLF = true; + byte last = '\n'; // Must be \n to add the initial ptr for (; ptr < end; ptr++) { - if (foundLF) { + if (last == '\n') { map.add(ptr); } - - if (buf[ptr] == '\0') { - return null; + byte curr = buf[ptr]; + if (RawText.isBinary(curr, last)) { + throw new BinaryBlobException(); } - - foundLF = (buf[ptr] == '\n'); + last = curr; + } + if (last == '\r') { + // Counts as binary + throw new BinaryBlobException(); } map.add(end); return map; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java index 1b03d097b..cedb15982 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java @@ -123,7 +123,7 @@ private boolean fillBuffer() throws IOException { return false; } if (detectBinary) { - isBinary = RawText.isBinary(buf, cnt); + isBinary = RawText.isBinary(buf, cnt, cnt < buf.length); detectBinary = false; } ptr = 0; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java index 05e271feb..e638b2de3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java @@ -122,22 +122,24 @@ public void write(byte[] b, int startOff, int startLen) } private int buffer(byte[] b, int off, int len) throws IOException { - if (binbufcnt > binbuf.length) + if (binbufcnt > binbuf.length) { return len; + } int copy = Math.min(binbuf.length - binbufcnt, len); System.arraycopy(b, off, binbuf, binbufcnt, copy); binbufcnt += copy; int remaining = len - copy; - if (remaining > 0) - decideMode(); + if (remaining > 0) { + decideMode(false); + } return remaining; } - private void decideMode() throws IOException { + private void decideMode(boolean complete) throws IOException { if (detectBinary) { - isBinary = RawText.isBinary(binbuf, binbufcnt); + isBinary = RawText.isBinary(binbuf, binbufcnt, complete); if (!isBinary) { - isBinary = RawText.isCrLfText(binbuf, binbufcnt); + isBinary = RawText.isCrLfText(binbuf, binbufcnt, complete); } detectBinary = false; } @@ -149,8 +151,9 @@ private void decideMode() throws IOException { /** {@inheritDoc} */ @Override public void flush() throws IOException { - if (binbufcnt <= binbuf.length) - decideMode(); + if (binbufcnt <= binbuf.length) { + decideMode(true); + } buf = -1; out.flush(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java index b6d1848b3..7db882c07 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java @@ -262,14 +262,14 @@ private boolean fillBuffer() throws IOException { return false; } if (detectBinary) { - isBinary = RawText.isBinary(buf, cnt); + isBinary = RawText.isBinary(buf, cnt, cnt < buf.length); passAsIs = isBinary; detectBinary = false; if (isBinary && abortIfBinary) { throw new IsBinaryException(); } if (!passAsIs && forCheckout) { - passAsIs = RawText.isCrLfText(buf, cnt); + passAsIs = RawText.isCrLfText(buf, cnt, cnt < buf.length); } } ptr = 0; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java index e08a53f50..a0e9fb68c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java @@ -146,16 +146,16 @@ private int buffer(byte[] b, int off, int len) throws IOException { binbufcnt += copy; int remaining = len - copy; if (remaining > 0) { - decideMode(); + decideMode(false); } return remaining; } - private void decideMode() throws IOException { + private void decideMode(boolean complete) throws IOException { if (detectBinary) { - isBinary = RawText.isBinary(binbuf, binbufcnt); + isBinary = RawText.isBinary(binbuf, binbufcnt, complete); if (!isBinary) { - isBinary = RawText.isCrLfText(binbuf, binbufcnt); + isBinary = RawText.isCrLfText(binbuf, binbufcnt, complete); } detectBinary = false; } @@ -168,7 +168,7 @@ private void decideMode() throws IOException { @Override public void flush() throws IOException { if (binbufcnt <= binbuf.length) { - decideMode(); + decideMode(true); } out.flush(); } From f6ef2f620110d1e62b69f3cfca017bac2784e6fb Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 31 Oct 2021 14:57:30 +0100 Subject: [PATCH 04/24] [doc] Add README and package-info to the SSH bundles Explain in the JSch bundle that it is essentially unmaintained. Add descriptions in both bundles explaining how to use it, or how to use an alternate implementation. Change-Id: Idaf46c33b14543279f78a55cb7c6bd42b06ee6b8 Signed-off-by: Thomas Wolf --- org.eclipse.jgit.ssh.apache/README.md | 61 +++++++++++++++++++ .../jgit/transport/sshd/package-info.java | 6 ++ org.eclipse.jgit.ssh.jsch/README.md | 59 ++++++++++++++++++ .../jgit/transport/ssh/jsch/package-info.java | 14 +++++ 4 files changed, 140 insertions(+) create mode 100644 org.eclipse.jgit.ssh.apache/README.md create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java create mode 100644 org.eclipse.jgit.ssh.jsch/README.md create mode 100644 org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/ssh/jsch/package-info.java diff --git a/org.eclipse.jgit.ssh.apache/README.md b/org.eclipse.jgit.ssh.apache/README.md new file mode 100644 index 000000000..3bbda523b --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/README.md @@ -0,0 +1,61 @@ +# JGit SSH support via Apache MINA sshd + +This bundle provides an implementation of git transport over SSH implemented via +[Apache MINA sshd](https://mina.apache.org/sshd-project/). + +## Service registration + +This bundle declares a service for the `java.util.ServiceLoader` for interface +`org.eclipse.jgit.transport.ssh.SshSessionFactory`. The core JGit bundle uses the service +loader to pick up an implementation of that interface. + +Note that JGit simply uses the first `SshSessionFactory` provided by the `ServiceLoader`. + +If the service loader cannot find the session factory, either ensure that the service +declaration is on the Classpath of bundle `org.eclipse.jgit`, or set the factory explicitly +(see below). + +In an OSGi environment, one might need a service loader bridge, or have a little OSGi +fragment for bundle `org.eclipse.jgit` that puts the right service declaration onto the +Classpath of that bundle. (OSGi fragments become part of the Classpath of their host +bundle.) + +## Configuring an SSH implementation for JGit + +The simplest way to set an SSH implementation for JGit is to install it globally via +`SshSessionFactory.setInstance()`. This instance will be used by JGit for all SSH +connections by default. + +It is also possible to set the SSH implementation individually for any git command +that needs a transport (`TransportCommand`) via a `org.eclipse.jgit.api.TransportConfigCallback`. + +To do so, set the wanted `SshSessionFactory` on the SSH transport, like: + +```java +SshSessionFactory customFactory = ...; // Get it from wherever +FetchCommand fetch = git.fetch() + .setTransportConfigCallback(transport -> { + if (transport instanceof SshTransport) { + ((SshTransport) transport).setSshSessionFactory(customFactory); + } + }) + ... + .call(); +``` + +## Using a different SSH implementation + +To use a different SSH implementation: + +* Do not include this bundle in your product. +* Include the bundle of the alternate implementation. + * If the service loader finds the alternate implementation, nothing more is needed. + * Otherwise ensure the service declaration from the other bundle is on the Classpath of bundle `org.eclipse.jgit`, + * or set the `SshSessionFactory` for JGit explicitly (see above). + +## Using an external SSH executable + +JGit has built-in support for not using any Java SSH implementation but an external SSH +executable. To use an external SSH executable, set environment variable **GIT_SSH** to +the path of the executable. JGit will create a sub-process to run the executable and +communicate with this sup-process to perform the git operation. diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java new file mode 100644 index 000000000..926234a3b --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides a JGit {@link org.eclipse.jgit.transport.SshSessionFactory} + * implemented via Apache MINA + * sshd. + */ +package org.eclipse.jgit.transport.sshd; diff --git a/org.eclipse.jgit.ssh.jsch/README.md b/org.eclipse.jgit.ssh.jsch/README.md new file mode 100644 index 000000000..48f43f0f8 --- /dev/null +++ b/org.eclipse.jgit.ssh.jsch/README.md @@ -0,0 +1,59 @@ +# JGit SSH support via JSch + +This bundle provides an implementation of git transport over SSH implemented via JSch. + +**This bundle should be considered deprecated**. It is essentially unmaintained, and +the JGit project may decide anytime to remove it completely without further ado. + +The officially supported SSH transport is in bundle `org.eclipse.jgit.ssh.apache` and is +built upon [Apache MINA sshd](https://mina.apache.org/sshd-project/). + +## Service registration + +This bundle declares a service for the `java.util.ServiceLoader` for interface +`org.eclipse.jgit.transport.ssh.SshSessionFactory`. The core JGit bundle uses the service +loader to pick up an implementation of that interface. The bundle in an OSGi fragment +to ensure that the service loader works in an OSGi environment without the need to +install a service loader bridge. + +Note that JGit simply uses the first `SshSessionFactory` provided by the `ServiceLoader`. + +## Using a different SSH implementation + +To use a different SSH implementation: + +* Do not include this bundle in your product. +* Include the bundle of the alternate implementation. + * If the service loader finds the alternate implementation, nothing more is needed. + * Otherwise ensure the service declaration from the other bundle is on the Classpath of bundle `org.eclipse.jgit`, + * or set the `SshSessionFactory` for JGit explicitly (see below). + +## Configuring an SSH implementation for JGit + +The simplest way to set an SSH implementation for JGit is to install it globally via +`SshSessionFactory.setInstance()`. This instance will be used by JGit for all SSH +connections by default. + +It is also possible to set the SSH implementation individually for any git command +that needs a transport (`TransportCommand`) via a `org.eclipse.jgit.api.TransportConfigCallback`. + +To do so, set the wanted `SshSessionFactory` on the SSH transport, like: + +```java +SshSessionFactory customFactory = ...; // Get it from wherever +FetchCommand fetch = git.fetch() + .setTransportConfigCallback(transport -> { + if (transport instanceof SshTransport) { + ((SshTransport) transport).setSshSessionFactory(customFactory); + } + }) + ... + .call(); +``` + +## Using an external SSH executable + +JGit has built-in support for not using any Java SSH implementation but an external SSH +executable. To use an external SSH executable, set environment variable **GIT_SSH** to +the path of the executable. JGit will create a sub-process to run the executable and +communicate with this sup-process to perform the git operation. diff --git a/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/ssh/jsch/package-info.java b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/ssh/jsch/package-info.java new file mode 100644 index 000000000..dc2915a09 --- /dev/null +++ b/org.eclipse.jgit.ssh.jsch/src/org/eclipse/jgit/transport/ssh/jsch/package-info.java @@ -0,0 +1,14 @@ +/** + * Provides a JGit {@link org.eclipse.jgit.transport.SshSessionFactory} + * implemented via JSch. + *

+ * This package should be considered deprecated. It is essentially + * unmaintained and the JGit project may decide to remove it completely without + * further ado at any time. + *

+ *

+ * The officially supported Java SSH implementation for JGit is in bundle + * {@code org.eclipse.jgit.ssh.apache} and is built upon + * Apache MINA sshd. + */ +package org.eclipse.jgit.transport.ssh.jsch; From 4f8d4346234ce28d592ae92bbfc02d4d203454b1 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 3 Nov 2021 22:40:48 +0100 Subject: [PATCH 05/24] Fix target platforms - jetty 9.4.30 moved to archive.eclipse.org - use the final release p2 repo instead of staging for 2020-09 Change-Id: Ia096200e5983f0022c6c0da4dae035433e852807 --- .../org.eclipse.jgit.target/jgit-4.10.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.11.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.12.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.13.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.14.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.15.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.16.target | 4 ++-- .../{jgit-4.17-staging.target => jgit-4.17.target} | 6 +++--- .../{jgit-4.17-staging.tpd => jgit-4.17.tpd} | 2 +- .../org.eclipse.jgit.target/jgit-4.6.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.7.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.8.target | 4 ++-- .../org.eclipse.jgit.target/jgit-4.9.target | 4 ++-- .../org.eclipse.jgit.target/projects/jetty-9.4.x.tpd | 2 +- 14 files changed, 27 insertions(+), 27 deletions(-) rename org.eclipse.jgit.packaging/org.eclipse.jgit.target/{jgit-4.17-staging.target => jgit-4.17.target} (95%) rename org.eclipse.jgit.packaging/org.eclipse.jgit.target/{jgit-4.17-staging.tpd => jgit-4.17.tpd} (72%) 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 ad0816c04..54aeb7a82 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 75f893f01..40a40184c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 2afbcb63a..f6cdc1e66 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 34c497a37..6f8c2e8e8 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 33bfc43e8..8c91bcd05 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 03b5cb78f..0583cda7b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 a7470c111..c087e76a8 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target similarity index 95% rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.target rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target index 79e021178..aa23a2662 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -88,7 +88,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd similarity index 72% rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.tpd rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd index 07745cb15..e7043184c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17-staging.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd @@ -3,6 +3,6 @@ target "jgit-4.17-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" include "orbit/R20200831200620-2020-09.tpd" -location "https://download.eclipse.org/staging/2020-09/" { +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.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index 566ef3389..41070856a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 5a690e89e..93a3090d1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 fe237d01c..ff3abcdd0 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 a812e7a57..c922b9fda 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,7 +1,7 @@ - + @@ -20,7 +20,7 @@ - + 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 70c426c18..f76d50030 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,6 +1,6 @@ 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.30.v20200611/" { +location jetty-9.4.30 "https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/jetty-bundles-9.x/9.4.30.v20200611/" { org.eclipse.jetty.client [9.4.30.v20200611,9.4.30.v20200611] org.eclipse.jetty.client.source [9.4.30.v20200611,9.4.30.v20200611] org.eclipse.jetty.continuation [9.4.30.v20200611,9.4.30.v20200611] From 4184ff0953b2569799221d423e77fd2f6880f77d Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 2 Nov 2021 18:47:26 +0100 Subject: [PATCH 06/24] [releng] Make the bazel build use Java 11 Make the default toolchain use Java 11, and fix two errorprone findings introduced recently. Change-Id: Iff51206fe8bdf096cb7d88cb1a499002550766cd Signed-off-by: Thomas Wolf --- .../src/org/eclipse/jgit/diff/RawText.java | 2 +- .../org/eclipse/jgit/util/StringUtils.java | 9 +++++++-- tools/BUILD | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java index aeb3c4563..19961a13e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java @@ -378,7 +378,7 @@ public static boolean isBinary(byte[] raw, int length, boolean complete) { * @since 6.0 */ public static boolean isBinary(byte curr, byte prev) { - return curr == '\0' || curr != '\n' && prev == '\r' || prev == '\0'; + return curr == '\0' || (curr != '\n' && prev == '\r') || prev == '\0'; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java index b77fb920e..8ab13385e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java @@ -383,7 +383,10 @@ public static long parseLongWithSuffix(@NonNull String value, try { return Math.multiplyExact(mul, number); } catch (ArithmeticException e) { - throw new NumberFormatException(e.getLocalizedMessage()); + NumberFormatException nfe = new NumberFormatException( + e.getLocalizedMessage()); + nfe.initCause(e); + throw nfe; } } @@ -413,9 +416,11 @@ public static int parseIntWithSuffix(@NonNull String value, try { return Math.toIntExact(parseLongWithSuffix(value, positiveOnly)); } catch (ArithmeticException e) { - throw new NumberFormatException( + NumberFormatException nfe = new NumberFormatException( MessageFormat.format(JGitText.get().valueExceedsRange, value, Integer.class.getSimpleName())); + nfe.initCause(e); + throw nfe; } } diff --git a/tools/BUILD b/tools/BUILD index 2b208744b..7bfab2d06 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -5,10 +5,27 @@ load( ) load("@rules_java//java:defs.bzl", "java_package_configuration") +JDK11_JVM_OPTS = [ + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--patch-module=java.compiler=$(location @bazel_tools//tools/jdk:java_compiler_jar)", + "--patch-module=jdk.compiler=$(location @bazel_tools//tools/jdk:jdk_compiler_jar)", + "--add-opens=java.base/java.nio=ALL-UNNAMED", + "--add-opens=java.base/java.lang=ALL-UNNAMED", +] + default_java_toolchain( name = "error_prone_warnings_toolchain", bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"], - jvm_opts = JDK9_JVM_OPTS, + jvm_opts = JDK11_JVM_OPTS, + source_version = "11", + target_version = "11", package_configuration = [ ":error_prone", ], From c04884fc9166fe491745fa51bd7540ff36ce6e7c Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 2 Nov 2021 19:20:59 +0100 Subject: [PATCH 07/24] [releng] bazel: Enable errorprone on o.e.j.ssh.apache Fix the few issues reported. (None serious.) Change-Id: I8d72ef7d425ab61f4c27b657c92fc021850730d6 Signed-off-by: Thomas Wolf --- .../transport/sshd/JGitSshClient.java | 2 +- .../sshd/auth/BasicAuthentication.java | 4 ++-- .../proxy/AbstractClientProxyConnector.java | 2 +- .../sshd/proxy/HttpClientConnector.java | 7 ++++--- .../transport/sshd/proxy/HttpParser.java | 19 ++++++++++++++++++- .../sshd/proxy/Socks5ClientConnector.java | 2 +- tools/BUILD | 1 + 7 files changed, 28 insertions(+), 9 deletions(-) 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 32819aea2..fdb8cde67 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 @@ -439,7 +439,7 @@ public boolean hasNext() { @Override public KeyPair next() { - if (hasElement == null && !hasNext() + if ((hasElement == null && !hasNext()) || !hasElement.booleanValue()) { throw new NoSuchElementException(); } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java index eae0d7535..e5f884e29 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/auth/BasicAuthentication.java @@ -95,8 +95,8 @@ public final void close() { @Override public final void start() throws Exception { - if (user != null && !user.isEmpty() - || password != null && password.length > 0) { + if ((user != null && !user.isEmpty()) + || (password != null && password.length > 0)) { return; } askCredentials(); diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java index 54e2cbceb..ae2b2b6ac 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/AbstractClientProxyConnector.java @@ -31,7 +31,7 @@ public abstract class AbstractClientProxyConnector .toMillis(30L); /** Guards {@link #done} and {@link #bufferedCommands}. */ - private Object lock = new Object(); + private final Object lock = new Object(); private boolean done; 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 e5d1e80f7..b7deb29dc 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 @@ -113,8 +113,8 @@ public void sendClientProxyMetadata(ClientSession sshSession) IoSession session = sshSession.getIoSession(); session.addCloseFutureListener(f -> close()); StringBuilder msg = connect(); - if (proxyUser != null && !proxyUser.isEmpty() - || proxyPassword != null && proxyPassword.length > 0) { + if ((proxyUser != null && !proxyUser.isEmpty()) + || (proxyPassword != null && proxyPassword.length > 0)) { authenticator = basic; basic.setParams(null); basic.start(); @@ -232,7 +232,8 @@ private void handleMessage(IoSession session, List reply) } catch (HttpParser.ParseException e) { throw new IOException( format(SshdText.get().proxyHttpUnexpectedReply, - proxyAddress, reply.get(0))); + proxyAddress, reply.get(0)), + e); } } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java index 0500a6342..ece22af1c 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/proxy/HttpParser.java @@ -31,6 +31,23 @@ public static class ParseException extends Exception { private static final long serialVersionUID = -1634090143702048640L; + /** + * Creates a new {@link ParseException} without cause. + */ + public ParseException() { + super(); + } + + /** + * Creates a new {@link ParseException} with the given {@code cause}. + * + * @param cause + * {@link Throwable} that caused this exception, or + * {@code null} if none + */ + public ParseException(Throwable cause) { + super(cause); + } } private HttpParser() { @@ -64,7 +81,7 @@ public static StatusLine parseStatusLine(String line) resultCode = Integer.parseUnsignedInt( line.substring(firstBlank + 1, secondBlank)); } catch (NumberFormatException e) { - throw new ParseException(); + throw new ParseException(e); } // Again, accept even if the reason is missing String reason = ""; //$NON-NLS-1$ 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 8844efa6b..bb227bbac 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 @@ -94,7 +94,7 @@ private enum SocksAuthenticationMethod { // JSON(9), NONE_ACCEPTABLE(0xFF); - private byte value; + private final byte value; SocksAuthenticationMethod(int value) { this.value = (byte) value; diff --git a/tools/BUILD b/tools/BUILD index 7bfab2d06..4769f4222 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -120,6 +120,7 @@ package_group( "//org.eclipse.jgit.packaging/...", "//org.eclipse.jgit.pgm.test/...", "//org.eclipse.jgit.pgm/...", + "//org.eclipse.jgit.ssh.apache/...", "//org.eclipse.jgit.test/...", "//org.eclipse.jgit.ui/...", "//org.eclipse.jgit/...", From 68017a029cb6b8648b29ae695c9e614d1f7a9770 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Wed, 20 Oct 2021 09:51:43 +0200 Subject: [PATCH 08/24] sshd: prepare for using an SSH agent Add interfaces Connector and ConnectorFactory. A "connector" is just something that knows how to connect to an ssh-agent and then can make simple synchronous RPC-style requests (request-reply). Add a way to customize an SshdSessionFactory with a ConnectorFactory. Provide a default setup using the Java ServiceLoader mechanism to discover an ConnectorFactory. Implement an SshAgentClient in the internal part. Unfortunately we cannot re-use the implementation in Apache MINA sshd: it's hard-wired to Apache Tomcat APR, and it's also buggy. No behavior changes yet since there is nothing that would provide an actual ConnectorFactory. So for Apache MINA sshd, the SshAgentFactory remains null as before. Change-Id: I963a3d181357df2bdb66298bc702f2b9a6607a30 Signed-off-by: Thomas Wolf --- .../META-INF/MANIFEST.MF | 4 +- .../transport/sshd/SshdText.properties | 5 + .../transport/sshd/JGitSshClient.java | 20 ++ .../internal/transport/sshd/SshdText.java | 14 + .../sshd/agent/ConnectorFactoryProvider.java | 51 ++++ .../sshd/agent/JGitSshAgentFactory.java | 72 +++++ .../transport/sshd/agent/SshAgentClient.java | 246 ++++++++++++++++++ .../transport/sshd/SshdSessionFactory.java | 21 +- .../sshd/SshdSessionFactoryBuilder.java | 55 +++- .../sshd/agent/AbstractConnector.java | 116 +++++++++ .../jgit/transport/sshd/agent/Connector.java | 61 +++++ .../sshd/agent/ConnectorFactory.java | 58 +++++ 12 files changed, 719 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF index 1f48841bc..339ba3aae 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -23,6 +23,7 @@ Export-Package: org.eclipse.jgit.internal.transport.sshd;version="6.0.0";x-inter org.apache.sshd.common.signature, org.apache.sshd.common.util.buffer, org.eclipse.jgit.transport", + org.eclipse.jgit.internal.transport.sshd.agent;version="6.0.0";x-internal:=true, org.eclipse.jgit.internal.transport.sshd.auth;version="6.0.0";x-internal:=true, org.eclipse.jgit.internal.transport.sshd.proxy;version="6.0.0";x-friends:="org.eclipse.jgit.ssh.apache.test", org.eclipse.jgit.transport.sshd;version="6.0.0"; @@ -31,7 +32,8 @@ Export-Package: org.eclipse.jgit.internal.transport.sshd;version="6.0.0";x-inter org.apache.sshd.common.keyprovider, org.eclipse.jgit.util, org.apache.sshd.client.session, - org.apache.sshd.client.keyverifier" + org.apache.sshd.client.keyverifier", + org.eclipse.jgit.transport.sshd.agent;version="6.0.0" Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.agent;version="[2.7.0,2.8.0)", org.apache.sshd.client;version="[2.7.0,2.8.0)", diff --git a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties index defcbdcfc..2bba736aa 100644 --- a/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties +++ b/org.eclipse.jgit.ssh.apache/resources/org/eclipse/jgit/internal/transport/sshd/SshdText.properties @@ -19,6 +19,7 @@ identityFileNoKey=No keys found in identity {0} identityFileMultipleKeys=Multiple key pairs found in identity {0} identityFileNotFound=Skipping identity ''{0}'': file not found identityFileUnsupportedFormat=Unsupported format in identity {0} +invalidSignatureAlgorithm=Signature algorithm ''{0}'' is not valid for a key of type ''{1}'' kexServerKeyInvalid=Server key did not validate keyEncryptedMsg=Key ''{0}'' is encrypted. Enter the passphrase to decrypt it. keyEncryptedPrompt=Passphrase @@ -84,6 +85,10 @@ serverIdTooLong=Server identification is longer than 255 characters (including l serverIdWithNul=Server identification contains a NUL character: {0} sessionCloseFailed=Closing the session failed sessionWithoutUsername=SSH session created without user name; cannot authenticate +sshAgentReplyLengthError=Invalid SSH agent reply message length {0} after command {1} +sshAgentReplyUnexpected=Unexpected reply from ssh-agent: {0} +sshAgentShortReadBuffer=Short read from SSH agent +sshAgentWrongNumberOfKeys=Invalid number of SSH agent keys: {0} sshClosingDown=Apache MINA sshd session factory is closing down; cannot create new ssh sessions on this factory sshCommandTimeout={0} timed out after {1} seconds while opening the channel sshProcessStillRunning={0} is not yet completed, cannot get exit code 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 fdb8cde67..71e8e6158 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 @@ -32,8 +32,10 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.sshd.agent.SshAgentFactory; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.future.ConnectFuture; @@ -100,6 +102,8 @@ public class JGitSshClient extends SshClient { private ProxyDataFactory proxyDatabase; + private Supplier agentFactorySupplier = () -> null; + @Override protected SessionFactory createSessionFactory() { // Override the parent's default @@ -368,6 +372,22 @@ public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } + @Override + public SshAgentFactory getAgentFactory() { + return agentFactorySupplier.get(); + } + + @Override + protected void checkConfig() { + // The super class requires channel factories for agent forwarding if a + // factory for an SSH agent is set. We haven't implemented this yet, and + // we don't do SSH agent forwarding for now. Unfortunately, there is no + // way to bypass this check in the super class except making + // getAgentFactory() return null until after the check. + super.checkConfig(); + agentFactorySupplier = super::getAgentFactory; + } + /** * A {@link SessionFactory} to create our own specialized * {@link JGitClientSession}s. diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java index c0f571962..00ee62d6d 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/SshdText.java @@ -1,3 +1,12 @@ +/* + * Copyright (C) 2018, 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.internal.transport.sshd; import org.eclipse.jgit.nls.NLS; @@ -39,6 +48,7 @@ public static SshdText get() { /***/ public String identityFileMultipleKeys; /***/ public String identityFileNotFound; /***/ public String identityFileUnsupportedFormat; + /***/ public String invalidSignatureAlgorithm; /***/ public String kexServerKeyInvalid; /***/ public String keyEncryptedMsg; /***/ public String keyEncryptedPrompt; @@ -96,6 +106,10 @@ public static SshdText get() { /***/ public String serverIdWithNul; /***/ public String sessionCloseFailed; /***/ public String sessionWithoutUsername; + /***/ public String sshAgentReplyLengthError; + /***/ public String sshAgentReplyUnexpected; + /***/ public String sshAgentShortReadBuffer; + /***/ public String sshAgentWrongNumberOfKeys; /***/ public String sshClosingDown; /***/ public String sshCommandTimeout; /***/ public String sshProcessStillRunning; diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java new file mode 100644 index 000000000..9984f9976 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java @@ -0,0 +1,51 @@ +/* + * 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.internal.transport.sshd.agent; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; + +/** + * Provides a {@link ConnectorFactory} obtained via the {@link ServiceLoader}. + */ +public final class ConnectorFactoryProvider { + + private static final ConnectorFactory FACTORY = loadDefaultFactory(); + + private static ConnectorFactory loadDefaultFactory() { + ServiceLoader loader = ServiceLoader + .load(ConnectorFactory.class); + Iterator iter = loader.iterator(); + while (iter.hasNext()) { + ConnectorFactory candidate = iter.next(); + if (candidate.isSupported()) { + return candidate; + } + } + return null; + + } + + private ConnectorFactoryProvider() { + // No instantiation + } + + /** + * Retrieves the default {@link ConnectorFactory} obtained via the + * {@link ServiceLoader}. + * + * @return the {@link ConnectorFactory}, or {@code null} if none. + */ + public static ConnectorFactory getDefaultFactory() { + return FACTORY; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java new file mode 100644 index 000000000..1ed2ab9d7 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/JGitSshAgentFactory.java @@ -0,0 +1,72 @@ +/* + * 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.internal.transport.sshd.agent; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentFactory; +import org.apache.sshd.agent.SshAgentServer; +import org.apache.sshd.common.FactoryManager; +import org.apache.sshd.common.channel.ChannelFactory; +import org.apache.sshd.common.session.ConnectionService; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; + +/** + * A factory for creating {@link SshAgentClient}s. + */ +public class JGitSshAgentFactory implements SshAgentFactory { + + private final @NonNull ConnectorFactory factory; + + private final File homeDir; + + /** + * Creates a new {@link JGitSshAgentFactory}. + * + * @param factory + * {@link JGitSshAgentFactory} to wrap + * @param homeDir + * for obtaining the current local user's home directory + */ + public JGitSshAgentFactory(@NonNull ConnectorFactory factory, + File homeDir) { + this.factory = factory; + this.homeDir = homeDir; + } + + @Override + public List getChannelForwardingFactories( + FactoryManager manager) { + // No agent forwarding supported yet. + return Collections.emptyList(); + } + + @Override + public SshAgent createClient(FactoryManager manager) throws IOException { + // sshd 2.8.0 will pass us the session here. At that point, we can get + // the HostConfigEntry and extract and handle the IdentityAgent setting. + // For now, pass null to let the ConnectorFactory do its default + // behavior (Pageant on Windows, SSH_AUTH_SOCK on Unixes with the + // jgit-builtin factory). + return new SshAgentClient(factory.create(null, homeDir)); + } + + @Override + public SshAgentServer createServer(ConnectionService service) + throws IOException { + // This should be called in a server only. + return null; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java new file mode 100644 index 000000000..08483e4c2 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/SshAgentClient.java @@ -0,0 +1,246 @@ +/* + * 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.internal.transport.sshd.agent; + +import java.io.IOException; +import java.security.KeyPair; +import java.security.PublicKey; +import java.text.MessageFormat; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentConstants; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.BufferException; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.transport.sshd.agent.Connector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A client for an SSH2 agent. This client supports only querying identities and + * signature requests. + * + * @see SSH + * Agent Protocol, RFC draft + */ +public class SshAgentClient implements SshAgent { + + private static final Logger LOG = LoggerFactory + .getLogger(SshAgentClient.class); + + // OpenSSH limit + private static final int MAX_NUMBER_OF_KEYS = 2048; + + private final AtomicBoolean closed = new AtomicBoolean(); + + private final Connector connector; + + /** + * Creates a new {@link SshAgentClient} implementing the SSH2 ssh agent + * protocol, using the given {@link Connector} to connect to the SSH agent + * and to exchange messages. + * + * @param connector + * {@link Connector} to use + */ + public SshAgentClient(Connector connector) { + this.connector = connector; + } + + private boolean open(boolean debugging) throws IOException { + if (closed.get()) { + if (debugging) { + LOG.debug("SSH agent connection already closed"); //$NON-NLS-1$ + } + return false; + } + boolean connected = connector != null && connector.connect(); + if (!connected) { + if (debugging) { + LOG.debug("No SSH agent (SSH_AUTH_SOCK not set)"); //$NON-NLS-1$ + } + } + return connected; + } + + @Override + public void close() throws IOException { + if (!closed.getAndSet(true) && connector != null) { + connector.close(); + } + } + + @Override + public Iterable> getIdentities() + throws IOException { + boolean debugging = LOG.isDebugEnabled(); + if (!open(debugging)) { + return Collections.emptyList(); + } + if (debugging) { + LOG.debug("Requesting identities from SSH agent"); //$NON-NLS-1$ + } + try { + Buffer reply = rpc( + SshAgentConstants.SSH2_AGENTC_REQUEST_IDENTITIES); + byte cmd = reply.getByte(); + if (cmd != SshAgentConstants.SSH2_AGENT_IDENTITIES_ANSWER) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentReplyUnexpected, + SshAgentConstants.getCommandMessageName(cmd))); + } + int numberOfKeys = reply.getInt(); + if (numberOfKeys < 0 || numberOfKeys > MAX_NUMBER_OF_KEYS) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentWrongNumberOfKeys, + Integer.toString(numberOfKeys))); + } + if (numberOfKeys == 0) { + if (debugging) { + LOG.debug("SSH agent has no keys"); //$NON-NLS-1$ + } + return Collections.emptyList(); + } + if (debugging) { + LOG.debug("Got {} key(s) from the SSH agent", //$NON-NLS-1$ + Integer.toString(numberOfKeys)); + } + boolean tracing = LOG.isTraceEnabled(); + List> keys = new ArrayList<>( + numberOfKeys); + for (int i = 0; i < numberOfKeys; i++) { + PublicKey key = reply.getPublicKey(); + String comment = reply.getString(); + if (tracing) { + LOG.trace("Got SSH agent {} key: {} {}", //$NON-NLS-1$ + KeyUtils.getKeyType(key), + KeyUtils.getFingerPrint(key), comment); + } + keys.add(new AbstractMap.SimpleImmutableEntry<>(key, comment)); + } + return keys; + } catch (BufferException e) { + throw new SshException(SshdText.get().sshAgentShortReadBuffer, e); + } + } + + @Override + public Map.Entry sign(SessionContext session, PublicKey key, + String algorithm, byte[] data) throws IOException { + boolean debugging = LOG.isDebugEnabled(); + String keyType = KeyUtils.getKeyType(key); + String signatureAlgorithm; + if (algorithm != null) { + if (!KeyUtils.getCanonicalKeyType(algorithm).equals(keyType)) { + throw new IllegalArgumentException(MessageFormat.format( + SshdText.get().invalidSignatureAlgorithm, algorithm, + keyType)); + } + signatureAlgorithm = algorithm; + } else { + signatureAlgorithm = keyType; + } + if (!open(debugging)) { + return null; + } + int flags = 0; + switch (signatureAlgorithm) { + case KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS: + case KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS: + flags = 4; + break; + case KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS: + case KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS: + flags = 2; + break; + default: + break; + } + ByteArrayBuffer msg = new ByteArrayBuffer(); + msg.putInt(0); + msg.putByte(SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST); + msg.putPublicKey(key); + msg.putBytes(data); + msg.putInt(flags); + if (debugging) { + LOG.debug( + "sign({}): signing request to SSH agent for {} key, {} signature; flags={}", //$NON-NLS-1$ + session, keyType, signatureAlgorithm, + Integer.toString(flags)); + } + Buffer reply = rpc(SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST, + msg.getCompactData()); + byte cmd = reply.getByte(); + if (cmd != SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE) { + throw new SshException( + MessageFormat.format(SshdText.get().sshAgentReplyUnexpected, + SshAgentConstants.getCommandMessageName(cmd))); + } + try { + Buffer signatureReply = new ByteArrayBuffer(reply.getBytes()); + String actualAlgorithm = signatureReply.getString(); + byte[] signature = signatureReply.getBytes(); + if (LOG.isTraceEnabled()) { + LOG.trace( + "sign({}): signature reply from SSH agent for {} key: {} signature={}", //$NON-NLS-1$ + session, keyType, actualAlgorithm, + BufferUtils.toHex(':', signature)); + + } else if (LOG.isDebugEnabled()) { + LOG.debug( + "sign({}): signature reply from SSH agent for {} key, {} signature", //$NON-NLS-1$ + session, keyType, actualAlgorithm); + } + return new AbstractMap.SimpleImmutableEntry<>(actualAlgorithm, + signature); + } catch (BufferException e) { + throw new SshException(SshdText.get().sshAgentShortReadBuffer, e); + } + } + + private Buffer rpc(byte command, byte[] message) throws IOException { + return new ByteArrayBuffer(connector.rpc(command, message)); + } + + private Buffer rpc(byte command) throws IOException { + return new ByteArrayBuffer(connector.rpc(command)); + } + + @Override + public boolean isOpen() { + return !closed.get(); + } + + @Override + public void addIdentity(KeyPair key, String comment) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void removeIdentity(PublicKey key) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAllIdentities() throws IOException { + throw new UnsupportedOperationException(); + } +} 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 cad959c90..da99f56cb 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Thomas Wolf and others + * Copyright (C) 2018, 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 @@ -56,11 +56,14 @@ import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase; import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper; import org.eclipse.jgit.internal.transport.sshd.SshdText; +import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider; +import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; import org.eclipse.jgit.transport.SshConstants; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.FS; /** @@ -216,6 +219,11 @@ public SshdSession getSession(URIish uri, new JGitUserInteraction(credentialsProvider)); client.setUserAuthFactories(getUserAuthFactories()); client.setKeyIdentityProvider(defaultKeysProvider); + ConnectorFactory connectors = getConnectorFactory(); + if (connectors != null) { + client.setAgentFactory( + new JGitSshAgentFactory(connectors, home)); + } // JGit-specific things: JGitSshClient jgitClient = (JGitSshClient) client; jgitClient.setKeyCache(getKeyCache()); @@ -436,6 +444,17 @@ protected ServerKeyDatabase createServerKeyDatabase(@NonNull File homeDir, getDefaultKnownHostsFiles(sshDir)); } + /** + * Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents + * are not supported. + * + * @return the factory, or {@code null} if no SSH agent support is desired + * @since 6.0 + */ + protected ConnectorFactory getConnectorFactory() { + return ConnectorFactoryProvider.getDefaultFactory(); + } + /** * Gets the list of default user known hosts files. The default returns * ~/.ssh/known_hosts and ~/.ssh/known_hosts2. The ssh config diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java index 2147c2bd5..7ed9b5ea3 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSessionFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Thomas Wolf and others + * Copyright (C) 2020, 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 @@ -20,6 +20,7 @@ import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.StringUtils; /** @@ -114,7 +115,7 @@ public SshdSessionFactoryBuilder setConfigFile( } /** - * A factory interface for creating a @link SshConfigStore}. + * A factory interface for creating a {@link SshConfigStore}. */ @FunctionalInterface public interface ConfigStoreFactory { @@ -232,6 +233,41 @@ public SshdSessionFactoryBuilder setServerKeyDatabase( return this; } + /** + * Sets an explicit {@link ConnectorFactory}. If {@code null}, there will be + * no support for SSH agents. + *

+ * If not set, the created {@link SshdSessionFactory} will use the + * {@link java.util.ServiceLoader} to find an {@link ConnectorFactory}. + *

+ * + * @param factory + * {@link ConnectorFactory} to use + * @return this {@link SshdSessionFactoryBuilder} + * @since 6.0 + */ + public SshdSessionFactoryBuilder setConnectorFactory( + ConnectorFactory factory) { + this.state.connectorFactory = factory; + this.state.connectorFactorySet = true; + return this; + } + + /** + * Removes a previously set {@link ConnectorFactory}. The created + * {@link SshdSessionFactory} will use the {@link java.util.ServiceLoader} + * to find an {@link ConnectorFactory}. This is also the default if + * {@link #setConnectorFactory(ConnectorFactory)} isn't called at all. + * + * @return this {@link SshdSessionFactoryBuilder} + * @since 6.0 + */ + public SshdSessionFactoryBuilder withDefaultConnectorFactory() { + this.state.connectorFactory = null; + this.state.connectorFactorySet = false; + return this; + } + /** * Builds a {@link SshdSessionFactory} as configured, using the given * {@link KeyCache} for caching keys. @@ -277,6 +313,10 @@ private static class State { BiFunction serverKeyDatabaseCreator; + ConnectorFactory connectorFactory; + + boolean connectorFactorySet; + State copy() { State c = new State(); c.proxyDataFactory = proxyDataFactory; @@ -290,6 +330,8 @@ State copy() { c.defaultKeyFileFinder = defaultKeyFileFinder; c.defaultKeysProvider = defaultKeysProvider; c.serverKeyDatabaseCreator = serverKeyDatabaseCreator; + c.connectorFactory = connectorFactory; + c.connectorFactorySet = connectorFactorySet; return c; } @@ -388,6 +430,15 @@ protected SshConfigStore createSshConfigStore(File homeDir, return super.createSshConfigStore(homeDir, configFile, localUserName); } + + @Override + protected ConnectorFactory getConnectorFactory() { + if (connectorFactorySet) { + return connectorFactory; + } + // Use default via ServiceLoader + return super.getConnectorFactory(); + } } } } diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java new file mode 100644 index 000000000..71ddc3b00 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/AbstractConnector.java @@ -0,0 +1,116 @@ +/* + * 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.transport.sshd.agent; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Objects; + +import org.apache.sshd.agent.SshAgentConstants; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.eclipse.jgit.internal.transport.sshd.SshdText; + +/** + * Provides some utility methods for implementing {@link Connector}s. + * + * @since 6.0 + */ +public abstract class AbstractConnector implements Connector { + + // A somewhat sane lower bound for the maximum reply length + private static final int MIN_REPLY_LENGTH = 8 * 1024; + + /** + * Default maximum reply length. 256kB is the OpenSSH limit. + */ + protected static final int DEFAULT_MAX_REPLY_LENGTH = 256 * 1024; + + private final int maxReplyLength; + + /** + * Creates a new instance using the {@link #DEFAULT_MAX_REPLY_LENGTH}. + */ + protected AbstractConnector() { + this(DEFAULT_MAX_REPLY_LENGTH); + } + + /** + * Creates a new instance. + * + * @param maxReplyLength + * maximum number of payload bytes we're ready to accept + */ + protected AbstractConnector(int maxReplyLength) { + if (maxReplyLength < MIN_REPLY_LENGTH) { + throw new IllegalArgumentException( + "Maximum payload length too small"); //$NON-NLS-1$ + } + this.maxReplyLength = maxReplyLength; + } + + /** + * Retrieves the maximum message length this {@link AbstractConnector} is + * configured for. + * + * @return the maximum message length + */ + protected int getMaximumMessageLength() { + return this.maxReplyLength; + } + + /** + * Prepares a message for sending by inserting the command and message + * length. + * + * @param command + * SSH agent command the request is for + * @param message + * about to be sent, including the 5 spare bytes at the front + * @throws IllegalArgumentException + * if {@code message} has less than 5 bytes + */ + protected void prepareMessage(byte command, byte[] message) + throws IllegalArgumentException { + Objects.requireNonNull(message); + if (message.length < 5) { + // No translation; internal error + throw new IllegalArgumentException("Message buffer for " //$NON-NLS-1$ + + SshAgentConstants.getCommandMessageName(command) + + " must have at least 5 bytes; have only " //$NON-NLS-1$ + + message.length); + } + BufferUtils.putUInt(message.length - 4, message); + message[4] = command; + } + + /** + * Checks the received length of a reply. + * + * @param command + * SSH agent command the reply is for + * @param length + * length as received: number of payload bytes + * @return the length as an {@code int} + * @throws IOException + * if the length is invalid + */ + protected int toLength(byte command, byte[] length) + throws IOException { + long l = BufferUtils.getUInt(length); + if (l <= 0 || l > maxReplyLength - 4) { + throw new SshException(MessageFormat.format( + SshdText.get().sshAgentReplyLengthError, + Long.toString(l), + SshAgentConstants.getCommandMessageName(command))); + } + return (int) l; + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java new file mode 100644 index 000000000..b6da0866a --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.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.transport.sshd.agent; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Simple interface for connecting to something and making RPC-style + * request-reply calls. + * + * @since 6.0 + */ +public interface Connector extends Closeable { + + /** + * Connects to an SSH agent if there is one running. If called when already + * connected just returns {@code true}. + * + * @return {@code true} if an SSH agent is available and connected, + * {@false} if no SSH agent is available + * @throws IOException + * if connecting to the SSH agent failed + */ + boolean connect() throws IOException; + + /** + * Performs a remote call to the SSH agent and returns the result. + * + * @param command + * to send + * @param message + * to send; must have at least 5 bytes, and must have 5 unused + * bytes at the front. + * @return the result received + * @throws IOException + * if an error occurs + */ + byte[] rpc(byte command, byte[] message) throws IOException; + + /** + * Performs a remote call sending only a command without any parameters to + * the SSH agent and returns the result. + * + * @param command + * to send + * @return the result received + * @throws IOException + * if an error occurs + */ + default byte[] rpc(byte command) throws IOException { + return rpc(command, new byte[5]); + } +} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java new file mode 100644 index 000000000..fa725ab85 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java @@ -0,0 +1,58 @@ +/* + * 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.transport.sshd.agent; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.annotations.NonNull; + +/** + * A factory for creating {@link Connector}s. + * + * @since 6.0 + */ +public interface ConnectorFactory { + + /** + * Creates a new {@link Connector}. + * + * @param identityAgent + * identifies the wanted agent connection; if {@code null}, the + * factory is free to provide a {@link Connector} to a default + * agent. The value will typically come from the IdentityAgent + * setting in ~/.ssh/config. + * @param homeDir + * the current local user's home directory as configured in the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory} + * @return a new {@link Connector} + * @throws IOException + * if no connector can be created + */ + @NonNull + Connector create(String identityAgent, File homeDir) + throws IOException; + + /** + * Tells whether this {@link ConnectorFactory} is applicable on the + * currently running platform. + * + * @return {@code true} if the factory can be used, {@code false} otherwise + */ + boolean isSupported(); + + /** + * Retrieves a name for this factory. + * + * @return the name + */ + String getName(); + +} From 634302d2da74226cff9f78e121ad5b8216c476e6 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Tue, 2 Nov 2021 18:48:25 +0100 Subject: [PATCH 09/24] sshd: add support for ssh-agent Add a simple SSH agent connector using JNA. Include com.sum.jna and com.sun.jna.platform in the target platform. JNA is used to communicate through Unix domain sockets with ssh-agent, and if on Windows, to communicate via shared memory with Pageant. The new bundle o.e.j.ssh.apache.agent is an OSGi fragment so that the java.util.ServiceLoader can find the provided factory without further ado in OSGi environments. Adapt both maven and bazel builds to include the new bundle. Manually tested on OS X, CentOS 7, and Win10 with Pageant 0.76. Tested by installing JGit built from this change into freshly downloaded Eclipse 2021-12 M1, and then doing git fetches via SSH with different ~/.ssh/config settings (explicit IdentityFile, without any but a key in the agent, with no keys and a key in the agent and IdentitiesOnly=yes (must fail)). Bug: 541274 Bug: 541275 Change-Id: I34e85467293707dbad1eb44d1f40fc2e70ba3622 Signed-off-by: Thomas Wolf --- BUILD | 1 + WORKSPACE | 12 + lib/BUILD | 17 + .../org.eclipse.jgit.pgm.feature/pom.xml | 13 + .../org.eclipse.jgit.repository/category.xml | 12 + .../org.eclipse.jgit.repository/pom.xml | 7 +- .../feature.xml | 7 + .../feature.xml | 8 + .../pom.xml | 6 + .../org.eclipse.jgit.target/jgit-4.17.target | 6 +- .../org.eclipse.jgit.target/jgit-4.18.target | 6 +- .../org.eclipse.jgit.target/jgit-4.19.target | 6 +- .../org.eclipse.jgit.target/jgit-4.20.target | 6 +- .../org.eclipse.jgit.target/jgit-4.21.target | 6 +- .../orbit/R20210825222808-2021-09.tpd | 4 + org.eclipse.jgit.packaging/pom.xml | 6 + org.eclipse.jgit.pgm/pom.xml | 6 + org.eclipse.jgit.ssh.apache.agent/.classpath | 8 + org.eclipse.jgit.ssh.apache.agent/.fbprefs | 125 +++++ org.eclipse.jgit.ssh.apache.agent/.project | 28 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 518 ++++++++++++++++++ .../.settings/org.eclipse.jdt.ui.prefs | 66 +++ .../org.eclipse.mylyn.tasks.ui.prefs | 4 + .../.settings/org.eclipse.mylyn.team.ui.prefs | 3 + .../.settings/org.eclipse.pde.api.tools.prefs | 104 ++++ .../.settings/org.eclipse.pde.core.prefs | 3 + org.eclipse.jgit.ssh.apache.agent/BUILD | 22 + .../META-INF/MANIFEST.MF | 16 + .../META-INF/SOURCE-MANIFEST.MF | 7 + org.eclipse.jgit.ssh.apache.agent/about.html | 96 ++++ .../build.properties | 7 + .../plugin.properties | 2 + org.eclipse.jgit.ssh.apache.agent/pom.xml | 227 ++++++++ ...jgit.transport.sshd.agent.ConnectorFactory | 1 + .../sshd/agent/connector/Texts.properties | 16 + .../sshd/agent/connector/Factory.java | 44 ++ .../sshd/agent/connector/LibraryHolder.java | 72 +++ .../agent/connector/PageantConnector.java | 56 ++ .../sshd/agent/connector/PageantLibrary.java | 240 ++++++++ .../sshd/agent/connector/Sockets.java | 78 +++ .../transport/sshd/agent/connector/Texts.java | 46 ++ .../connector/UnixDomainSocketConnector.java | 218 ++++++++ .../sshd/agent/connector/UnixSockets.java | 122 +++++ .../META-INF/MANIFEST.MF | 1 + .../sshd/ApacheSshProtocol2Test.java | 15 +- .../jgit/transport/sshd/ApacheSshTest.java | 15 +- .../transport/sshd/NoFilesSshBuilderTest.java | 4 + .../jgit/transport/sshd/NoFilesSshTest.java | 7 + .../sshd/JGitPublicKeyAuthentication.java | 49 +- .../transport/sshd/SshdSessionFactory.java | 17 +- .../jgit/transport/sshd/agent/Connector.java | 1 + .../sshd/agent/ConnectorFactory.java | 5 +- .../transport/sshd/agent/package-info.java | 6 + pom.xml | 1 + tools/BUILD | 1 + 57 files changed, 2362 insertions(+), 24 deletions(-) create mode 100644 org.eclipse.jgit.ssh.apache.agent/.classpath create mode 100644 org.eclipse.jgit.ssh.apache.agent/.fbprefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.project create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.tasks.ui.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.team.ui.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.api.tools.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.core.prefs create mode 100644 org.eclipse.jgit.ssh.apache.agent/BUILD create mode 100644 org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF create mode 100644 org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF create mode 100644 org.eclipse.jgit.ssh.apache.agent/about.html create mode 100644 org.eclipse.jgit.ssh.apache.agent/build.properties create mode 100644 org.eclipse.jgit.ssh.apache.agent/plugin.properties create mode 100644 org.eclipse.jgit.ssh.apache.agent/pom.xml create mode 100644 org.eclipse.jgit.ssh.apache.agent/resources/META-INF/services/org.eclipse.jgit.transport.sshd.agent.ConnectorFactory create mode 100644 org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantLibrary.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java create mode 100644 org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixSockets.java create mode 100644 org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java diff --git a/BUILD b/BUILD index 184ab272d..b8d8b9f0b 100644 --- a/BUILD +++ b/BUILD @@ -14,6 +14,7 @@ genrule( "//org.eclipse.jgit.lfs.server:jgit-lfs-server", "//org.eclipse.jgit.junit:junit", "//org.eclipse.jgit.ssh.apache:ssh-apache", + "//org.eclipse.jgit.ssh.apache.agent:ssh-apache-agent", "//org.eclipse.jgit.ssh.jsch:ssh-jsch", ], outs = ["all.zip"], diff --git a/WORKSPACE b/WORKSPACE index 1324ce46a..f4694b3ae 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -133,6 +133,18 @@ maven_jar( sha1 = "0c9eff7145e20b338c1dd6aca36ba93ed7c0147c", ) +maven_jar( + name = "jna", + artifact = "net.java.dev.jna:jna:5.8.0", + sha1 = "3551d8d827e54858214107541d3aff9c615cb615", +) + +maven_jar( + name = "jna-platform", + artifact = "net.java.dev.jna:jna-platform:5.8.0", + sha1 = "2f12f6d7f7652270d13624cef1b82d8cd9a5398e", +) + maven_jar( name = "commons-codec", artifact = "commons-codec:commons-codec:1.14", diff --git a/lib/BUILD b/lib/BUILD index 1901be8ec..00c91e3ba 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -76,6 +76,7 @@ java_library( visibility = [ "//org.eclipse.jgit.junit.ssh:__pkg__", "//org.eclipse.jgit.ssh.apache:__pkg__", + "//org.eclipse.jgit.ssh.apache.agent:__pkg__", "//org.eclipse.jgit.ssh.apache.test:__pkg__", "//org.eclipse.jgit.test:__pkg__", ], @@ -93,6 +94,22 @@ java_library( exports = ["@sshd-sftp//jar"], ) +java_library( + name = "jna", + visibility = [ + "//org.eclipse.jgit.ssh.apache.agent:__pkg__", + ], + exports = ["@jna//jar"], +) + +java_library( + name = "jna-platform", + visibility = [ + "//org.eclipse.jgit.ssh.apache.agent:__pkg__", + ], + exports = ["@jna-platform//jar"], +) + java_library( name = "javaewah", visibility = ["//visibility:public"], diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml index 813981039..22e4ebc24 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml @@ -45,6 +45,19 @@ org.eclipse.jgit.ui ${project.version} + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache.agent + ${project.version} + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml index ca0935a51..a15d0fd76 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/category.xml @@ -57,6 +57,18 @@ + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml index f36717e95..98f264da0 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml @@ -91,7 +91,12 @@ org.eclipse.jgit.ssh.apache ${project.version} - + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache.agent + ${project.version} + + org.eclipse.jgit org.eclipse.jgit.ssh.jsch ${project.version} diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml index eb0a658e5..dcd1238f9 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml @@ -110,6 +110,13 @@ version="0.0.0" unpack="false"/> + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml index 886b9b3ce..5e0f05a85 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.ssh.apache.feature/pom.xml @@ -39,6 +39,12 @@ ${project.version} + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache.agent + ${project.version} + + 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 ab7f63d98..5356dea1c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,7 +1,7 @@ - + @@ -31,6 +31,10 @@ + + + + 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 611ac5bcf..cf8d12fdb 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,7 +1,7 @@ - + @@ -31,6 +31,10 @@ + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target index d852f9178..3e6ee56b8 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target @@ -1,7 +1,7 @@ - + @@ -31,6 +31,10 @@ + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target index 08fb6ccc3..ce9d8bca2 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target @@ -1,7 +1,7 @@ - + @@ -31,6 +31,10 @@ + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target index eaf975258..41954429d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target @@ -1,7 +1,7 @@ - + @@ -31,6 +31,10 @@ + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd index 059a5844a..99f352011 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210825222808-2021-09.tpd @@ -8,6 +8,10 @@ location "https://download.eclipse.org/tools/orbit/downloads/drops/R202108252228 com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902] com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] + com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343] + com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343] + com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004] + com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004] javaewah [1.1.12.v20210622-2206,1.1.12.v20210622-2206] javaewah.source [1.1.12.v20210622-2206,1.1.12.v20210622-2206] javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml index 7b75c2eb6..4e1fbcb97 100644 --- a/org.eclipse.jgit.packaging/pom.xml +++ b/org.eclipse.jgit.packaging/pom.xml @@ -147,6 +147,12 @@ ${project.version} sources + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache.agent + ${project.version} + sources + org.eclipse.jgit org.eclipse.jgit.ssh.jsch diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index c28da4d7f..11f3f7963 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -79,6 +79,12 @@ ${project.version} + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache.agent + ${project.version} + + org.eclipse.jgit org.eclipse.jgit.ssh.jsch diff --git a/org.eclipse.jgit.ssh.apache.agent/.classpath b/org.eclipse.jgit.ssh.apache.agent/.classpath new file mode 100644 index 000000000..df1b324f7 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclipse.jgit.ssh.apache.agent/.fbprefs b/org.eclipse.jgit.ssh.apache.agent/.fbprefs new file mode 100644 index 000000000..81a0767ff --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.fbprefs @@ -0,0 +1,125 @@ +#FindBugs User Preferences +#Mon May 04 16:24:13 PDT 2009 +detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true +detectorBadAppletConstructor=BadAppletConstructor|false +detectorBadResultSetAccess=BadResultSetAccess|true +detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true +detectorBadUseOfReturnValue=BadUseOfReturnValue|true +detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true +detectorBooleanReturnNull=BooleanReturnNull|true +detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true +detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true +detectorCheckTypeQualifiers=CheckTypeQualifiers|true +detectorCloneIdiom=CloneIdiom|false +detectorComparatorIdiom=ComparatorIdiom|true +detectorConfusedInheritance=ConfusedInheritance|true +detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true +detectorCrossSiteScripting=CrossSiteScripting|true +detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true +detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true +detectorDontUseEnum=DontUseEnum|true +detectorDroppedException=DroppedException|true +detectorDumbMethodInvocations=DumbMethodInvocations|true +detectorDumbMethods=DumbMethods|true +detectorDuplicateBranches=DuplicateBranches|true +detectorEmptyZipFileEntry=EmptyZipFileEntry|true +detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true +detectorFinalizerNullsFields=FinalizerNullsFields|true +detectorFindBadCast2=FindBadCast2|true +detectorFindBadForLoop=FindBadForLoop|true +detectorFindCircularDependencies=FindCircularDependencies|false +detectorFindDeadLocalStores=FindDeadLocalStores|true +detectorFindDoubleCheck=FindDoubleCheck|true +detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true +detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true +detectorFindFinalizeInvocations=FindFinalizeInvocations|true +detectorFindFloatEquality=FindFloatEquality|true +detectorFindHEmismatch=FindHEmismatch|true +detectorFindInconsistentSync2=FindInconsistentSync2|true +detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true +detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true +detectorFindMaskedFields=FindMaskedFields|true +detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true +detectorFindNakedNotify=FindNakedNotify|true +detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true +detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true +detectorFindNonShortCircuit=FindNonShortCircuit|true +detectorFindNullDeref=FindNullDeref|true +detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true +detectorFindOpenStream=FindOpenStream|true +detectorFindPuzzlers=FindPuzzlers|true +detectorFindRefComparison=FindRefComparison|true +detectorFindReturnRef=FindReturnRef|true +detectorFindRunInvocations=FindRunInvocations|true +detectorFindSelfComparison=FindSelfComparison|true +detectorFindSelfComparison2=FindSelfComparison2|true +detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true +detectorFindSpinLoop=FindSpinLoop|true +detectorFindSqlInjection=FindSqlInjection|true +detectorFindTwoLockWait=FindTwoLockWait|true +detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true +detectorFindUnconditionalWait=FindUnconditionalWait|true +detectorFindUninitializedGet=FindUninitializedGet|true +detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true +detectorFindUnreleasedLock=FindUnreleasedLock|true +detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true +detectorFindUnsyncGet=FindUnsyncGet|true +detectorFindUselessControlFlow=FindUselessControlFlow|true +detectorFormatStringChecker=FormatStringChecker|true +detectorHugeSharedStringConstants=HugeSharedStringConstants|true +detectorIDivResultCastToDouble=IDivResultCastToDouble|true +detectorIncompatMask=IncompatMask|true +detectorInconsistentAnnotations=InconsistentAnnotations|true +detectorInefficientMemberAccess=InefficientMemberAccess|false +detectorInefficientToArray=InefficientToArray|true +detectorInfiniteLoop=InfiniteLoop|true +detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true +detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false +detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true +detectorInitializationChain=InitializationChain|true +detectorInstantiateStaticClass=InstantiateStaticClass|true +detectorInvalidJUnitTest=InvalidJUnitTest|true +detectorIteratorIdioms=IteratorIdioms|true +detectorLazyInit=LazyInit|true +detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true +detectorMethodReturnCheck=MethodReturnCheck|true +detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true +detectorMutableLock=MutableLock|true +detectorMutableStaticFields=MutableStaticFields|true +detectorNaming=Naming|true +detectorNumberConstructor=NumberConstructor|true +detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true +detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true +detectorPublicSemaphores=PublicSemaphores|false +detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true +detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true +detectorRedundantInterfaces=RedundantInterfaces|true +detectorRepeatedConditionals=RepeatedConditionals|true +detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true +detectorSerializableIdiom=SerializableIdiom|true +detectorStartInConstructor=StartInConstructor|true +detectorStaticCalendarDetector=StaticCalendarDetector|true +detectorStringConcatenation=StringConcatenation|true +detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true +detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true +detectorSwitchFallthrough=SwitchFallthrough|true +detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true +detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true +detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true +detectorURLProblems=URLProblems|true +detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true +detectorUnnecessaryMath=UnnecessaryMath|true +detectorUnreadFields=UnreadFields|true +detectorUseObjectEquals=UseObjectEquals|false +detectorUselessSubclassMethod=UselessSubclassMethod|false +detectorVarArgsProblems=VarArgsProblems|true +detectorVolatileUsage=VolatileUsage|true +detectorWaitInLoop=WaitInLoop|true +detectorWrongMapIterator=WrongMapIterator|true +detectorXMLFactoryBypass=XMLFactoryBypass|true +detector_threshold=2 +effort=default +excludefilter0=findBugs/FindBugsExcludeFilter.xml +filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false +filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| +run_at_full_build=true diff --git a/org.eclipse.jgit.ssh.apache.agent/.project b/org.eclipse.jgit.ssh.apache.agent/.project new file mode 100644 index 000000000..73358f4a6 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.project @@ -0,0 +1,28 @@ + + + org.eclipse.jgit.ssh.apache.agent + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..66ac15c47 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Mon Aug 11 16:46:12 PDT 2008 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 000000000..006e07ede --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Mon Mar 24 18:55:50 EDT 2008 +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..d1f54bbe6 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,518 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=error +org.eclipse.jdt.core.compiler.problem.deadCode=error +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error +org.eclipse.jdt.core.compiler.problem.potentialNullReference=error +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=error +org.eclipse.jdt.core.compiler.problem.unusedLocal=error +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_record_components=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.text_block_indentation=0 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..5cfb8b6ac --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,66 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGit Format +formatter_settings_version=21 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=true +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.tasks.ui.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.tasks.ui.prefs new file mode 100644 index 000000000..823c0f56a --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.tasks.ui.prefs @@ -0,0 +1,4 @@ +#Tue Jul 19 20:11:28 CEST 2011 +eclipse.preferences.version=1 +project.repository.kind=bugzilla +project.repository.url=https\://bugs.eclipse.org/bugs diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.team.ui.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.team.ui.prefs new file mode 100644 index 000000000..0cba949fb --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.mylyn.team.ui.prefs @@ -0,0 +1,3 @@ +#Tue Jul 19 20:11:28 CEST 2011 +commit.comment.template=${task.description} \n\nBug\: ${task.key} +eclipse.preferences.version=1 diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.api.tools.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.api.tools.prefs new file mode 100644 index 000000000..c0030ded7 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.api.tools.prefs @@ -0,0 +1,104 @@ +ANNOTATION_ELEMENT_TYPE_ADDED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error +ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error +ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error +API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error +API_USE_SCAN_FIELD_SEVERITY=Error +API_USE_SCAN_METHOD_SEVERITY=Error +API_USE_SCAN_TYPE_SEVERITY=Error +CLASS_ELEMENT_TYPE_ADDED_FIELD=Error +CLASS_ELEMENT_TYPE_ADDED_METHOD=Error +CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error +CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error +CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error +CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error +ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error +ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error +ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +FIELD_ELEMENT_TYPE_ADDED_VALUE=Error +FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error +FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error +FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error +FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error +ILLEGAL_EXTEND=Warning +ILLEGAL_IMPLEMENT=Warning +ILLEGAL_INSTANTIATE=Warning +ILLEGAL_OVERRIDE=Warning +ILLEGAL_REFERENCE=Warning +INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error +INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error +INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error +INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error +INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error +INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error +INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +INVALID_ANNOTATION=Ignore +INVALID_JAVADOC_TAG=Ignore +INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Error +LEAK_EXTEND=Warning +LEAK_FIELD_DECL=Warning +LEAK_IMPLEMENT=Warning +LEAK_METHOD_PARAM=Warning +LEAK_METHOD_RETURN_TYPE=Warning +METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error +METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error +METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error +METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error +METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error +METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error +METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error +MISSING_EE_DESCRIPTIONS=Warning +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error +TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error +UNUSED_PROBLEM_FILTERS=Warning +automatically_removed_unused_problem_filters=false +changed_execution_env=Error +eclipse.preferences.version=1 +incompatible_api_component_version=Error +incompatible_api_component_version_include_major_without_breaking_change=Disabled +incompatible_api_component_version_include_minor_without_api_change=Disabled +incompatible_api_component_version_report_major_without_breaking_change=Warning +incompatible_api_component_version_report_minor_without_api_change=Ignore +invalid_since_tag_version=Error +malformed_since_tag=Error +missing_since_tag=Error +report_api_breakage_when_major_version_incremented=Disabled +report_resolution_errors_api_component=Warning diff --git a/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.core.prefs b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..82793f2d2 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +#Thu Jan 14 14:34:32 CST 2010 +eclipse.preferences.version=1 +resolve.requirebundle=false diff --git a/org.eclipse.jgit.ssh.apache.agent/BUILD b/org.eclipse.jgit.ssh.apache.agent/BUILD new file mode 100644 index 000000000..0c8cf838d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/BUILD @@ -0,0 +1,22 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +SRCS = glob(["src/**/*.java"]) + +RESOURCES = glob(["resources/**"]) + +java_library( + name = "ssh-apache-agent", + srcs = SRCS, + resource_strip_prefix = "org.eclipse.jgit.ssh.apache.agent/resources", + resources = RESOURCES, + deps = [ + "//lib:jna", + "//lib:jna-platform", + "//lib:slf4j-api", + "//lib:sshd-osgi", + "//org.eclipse.jgit:jgit", + "//org.eclipse.jgit.ssh.apache:ssh-apache" + ], +) diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF new file mode 100644 index 000000000..19b8ee850 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent;singleton:=true +Bundle-Version: 6.0.0.qualifier +Bundle-Vendor: %Bundle-Vendor +Fragment-Host: org.eclipse.jgit.ssh.apache;bundle-version="[6.0.0,6.1.0)" +Bundle-ActivationPolicy: lazy +Automatic-Module-Name: org.eclipse.jgit.ssh.apache.agent +Bundle-RequiredExecutionEnvironment: JavaSE-11 +Import-Package: org.eclipse.jgit.transport.sshd;version="[6.0.0,6.1.0)", + org.eclipse.jgit.nls;version="[6.0.0,6.1.0)", + org.eclipse.jgit.util;version="[6.0.0,6.1.0)" +Require-Bundle: com.sun.jna;bundle-version="[5.8.0,6.0.0)", + com.sun.jna.platform;bundle-version="[5.8.0,6.0.0)" +Export-Package: org.eclipse.jgit.internal.transport.sshd.agent.connector;version="6.0.0";x-internal:=true diff --git a/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF new file mode 100644 index 000000000..afad57084 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/META-INF/SOURCE-MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: org.eclipse.jgit.ssh.apache.agent - Sources +Bundle-SymbolicName: org.eclipse.jgit.ssh.apache.agent.source +Bundle-Vendor: Eclipse.org - JGit +Bundle-Version: 6.0.0.qualifier +Eclipse-SourceBundle: org.eclipse.jgit.ssh.apache.agent;version="6.0.0.qualifier";roots="." diff --git a/org.eclipse.jgit.ssh.apache.agent/about.html b/org.eclipse.jgit.ssh.apache.agent/about.html new file mode 100644 index 000000000..f971af18d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/about.html @@ -0,0 +1,96 @@ + + + + + + +Eclipse Distribution License - Version 1.0 + + + + + + +

Eclipse Distribution License - v 1.0

+ +

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

+ +

All rights reserved.

+

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, + 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 + 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 + 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 +POSSIBILITY OF SUCH DAMAGE.

+ +
+

SHA-1 UbcCheck - 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: +

+
  • 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.ssh.apache.agent/build.properties b/org.eclipse.jgit.ssh.apache.agent/build.properties new file mode 100644 index 000000000..8148271ef --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/build.properties @@ -0,0 +1,7 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + about.html diff --git a/org.eclipse.jgit.ssh.apache.agent/plugin.properties b/org.eclipse.jgit.ssh.apache.agent/plugin.properties new file mode 100644 index 000000000..86df8f2e7 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/plugin.properties @@ -0,0 +1,2 @@ +Bundle-Name=JGit Unix SSH agent client for Apache MINA sshd +Bundle-Vendor=Eclipse JGit diff --git a/org.eclipse.jgit.ssh.apache.agent/pom.xml b/org.eclipse.jgit.ssh.apache.agent/pom.xml new file mode 100644 index 000000000..0b5267a40 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/pom.xml @@ -0,0 +1,227 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 6.0.0-SNAPSHOT + + + org.eclipse.jgit.ssh.apache.agent + JGit - Apache sshd SSH agent support + + + Support for ssh-agent for the Apache MINA sshd SSH connector + + + + 5.8.0 + + ${project.build.directory}/META-INF/SOURCE-MANIFEST.MF + + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache + ${project.version} + + + + net.java.dev.jna + jna + ${jna-version} + + + + net.java.dev.jna + jna-platform + ${jna-version} + + + + org.slf4j + slf4j-api + + + + + src/ + + + + . + + plugin.properties + about.html + + + + resources/ + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + translate-source-qualifier + generate-resources + + + + + + + + + + run + + + + + + + org.apache.maven.plugins + maven-source-plugin + true + + + attach-sources + process-classes + + jar + + + + ${source-bundle-manifest} + + + + + + + + maven-jar-plugin + + + ${bundle-manifest} + + + + + + + + + + diff --git a/org.eclipse.jgit.ssh.apache.agent/resources/META-INF/services/org.eclipse.jgit.transport.sshd.agent.ConnectorFactory b/org.eclipse.jgit.ssh.apache.agent/resources/META-INF/services/org.eclipse.jgit.transport.sshd.agent.ConnectorFactory new file mode 100644 index 000000000..538888c77 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/resources/META-INF/services/org.eclipse.jgit.transport.sshd.agent.ConnectorFactory @@ -0,0 +1 @@ +org.eclipse.jgit.internal.transport.sshd.agent.connector.Factory diff --git a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties new file mode 100644 index 000000000..f884adc08 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties @@ -0,0 +1,16 @@ +errCloseMappedFile=Cannot close mapped file: {0} - {1} +errLastError=System message for error {0} could not be retrieved, got {1} +errReleaseSharedMemory=Cannot release shared memory: {0} - {1} +errUnknown=unknown error +logErrorLoadLibrary=Cannot load socket library; SSH agent support is switched off +msgCloseFailed=Cannot close SSH agent socket {0} +msgConnectFailed=Could not connect to SSH agent via socket ''{0}'' +msgNoMappedFile=Could not create file mapping: {0} - {1} +msgNoSharedMemory=Could not initialize shared memory: {0} - {1} +msgPageantUnavailable=Could not connect to Pageant +msgReadFailed=Reading {0} bytes from the SSH agent failed +msgSendFailed=Sending {0} bytes to SSH agent failed; {0} bytes not written +msgSendFailed2=Sending {0} bytes to SSH agent failed: {1} - {2} +msgSharedMemoryFailed=Could not set up shared memory for communicating with Pageant +msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2} + diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java new file mode 100644 index 000000000..1cbf0e41b --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java @@ -0,0 +1,44 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.transport.sshd.agent.Connector; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; +import org.eclipse.jgit.util.SystemReader; + +/** + * An {@link ConnectorFactory} for connecting to an OpenSSH SSH agent. + */ +public class Factory implements ConnectorFactory { + + private static final String NAME = "jgit-builtin"; //$NON-NLS-1$ + + @Override + public Connector create(String identityAgent, File homeDir) + throws IOException { + if (SystemReader.getInstance().isWindows()) { + return new PageantConnector(); + } + return new UnixDomainSocketConnector(identityAgent); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java new file mode 100644 index 000000000..b09b55f81 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java @@ -0,0 +1,72 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import java.text.MessageFormat; + +import com.sun.jna.LastErrorException; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.Kernel32Util; +import com.sun.jna.platform.win32.User32; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Delay loading the native libraries until needed. + */ +class LibraryHolder { + + private static final Logger LOG = LoggerFactory + .getLogger(LibraryHolder.class); + + private static LibraryHolder INSTANCE; + + private static boolean libraryLoaded = false; + + public static synchronized LibraryHolder getLibrary() { + if (!libraryLoaded) { + libraryLoaded = true; + try { + INSTANCE = new LibraryHolder(); + } catch (Exception | UnsatisfiedLinkError + | NoClassDefFoundError e) { + LOG.error(Texts.get().logErrorLoadLibrary, e); + } + } + return INSTANCE; + } + + User32 user; + + Kernel32 kernel; + + private LibraryHolder() { + user = User32.INSTANCE; + kernel = Kernel32.INSTANCE; + } + + String systemError(String pattern) { + int lastError = kernel.GetLastError(); + String msg; + try { + msg = Kernel32Util.formatMessageFromLastErrorCode(lastError); + } catch (Exception e) { + String err = e instanceof LastErrorException + ? Integer.toString(((LastErrorException) e).getErrorCode()) + : Texts.get().errUnknown; + msg = MessageFormat.format(Texts.get().errLastError, + Integer.toString(lastError), err); + LOG.error(msg, e); + } + return MessageFormat.format(pattern, Integer.toString(lastError), msg); + } + +} \ No newline at end of file diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java new file mode 100644 index 000000000..59fe2fc24 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.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.internal.transport.sshd.agent.connector; + +import java.io.IOException; + +import org.eclipse.jgit.transport.sshd.agent.AbstractConnector; + +/** + * A connector using Pageant's shared memory IPC mechanism. + */ +public class PageantConnector extends AbstractConnector { + + private final PageantLibrary lib; + + /** + * Creates a new {@link PageantConnector}. + */ + public PageantConnector() { + super(); // Use default maximum message size + this.lib = new PageantLibrary(); + } + + @Override + public boolean connect() throws IOException { + return lib.isPageantAvailable(); + } + + @Override + public void close() throws IOException { + // Nothing to do + } + + @Override + public byte[] rpc(byte command, byte[] message) throws IOException { + try (PageantLibrary.Pipe pipe = lib + .createPipe(getClass().getSimpleName(), + getMaximumMessageLength())) { + prepareMessage(command, message); + pipe.send(message); + byte[] lengthBuf = new byte[4]; + pipe.receive(lengthBuf); + int length = toLength(command, lengthBuf); + byte[] payload = new byte[length]; + pipe.receive(payload); + return payload; + } + } +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantLibrary.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantLibrary.java new file mode 100644 index 000000000..9a30d804e --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantLibrary.java @@ -0,0 +1,240 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinBase; +import com.sun.jna.platform.win32.WinDef.HWND; +import com.sun.jna.platform.win32.WinDef.LPARAM; +import com.sun.jna.platform.win32.WinDef.LRESULT; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.platform.win32.WinUser; + +/** + * The {@link PageantLibrary} encapsulates the shared memory access and provides + * a simple pipe abstraction. + */ +public final class PageantLibrary { + + private static final Logger LOG = LoggerFactory + .getLogger(PageantLibrary.class); + + /** Pageant's "class" and "window name". */ + private static final String PAGEANT = "Pageant"; //$NON-NLS-1$ + + /** + * Magic constant from Pageant; ID for the CopyStruct used in SendMessage. + * + * @see "random goop" + */ + private static final int PAGEANT_ID = 0x804e_50ba; + + /** + * Determines whether Pageant is currently running. + * + * @return {@code true} if Pageant is running, {@code false} otherwise + */ + boolean isPageantAvailable() { + LibraryHolder libs = LibraryHolder.getLibrary(); + if (libs == null) { + return false; + } + HWND window = libs.user.FindWindow(PAGEANT, PAGEANT); + return window != null && !window.equals(WinBase.INVALID_HANDLE_VALUE); + } + + /** + * An abstraction for a bi-directional pipe. + */ + interface Pipe extends Closeable { + + /** + * Send the given message. + * + * @param message + * to send + * @throws IOException + * on errors + */ + void send(byte[] message) throws IOException; + + /** + * Reads bytes from the pipe until {@code data} is full. + * + * @param data + * to read + * @throws IOException + * on errors + */ + void receive(byte[] data) throws IOException; + } + + /** + * Windows' COPYDATASTRUCT. Must be public for JNA. + */ + public static class CopyStruct extends Structure { + + /** Must be set the {@link #PAGEANT_ID}. */ + public int dwData = PAGEANT_ID; + + /** Data length; number of bytes in {@link #lpData}. */ + public long cbData; + + /** Points to {@link #cbData} bytes. */ + public Pointer lpData; + + @Override + protected List getFieldOrder() { + return List.of("dwData", "cbData", "lpData"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + + private static class PipeImpl implements Pipe { + + private final LibraryHolder libs; + + private final HWND window; + + private final byte[] name; + + private final HANDLE file; + + private final Pointer memory; + + private long readPos = 0; + + PipeImpl(LibraryHolder libs, HWND window, String name, HANDLE file, + Pointer memory) { + this.libs = libs; + this.window = window; + this.name = name.getBytes(StandardCharsets.US_ASCII); + this.file = file; + this.memory = memory; + } + + @Override + public void close() throws IOException { + PageantLibrary.close(libs, file, memory, false); + } + + private Pointer init(CopyStruct c) { + c.cbData = name.length + 1; + c.lpData = new Memory(c.cbData); + c.lpData.write(0, name, 0, name.length); + c.lpData.setByte(name.length, (byte) 0); + c.write(); + return c.getPointer(); + } + + @Override + public void send(byte[] message) throws IOException { + memory.write(0, message, 0, message.length); + CopyStruct c = new CopyStruct(); + Pointer p = init(c); + LRESULT result = libs.user.SendMessage(window, WinUser.WM_COPYDATA, + null, new LPARAM(Pointer.nativeValue(p))); + if (result == null || result.longValue() == 0) { + throw new IOException( + libs.systemError(Texts.get().msgSendFailed2)); + } + } + + @Override + public void receive(byte[] data) throws IOException { + // Relies on Pageant handling the request synchronously, i.e., + // SendMessage() above returning successfully only once Pageant + // has indeed written into the shared memory. + memory.read(readPos, data, 0, data.length); + readPos += data.length; + } + } + + /** + * Creates a new {@link Pipe}. + * + * @param name + * for the pipe + * @param maxSize + * maximum size for messages + * @return the {@link Pipe}, or {@code null} if none created + * @throws IOException on errors + */ + Pipe createPipe(String name, int maxSize) throws IOException { + LibraryHolder libs = LibraryHolder.getLibrary(); + if (libs == null) { + throw new IllegalStateException("Libraries were not loaded"); //$NON-NLS-1$ + } + HWND window = libs.user.FindWindow(PAGEANT, PAGEANT); + if (window == null || window.equals(WinBase.INVALID_HANDLE_VALUE)) { + throw new IOException(Texts.get().msgPageantUnavailable); + } + String fileName = name + libs.kernel.GetCurrentThreadId(); + HANDLE file = null; + Pointer sharedMemory = null; + try { + file = libs.kernel.CreateFileMapping(WinBase.INVALID_HANDLE_VALUE, + null, WinNT.PAGE_READWRITE, 0, maxSize, fileName); + if (file == null || file.equals(WinBase.INVALID_HANDLE_VALUE)) { + throw new IOException( + libs.systemError(Texts.get().msgNoMappedFile)); + } + sharedMemory = libs.kernel.MapViewOfFile(file, + WinNT.SECTION_MAP_WRITE, 0, 0, 0); + if (sharedMemory == null) { + throw new IOException( + libs.systemError(Texts.get().msgNoSharedMemory)); + } + return new PipeImpl(libs, window, fileName, file, sharedMemory); + } catch (IOException e) { + close(libs, file, sharedMemory, true); + throw e; + } catch (Throwable e) { + close(libs, file, sharedMemory, true); + throw new IOException(Texts.get().msgSharedMemoryFailed, e); + } + } + + private static void close(LibraryHolder libs, HANDLE file, Pointer memory, + boolean silent) throws IOException { + if (memory != null) { + if (!libs.kernel.UnmapViewOfFile(memory)) { + String msg = libs + .systemError(Texts.get().errReleaseSharedMemory); + if (silent) { + LOG.error(msg); + } else { + throw new IOException(msg); + } + } + } + if (file != null) { + if (!libs.kernel.CloseHandle(file)) { + String msg = libs.systemError(Texts.get().errCloseMappedFile); + if (silent) { + LOG.error(msg); + } else { + throw new IOException(msg); + } + } + } + } +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java new file mode 100644 index 000000000..3d95bdb51 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Sockets.java @@ -0,0 +1,78 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import java.nio.charset.Charset; + +import com.sun.jna.Structure; +import com.sun.jna.Structure.FieldOrder; + +/** + * Common things for socket communication. + */ +public final class Sockets { + + private Sockets() { + // No instantiation + } + + /** + * Default SSH agent socket environment variable name. + */ + public static final String ENV_SSH_AUTH_SOCK = "SSH_AUTH_SOCK"; //$NON-NLS-1$ + + /** + * Domain for Unix domain sockets. + */ + public static final int AF_UNIX = 1; + + /** + * Socket type for duplex sockets. + */ + public static final int SOCK_STREAM = 1; + + /** + * Default protocol selector. + */ + public static final int DEFAULT_PROTOCOL = 0; + + /** + * Very simple representation of the C SockAddr type. + */ + @FieldOrder(value = { "sa_family", "sa_data" }) + public static class SockAddr extends Structure { + // This is a "variable length struct" in C. + + // Why 108 is apparently lost in time. But the file path for a Unix + // domain socket cannot be longer (including the terminating NUL). + private static final int MAX_DATA_LENGTH = 108; + + /** Socket family */ + public short sa_family = AF_UNIX; + + /** Unix domain socket path. */ + public byte[] sa_data = new byte[MAX_DATA_LENGTH]; + + /** + * Creates a new {@link SockAddr} for the given {@code path}. + * + * @param path + * for the Socket + * @param encoding + * to use to decode the {@code path} to a byte sequence + */ + public SockAddr(String path, Charset encoding) { + byte[] bytes = path.getBytes(encoding); + int toCopy = Math.min(sa_data.length - 1, bytes.length); + System.arraycopy(bytes, 0, sa_data, 0, toCopy); + sa_data[toCopy] = 0; + } + } +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java new file mode 100644 index 000000000..6b6626154 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.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.internal.transport.sshd.agent.connector; + +import org.eclipse.jgit.nls.NLS; +import org.eclipse.jgit.nls.TranslationBundle; + +/** + * Externalized text messages for localization. + */ +public final class Texts extends TranslationBundle { + + /** + * Get an instance of this translation bundle. + * + * @return an instance of this translation bundle + */ + public static Texts get() { + return NLS.getBundleFor(Texts.class); + } + + // @formatter:off + /***/ public String errCloseMappedFile; + /***/ public String errLastError; + /***/ public String errReleaseSharedMemory; + /***/ public String errUnknown; + /***/ public String logErrorLoadLibrary; + /***/ public String msgCloseFailed; + /***/ public String msgConnectFailed; + /***/ public String msgNoMappedFile; + /***/ public String msgNoSharedMemory; + /***/ public String msgPageantUnavailable; + /***/ public String msgReadFailed; + /***/ public String msgSendFailed; + /***/ public String msgSendFailed2; + /***/ public String msgSharedMemoryFailed; + /***/ public String msgShortRead; + +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java new file mode 100644 index 000000000..4c698d974 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java @@ -0,0 +1,218 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.AF_UNIX; +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.DEFAULT_PROTOCOL; +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.ENV_SSH_AUTH_SOCK; +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.Sockets.SOCK_STREAM; +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixSockets.FD_CLOEXEC; +import static org.eclipse.jgit.internal.transport.sshd.agent.connector.UnixSockets.F_SETFD; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.common.SshException; +import org.eclipse.jgit.transport.sshd.agent.AbstractConnector; +import org.eclipse.jgit.util.StringUtils; +import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Native; +import com.sun.jna.platform.unix.LibCAPI; + +/** + * JNA-based implementation of communication through a Unix domain socket. + */ +public class UnixDomainSocketConnector extends AbstractConnector { + + private static final Logger LOG = LoggerFactory + .getLogger(UnixDomainSocketConnector.class); + + private static UnixSockets library; + + private static boolean libraryLoaded = false; + + private static synchronized UnixSockets getLibrary() { + if (!libraryLoaded) { + libraryLoaded = true; + try { + library = Native.load(UnixSockets.LIBRARY_NAME, UnixSockets.class); + } catch (Exception | UnsatisfiedLinkError + | NoClassDefFoundError e) { + LOG.error(Texts.get().logErrorLoadLibrary, e); + } + } + return library; + } + + private final String socketFile; + + private AtomicBoolean connected = new AtomicBoolean(); + + private volatile int socketFd = -1; + + /** + * Creates a new instance. + * + * @param socketFile + * to use; if {@code null} or empty, use environment variable + * SSH_AUTH_SOCK + */ + public UnixDomainSocketConnector(String socketFile) { + super(); + String file = socketFile; + if (StringUtils.isEmptyOrNull(file)) { + file = SystemReader.getInstance().getenv(ENV_SSH_AUTH_SOCK); + } + this.socketFile = file; + } + + @Override + public boolean connect() throws IOException { + if (StringUtils.isEmptyOrNull(socketFile)) { + return false; + } + int fd = socketFd; + synchronized (this) { + if (connected.get()) { + return true; + } + UnixSockets sockets = getLibrary(); + if (sockets == null) { + return false; + } + try { + fd = sockets.socket(AF_UNIX, SOCK_STREAM, DEFAULT_PROTOCOL); + // OS X apparently doesn't have SOCK_CLOEXEC, so we can't set it + // atomically. Set it via fcntl, which exists on all systems + // we're interested in. + sockets.fcntl(fd, F_SETFD, FD_CLOEXEC); + Sockets.SockAddr sockAddr = new Sockets.SockAddr(socketFile, + StandardCharsets.UTF_8); + sockets.connect(fd, sockAddr, sockAddr.size()); + connected.set(true); + } catch (LastErrorException e) { + if (fd >= 0) { + try { + sockets.close(fd); + } catch (LastErrorException e1) { + e.addSuppressed(e1); + } + } + throw new IOException(MessageFormat + .format(Texts.get().msgConnectFailed, socketFile), e); + } + } + socketFd = fd; + return connected.get(); + } + + @Override + public synchronized void close() throws IOException { + int fd = socketFd; + if (connected.getAndSet(false) && fd >= 0) { + socketFd = -1; + try { + getLibrary().close(fd); + } catch (LastErrorException e) { + throw new IOException(MessageFormat.format( + Texts.get().msgCloseFailed, Integer.toString(fd)), e); + } + } + } + + @Override + public byte[] rpc(byte command, byte[] message) throws IOException { + prepareMessage(command, message); + int fd = socketFd; + if (!connected.get() || fd < 0) { + // No translation, internal error + throw new IllegalStateException("Not connected to SSH agent"); //$NON-NLS-1$ + } + writeFully(fd, message); + // Now receive the reply + byte[] lengthBuf = new byte[4]; + readFully(fd, lengthBuf); + int length = toLength(command, lengthBuf); + byte[] payload = new byte[length]; + readFully(fd, payload); + return payload; + } + + private void writeFully(int fd, byte[] message) throws IOException { + int toWrite = message.length; + try { + byte[] buf = message; + while (toWrite > 0) { + int written = getLibrary() + .write(fd, buf, new LibCAPI.size_t(buf.length)) + .intValue(); + if (written < 0) { + throw new IOException( + MessageFormat.format(Texts.get().msgSendFailed, + Integer.toString(message.length), + Integer.toString(toWrite))); + } + toWrite -= written; + if (written > 0 && toWrite > 0) { + buf = Arrays.copyOfRange(buf, written, buf.length); + } + } + } catch (LastErrorException e) { + throw new IOException( + MessageFormat.format(Texts.get().msgSendFailed, + Integer.toString(message.length), + Integer.toString(toWrite)), + e); + } + } + + private void readFully(int fd, byte[] data) throws IOException { + int n = 0; + int offset = 0; + while (offset < data.length + && (n = read(fd, data, offset, data.length - offset)) > 0) { + offset += n; + } + if (offset < data.length) { + throw new SshException( + MessageFormat.format(Texts.get().msgShortRead, + Integer.toString(data.length), + Integer.toString(offset), Integer.toString(n))); + } + } + + private int read(int fd, byte[] buffer, int offset, int length) + throws IOException { + try { + LibCAPI.size_t toRead = new LibCAPI.size_t(length); + if (offset == 0) { + return getLibrary().read(fd, buffer, toRead).intValue(); + } + byte[] data = new byte[length]; + int read = getLibrary().read(fd, data, toRead).intValue(); + if (read > 0) { + System.arraycopy(data, 0, buffer, offset, read); + } + return read; + } catch (LastErrorException e) { + throw new IOException( + MessageFormat.format(Texts.get().msgReadFailed, + Integer.toString(length)), + e); + } + } +} diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixSockets.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixSockets.java new file mode 100644 index 000000000..6f8153d00 --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixSockets.java @@ -0,0 +1,122 @@ +/* + * 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.internal.transport.sshd.agent.connector; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Library; +import com.sun.jna.Structure; +import com.sun.jna.platform.unix.LibCAPI; + +/** + * Low-level Unix/Linux JNA socket API. + */ +interface UnixSockets extends LibCAPI, Library { + + /** + * Library to load. These functions live in libc. + */ + String LIBRARY_NAME = "c"; //$NON-NLS-1$ + + /** + * Command to set the close-on-exec flag on a file descriptor via + * {@link #fcntl(int, int, int)}. + */ + int F_SETFD = 2; + + /** + * Specifies that a file descriptor shall not be inherited by child + * processes. + */ + int FD_CLOEXEC = 1; + + /** + * Creates a socket and returns a file descriptor for it. + * + * @param domain + * socket domain; use {@link Sockets#AF_UNIX} + * @param type + * socket type; use {@link Sockets#SOCK_STREAM} + * @param protocol + * socket communication protocol; use + * {@link Sockets#DEFAULT_PROTOCOL}. + * @return file descriptor for the socket; should be closed eventually, or + * -1 on error. + * @throws LastErrorException + * on errors + * @see LibCAPI#close(int) + */ + int socket(int domain, int type, int protocol) throws LastErrorException; + + /** + * Simple binding to fcntl; used to set the FD_CLOEXEC flag. On OS X, we + * cannot include SOCK_CLOEXEC in the socket() call. + * + * @param fd + * file descriptor to operate on + * @param command + * set to {@link #F_SETFD} + * @param flag + * zero to clear the close-on-exec flag, {@link #FD_CLOEXEC} to + * set it + * @return -1 on error, otherwise a value >= 0 + * @throws LastErrorException + */ + int fcntl(int fd, int command, int flag) throws LastErrorException; + + /** + * Connects a file descriptor, which must refer to a socket, to a + * {@link Sockets.SockAddr}. + * + * @param fd + * file descriptor of the socket, as returned by + * {@link #socket(int, int, int)} + * @param addr + * address to connect to + * @param addrLen + * Length of {@code addr}, use {@link Structure#size()} + * @return 0 on success; -1 otherwise + * @throws LastErrorException + * on errors + */ + int connect(int fd, Sockets.SockAddr addr, int addrLen) + throws LastErrorException; + + /** + * Read data from a file descriptor. + * + * @param fd + * file descriptor to read from + * @param buf + * buffer to read into + * @param bufLen + * maximum number of bytes to read; at most length of {@code buf} + * @return number of bytes actually read; zero for EOF, -1 on error + * @throws LastErrorException + * on errors + */ + LibCAPI.ssize_t read(int fd, byte[] buf, LibCAPI.size_t bufLen) + throws LastErrorException; + + /** + * Write data to a file descriptor. + * + * @param fd + * file descriptor to write to + * @param data + * data to write + * @param dataLen + * number of bytes to write + * @return number of bytes actually written; -1 on error + * @throws LastErrorException + * on errors + */ + LibCAPI.ssize_t write(int fd, byte[] data, LibCAPI.size_t dataLen) + throws LastErrorException; +} 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 b9a7ba9dc..e5a66f1ee 100644 --- a/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache.test/META-INF/MANIFEST.MF @@ -29,6 +29,7 @@ Import-Package: org.apache.sshd.client.config.hosts;version="[2.7.0,2.8.0)", org.eclipse.jgit.lib;version="[6.0.0,6.1.0)", org.eclipse.jgit.transport;version="[6.0.0,6.1.0)", org.eclipse.jgit.transport.sshd;version="[6.0.0,6.1.0)", + org.eclipse.jgit.transport.sshd.agent;version="[6.0.0,6.1.0)", org.eclipse.jgit.util;version="[6.0.0,6.1.0)", org.hamcrest;version="[1.1.0,3.0.0)", org.junit;version="[4.13,5.0.0)", diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshProtocol2Test.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshProtocol2Test.java index 0ad96b9ac..eef0402b0 100644 --- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshProtocol2Test.java +++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/ApacheSshProtocol2Test.java @@ -26,12 +26,15 @@ public class ApacheSshProtocol2Test extends SshBasicTestBase { @Override protected SshSessionFactory createSessionFactory() { - SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(), - null); - // The home directory is mocked at this point! - result.setHomeDirectory(FS.DETECTED.userHome()); - result.setSshDirectory(sshDir); - return result; + return new SshdSessionFactoryBuilder() + // No proxies in tests + .setProxyDataFactory(null) + // No ssh-agent in tests + .setConnectorFactory(null) + // The home directory is mocked at this point! + .setHomeDirectory(FS.DETECTED.userHome()) + .setSshDirectory(sshDir) + .build(new JGitKeyCache()); } @Override 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 c1f5fef3c..85626d8ee 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 @@ -65,12 +65,15 @@ public class ApacheSshTest extends SshTestBase { @Override protected SshSessionFactory createSessionFactory() { - SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(), - null); - // The home directory is mocked at this point! - result.setHomeDirectory(FS.DETECTED.userHome()); - result.setSshDirectory(sshDir); - return result; + return new SshdSessionFactoryBuilder() + // No proxies in tests + .setProxyDataFactory(null) + // No ssh-agent in tests + .setConnectorFactory(null) + // The home directory is mocked at this point! + .setHomeDirectory(FS.DETECTED.userHome()) + .setSshDirectory(sshDir) + .build(new JGitKeyCache()); } @Override diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshBuilderTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshBuilderTest.java index 9d64adc95..fd51e0cf4 100644 --- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshBuilderTest.java +++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshBuilderTest.java @@ -50,6 +50,10 @@ public class NoFilesSshBuilderTest extends SshTestHarness { @Override protected SshSessionFactory createSessionFactory() { return new SshdSessionFactoryBuilder() // + // No proxies in tests + .setProxyDataFactory(null) + // No ssh-agent in tests + .setConnectorFactory(null) .setConfigStoreFactory((h, f, u) -> null) .setDefaultKeysProvider(f -> new KeyAuthenticator()) .setServerKeyDatabase((h, s) -> new ServerKeyDatabase() { diff --git a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java index 7b6e508c3..04c1c605d 100644 --- a/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java +++ b/org.eclipse.jgit.ssh.apache.test/tst/org/eclipse/jgit/transport/sshd/NoFilesSshTest.java @@ -33,6 +33,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.FS; import org.junit.After; import org.junit.Test; @@ -80,6 +81,12 @@ public boolean accept(String connectAddress, }; } + @Override + protected ConnectorFactory getConnectorFactory() { + // No ssh-agent in tests + return null; + } + @Override protected Iterable getDefaultKeys(File dir) { // This would work for this simple test case: diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java index 08da18f5a..c082a9a96 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/JGitPublicKeyAuthentication.java @@ -12,8 +12,12 @@ import static java.text.MessageFormat.format; import static org.eclipse.jgit.transport.SshConstants.PUBKEY_ACCEPTED_ALGORITHMS; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import org.apache.sshd.client.auth.pubkey.KeyAgentIdentity; +import org.apache.sshd.client.auth.pubkey.PublicKeyIdentity; import org.apache.sshd.client.auth.pubkey.UserAuthPublicKey; import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.session.ClientSession; @@ -38,7 +42,7 @@ public void init(ClientSession rawSession, String service) throw new IllegalStateException("Wrong session type: " //$NON-NLS-1$ + rawSession.getClass().getCanonicalName()); } - JGitClientSession session = ((JGitClientSession) rawSession); + JGitClientSession session = (JGitClientSession) rawSession; HostConfigEntry hostConfig = session.getHostConfigEntry(); // Set signature algorithms for public key authentication String pubkeyAlgos = hostConfig.getProperty(PUBKEY_ACCEPTED_ALGORITHMS); @@ -60,5 +64,48 @@ public void init(ClientSession rawSession, String service) // If we don't set signature factories here, the default ones from the // session will be used. super.init(session, service); + // In sshd 2.7.0, we end up now with a key iterator that uses keys + // provided by an ssh-agent even if IdentitiesOnly is true. So if + // needed, filter out any KeyAgentIdentity. + if (hostConfig.isIdentitiesOnly()) { + Iterator original = keys; + // The original iterator will already have gotten the identities + // from the agent. Unfortunately there's nothing we can do about + // that; it'll have to be fixed upstream. (As will, ultimately, + // respecting isIdentitiesOnly().) At least we can simply not + // use the keys the agent provided. + // + // See https://issues.apache.org/jira/browse/SSHD-1218 + keys = new Iterator<>() { + + private PublicKeyIdentity value; + + @Override + public boolean hasNext() { + if (value != null) { + return true; + } + PublicKeyIdentity next = null; + while (original.hasNext()) { + next = original.next(); + if (!(next instanceof KeyAgentIdentity)) { + value = next; + return true; + } + } + return false; + } + + @Override + public PublicKeyIdentity next() { + if (hasNext()) { + PublicKeyIdentity result = value; + value = null; + return result; + } + throw new NoSuchElementException(); + } + }; + } } } 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 da99f56cb..336418009 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 @@ -63,6 +63,7 @@ import org.eclipse.jgit.transport.SshConstants; import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.sshd.agent.Connector; import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; import org.eclipse.jgit.util.FS; @@ -105,9 +106,9 @@ public SshdSessionFactory() { /** * Creates a new {@link SshdSessionFactory} using the given {@link KeyCache} - * and {@link ProxyDataFactory}. The {@code keyCache} is used for all sessions - * created through this session factory; cached keys are destroyed when the - * session factory is {@link #close() closed}. + * and {@link ProxyDataFactory}. The {@code keyCache} is used for all + * sessions created through this session factory; cached keys are destroyed + * when the session factory is {@link #close() closed}. *

* Caching ssh keys in memory for an extended period of time is generally * considered bad practice, but there may be circumstances where using a @@ -117,13 +118,21 @@ public SshdSessionFactory() { * to use a {@link #createKeyPasswordProvider(CredentialsProvider) * KeyPasswordProvider} that has access to some secure storage and can save * and retrieve passwords from there without user interaction. Another - * approach is to use an ssh agent. + * approach is to use an SSH agent. *

*

* Note that the underlying ssh library (Apache MINA sshd) may or may not * keep ssh keys in memory for unspecified periods of time irrespective of * the use of a {@link KeyCache}. *

+ *

+ * By default, the factory uses the {@link java.util.ServiceLoader} to find + * a {@link ConnectorFactory} for creating a {@link Connector} to connect to + * a running SSH agent. If it finds one, the SSH agent is used in publickey + * authentication. If there is none, no SSH agent will ever be contacted. + * Note that one can define {@code IdentitiesOnly yes} for a host entry in + * the {@code ~/.ssh/config} file to bypass the SSH agent in any case. + *

* * @param keyCache * {@link KeyCache} to use for caching ssh keys, or {@code null} diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java index b6da0866a..d8dfbfc94 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/Connector.java @@ -16,6 +16,7 @@ * Simple interface for connecting to something and making RPC-style * request-reply calls. * + * @see ConnectorFactory * @since 6.0 */ public interface Connector extends Closeable { diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java index fa725ab85..b351d89ef 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java @@ -15,7 +15,10 @@ import org.eclipse.jgit.annotations.NonNull; /** - * A factory for creating {@link Connector}s. + * A factory for creating {@link Connector}s. This is a service provider + * interface; implementations are discovered via the + * {@link java.util.ServiceLoader}, or can be set explicitly on a + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. * * @since 6.0 */ diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java new file mode 100644 index 000000000..71ca43f3d --- /dev/null +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/package-info.java @@ -0,0 +1,6 @@ +/** + * Service provider interfaces for connecting to an SSH agent. Implementations + * are discovered via the {@link java.util.ServiceLoader}, or can be set + * explicitly on a {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + */ +package org.eclipse.jgit.transport.sshd.agent; diff --git a/pom.xml b/pom.xml index 0833c7150..11789693f 100644 --- a/pom.xml +++ b/pom.xml @@ -925,6 +925,7 @@ org.eclipse.jgit.http.apache org.eclipse.jgit.http.server org.eclipse.jgit.ssh.apache + org.eclipse.jgit.ssh.apache.agent org.eclipse.jgit.ssh.jsch org.eclipse.jgit.pgm org.eclipse.jgit.lfs diff --git a/tools/BUILD b/tools/BUILD index 4769f4222..0c6b8ece7 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -121,6 +121,7 @@ package_group( "//org.eclipse.jgit.pgm.test/...", "//org.eclipse.jgit.pgm/...", "//org.eclipse.jgit.ssh.apache/...", + "//org.eclipse.jgit.ssh.apache.agent/...", "//org.eclipse.jgit.test/...", "//org.eclipse.jgit.ui/...", "//org.eclipse.jgit/...", From c6d48ab2f8ac776eb0eb8b385a869d9c0ab9dbf8 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Wed, 13 Oct 2021 22:21:52 +0200 Subject: [PATCH 10/24] [test] test OpenSshConfigFile directly, not via the JSch config This is a prerequisite for removing the JSch support bundle; otherwise OpenSshConfigFile would be left without tests. Copy OpenSshConfigTest from the JSch support bundle and adapt all tests to perform the equivalent checks on OpenSshConfigFile directly. Add a new lookupDefault() method to the SshConfigStore interface and implement it so that it behaves the same and the tests work identically. Change-Id: I046abd9197a8484003e77005024e5d973456f1a3 --- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../transport/ssh/OpenSshConfigFileTest.java | 605 ++++++++++++++++++ org.eclipse.jgit/META-INF/MANIFEST.MF | 3 +- .../transport/ssh/OpenSshConfigFile.java | 38 +- .../jgit/transport/SshConfigStore.java | 42 ++ 5 files changed, 683 insertions(+), 6 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index b550bea59..91f8f74b6 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -43,6 +43,7 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.eclipse.jgit.internal.transport.connectivity;version="[6.0.0,6.1.0)", org.eclipse.jgit.internal.transport.http;version="[6.0.0,6.1.0)", org.eclipse.jgit.internal.transport.parser;version="[6.0.0,6.1.0)", + org.eclipse.jgit.internal.transport.ssh;version="[6.0.0,6.1.0)", org.eclipse.jgit.junit;version="[6.0.0,6.1.0)", org.eclipse.jgit.junit.time;version="[6.0.0,6.1.0)", org.eclipse.jgit.lfs;version="[6.0.0,6.1.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java new file mode 100644 index 000000000..27bae3747 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2008, 2021 Google Inc. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.internal.transport.ssh; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.transport.SshConfigStore.HostConfig; +import org.eclipse.jgit.transport.SshConstants; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.SystemReader; +import org.junit.Before; +import org.junit.Test; + +public class OpenSshConfigFileTest extends RepositoryTestCase { + + private File home; + + private File configFile; + + private OpenSshConfigFile osc; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + home = new File(trash, "home"); + FileUtils.mkdir(home); + + configFile = new File(new File(home, ".ssh"), Constants.CONFIG); + FileUtils.mkdir(configFile.getParentFile()); + + mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit"); + mockSystemReader.setProperty("TST_VAR", "TEST"); + osc = new OpenSshConfigFile(home, configFile, "jex_junit"); + } + + private void config(String data) throws IOException { + FS fs = FS.DETECTED; + long resolution = FS.getFileStoreAttributes(configFile.toPath()) + .getFsTimestampResolution().toNanos(); + Instant lastMtime = fs.lastModifiedInstant(configFile); + do { + try (final OutputStreamWriter fw = new OutputStreamWriter( + new FileOutputStream(configFile), UTF_8)) { + fw.write(data); + TimeUnit.NANOSECONDS.sleep(resolution); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } while (lastMtime.equals(fs.lastModifiedInstant(configFile))); + } + + private HostConfig lookup(String hostname) { + return osc.lookupDefault(hostname, 0, null); + } + + private void assertHost(String expected, HostConfig h) { + assertEquals(expected, h.getValue(SshConstants.HOST_NAME)); + } + + private void assertUser(String expected, HostConfig h) { + assertEquals(expected, h.getValue(SshConstants.USER)); + } + + private void assertPort(int expected, HostConfig h) { + assertEquals(expected, + OpenSshConfigFile.positive(h.getValue(SshConstants.PORT))); + } + + private void assertIdentity(File expected, HostConfig h) { + String actual = h.getValue(SshConstants.IDENTITY_FILE); + if (expected == null) { + assertNull(actual); + } else { + assertEquals(expected, new File(actual)); + } + } + + private void assertAttempts(int expected, HostConfig h) { + assertEquals(expected, OpenSshConfigFile + .positive(h.getValue(SshConstants.CONNECTION_ATTEMPTS))); + } + + @Test + public void testNoConfig() { + final HostConfig h = lookup("repo.or.cz"); + assertNotNull(h); + assertHost("repo.or.cz", h); + assertUser("jex_junit", h); + assertPort(22, h); + assertAttempts(1, h); + assertIdentity(null, h); + } + + @Test + public void testSeparatorParsing() throws Exception { + config("Host\tfirst\n" + + "\tHostName\tfirst.tld\n" + + "\n" + + "Host second\n" + + " HostName\tsecond.tld\n" + + "Host=third\n" + + "HostName=third.tld\n\n\n" + + "\t Host = fourth\n\n\n" + + " \t HostName\t=fourth.tld\n" + + "Host\t = last\n" + + "HostName \t last.tld"); + assertNotNull(lookup("first")); + assertHost("first.tld", lookup("first")); + assertNotNull(lookup("second")); + assertHost("second.tld", lookup("second")); + assertNotNull(lookup("third")); + assertHost("third.tld", lookup("third")); + assertNotNull(lookup("fourth")); + assertHost("fourth.tld", lookup("fourth")); + assertNotNull(lookup("last")); + assertHost("last.tld", lookup("last")); + } + + @Test + public void testQuoteParsing() throws Exception { + config("Host \"good\"\n" + + " HostName=\"good.tld\"\n" + + " Port=\"6007\"\n" + + " User=\"gooduser\"\n" + + "Host multiple unquoted and \"quoted\" \"hosts\"\n" + + " Port=\"2222\"\n" + + "Host \"spaced\"\n" + + "# Bad host name, but testing preservation of spaces\n" + + " HostName=\" spaced\ttld \"\n" + + "# Misbalanced quotes\n" + + "Host \"bad\"\n" + + "# OpenSSH doesn't allow this but ...\n" + + " HostName=bad.tld\"\n"); + assertHost("good.tld", lookup("good")); + assertUser("gooduser", lookup("good")); + assertPort(6007, lookup("good")); + assertPort(2222, lookup("multiple")); + assertPort(2222, lookup("quoted")); + assertPort(2222, lookup("and")); + assertPort(2222, lookup("unquoted")); + assertPort(2222, lookup("hosts")); + assertHost(" spaced\ttld ", lookup("spaced")); + assertHost("bad.tld\"", lookup("bad")); + } + + @Test + public void testCaseInsensitiveKeyLookup() throws Exception { + config("Host orcz\n" + "Port 29418\n" + + "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n"); + final HostConfig c = lookup("orcz"); + String exactCase = c.getValue("StrictHostKeyChecking"); + assertEquals("yes", exactCase); + assertEquals(exactCase, c.getValue("stricthostkeychecking")); + assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING")); + assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING")); + assertNull(c.getValue("sTrIcThostKEYcheckIN")); + } + + @Test + public void testAlias_DoesNotMatch() throws Exception { + config("Host orcz\n" + "Port 29418\n" + + "\tHostName repo.or.cz\n"); + final HostConfig h = lookup("repo.or.cz"); + assertNotNull(h); + assertHost("repo.or.cz", h); + assertUser("jex_junit", h); + assertPort(22, h); + assertIdentity(null, h); + final HostConfig h2 = lookup("orcz"); + assertHost("repo.or.cz", h); + assertUser("jex_junit", h); + assertPort(29418, h2); + assertIdentity(null, h); + } + + @Test + public void testAlias_OptionsSet() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\tPort 2222\n" + + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertHost("repo.or.cz", h); + assertUser("jex", h); + assertPort(2222, h); + assertIdentity(new File(home, ".ssh/id_jex"), h); + } + + @Test + public void testAlias_OptionsKeywordCaseInsensitive() throws Exception { + config("hOsT orcz\n" + "\thOsTnAmE repo.or.cz\n" + "\tPORT 2222\n" + + "\tuser jex\n" + "\tidentityfile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertHost("repo.or.cz", h); + assertUser("jex", h); + assertPort(2222, h); + assertIdentity(new File(home, ".ssh/id_jex"), h); + } + + @Test + public void testAlias_OptionsInherit() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tHostName not.a.host.example.com\n" + "\tPort 2222\n" + + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertHost("repo.or.cz", h); + assertUser("jex", h); + assertPort(2222, h); + assertIdentity(new File(home, ".ssh/id_jex"), h); + } + + @Test + public void testAlias_PreferredAuthenticationsDefault() throws Exception { + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertNull(h.getValue(SshConstants.PREFERRED_AUTHENTICATIONS)); + } + + @Test + public void testAlias_PreferredAuthentications() throws Exception { + config("Host orcz\n" + "\tPreferredAuthentications publickey\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertEquals("publickey", + h.getValue(SshConstants.PREFERRED_AUTHENTICATIONS)); + } + + @Test + public void testAlias_InheritPreferredAuthentications() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tPreferredAuthentications publickey, hostbased\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertEquals("publickey,hostbased", + h.getValue(SshConstants.PREFERRED_AUTHENTICATIONS)); + } + + @Test + public void testAlias_BatchModeDefault() throws Exception { + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertNull(h.getValue(SshConstants.BATCH_MODE)); + } + + @Test + public void testAlias_BatchModeYes() throws Exception { + config("Host orcz\n" + "\tBatchMode yes\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertTrue(OpenSshConfigFile.flag(h.getValue(SshConstants.BATCH_MODE))); + } + + @Test + public void testAlias_InheritBatchMode() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tBatchMode yes\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertTrue(OpenSshConfigFile.flag(h.getValue(SshConstants.BATCH_MODE))); + } + + @Test + public void testAlias_ConnectionAttemptsDefault() throws Exception { + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(1, h); + } + + @Test + public void testAlias_ConnectionAttempts() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts 5\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(5, h); + } + + @Test + public void testAlias_invalidConnectionAttempts() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts -1\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(1, h); + } + + @Test + public void testAlias_badConnectionAttempts() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts xxx\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(1, h); + } + + @Test + public void testDefaultBlock() throws Exception { + config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(5, h); + } + + @Test + public void testHostCaseInsensitive() throws Exception { + config("hOsT orcz\nConnectionAttempts 3\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(3, h); + } + + @Test + public void testListValueSingle() throws Exception { + config("Host orcz\nUserKnownHostsFile /foo/bar\n"); + final HostConfig c = lookup("orcz"); + assertNotNull(c); + assertEquals("/foo/bar", c.getValue("UserKnownHostsFile")); + } + + @Test + public void testListValueMultiple() throws Exception { + // Tilde expansion occurs within the parser + config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n"); + final HostConfig c = lookup("orcz"); + assertNotNull(c); + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar" }, + c.getValues("UserKnownHostsFile").toArray()); + } + + @Test + public void testRepeatedLookupsWithModification() throws Exception { + config("Host orcz\n" + "\tConnectionAttempts -1\n"); + final HostConfig h1 = lookup("orcz"); + assertNotNull(h1); + assertAttempts(1, h1); + config("Host orcz\n" + "\tConnectionAttempts 5\n"); + final HostConfig h2 = lookup("orcz"); + assertNotNull(h2); + assertNotSame(h1, h2); + assertAttempts(5, h2); + assertAttempts(1, h1); + assertNotSame(h1, h2); + } + + @Test + public void testIdentityFile() throws Exception { + config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + // Does tilde replacement + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar" }, + h.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testMultiIdentityFile() throws Exception { + config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(), + "/foo/bar", "/foo/baz" }, + h.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testNegatedPattern() throws Exception { + config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz"); + final HostConfig h = lookup("repo.or.cz"); + assertNotNull(h); + assertIdentity(new File(home, "foo/bar"), h); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() }, + h.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testPattern() throws Exception { + config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz"); + final HostConfig h = lookup("repo.or.cz"); + assertNotNull(h); + assertIdentity(new File(home, "foo/bar"), h); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(), + "/foo/baz" }, + h.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testMultiHost() throws Exception { + config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz"); + final HostConfig h1 = lookup("repo.or.cz"); + assertNotNull(h1); + assertIdentity(new File(home, "foo/bar"), h1); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(), + "/foo/baz" }, + h1.getValues(SshConstants.IDENTITY_FILE).toArray()); + final HostConfig h2 = lookup("orcz"); + assertNotNull(h2); + assertIdentity(new File(home, "foo/bar"), h2); + assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() }, + h2.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testEqualsSign() throws Exception { + config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertAttempts(5, h); + assertUser("foobar", h); + } + + @Test + public void testMissingArgument() throws Exception { + config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertUser("foobar", h); + assertEquals("[]", h.getValues("SendEnv").toString()); + assertIdentity(null, h); + assertNull(h.getValue("ForwardX11")); + } + + @Test + public void testHomeDirUserReplacement() throws Exception { + config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertIdentity(new File(new File(home, ".ssh"), "jex_junit_id_dsa"), h); + } + + @Test + public void testHostnameReplacement() throws Exception { + config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertHost("orcz.example.org", h); + } + + @Test + public void testRemoteUserReplacement() throws Exception { + config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n" + + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertIdentity( + new File(new File(home, ".ssh"), + "orcz.ex%20ample.org_foo_id_dsa"), + h); + } + + @Test + public void testLocalhostFQDNReplacement() throws Exception { + String localhost = SystemReader.getInstance().getHostname(); + config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa"); + final HostConfig h = lookup("orcz"); + assertNotNull(h); + assertIdentity( + new File(new File(home, ".ssh"), localhost + "_id_dsa"), + h); + } + + @Test + public void testPubKeyAcceptedAlgorithms() throws Exception { + config("Host=orcz\n\tPubkeyAcceptedAlgorithms ^ssh-rsa"); + HostConfig h = lookup("orcz"); + assertEquals("^ssh-rsa", + h.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS)); + assertEquals("^ssh-rsa", h.getValue("PubkeyAcceptedKeyTypes")); + } + + @Test + public void testPubKeyAcceptedKeyTypes() throws Exception { + config("Host=orcz\n\tPubkeyAcceptedKeyTypes ^ssh-rsa"); + HostConfig h = lookup("orcz"); + assertEquals("^ssh-rsa", + h.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS)); + assertEquals("^ssh-rsa", h.getValue("PubkeyAcceptedKeyTypes")); + } + + @Test + public void testEolComments() throws Exception { + config("#Comment\nHost=orcz #Comment\n\tPubkeyAcceptedAlgorithms ^ssh-rsa # Comment\n#Comment"); + HostConfig h = lookup("orcz"); + assertNotNull(h); + assertEquals("^ssh-rsa", + h.getValue(SshConstants.PUBKEY_ACCEPTED_ALGORITHMS)); + } + + @Test + public void testEnVarSubstitution() throws Exception { + config("Host orcz\nIdentityFile /tmp/${TST_VAR}\n" + + "CertificateFile /tmp/${}/foo\nUser ${TST_VAR}\nIdentityAgent /tmp/${TST_VAR/bar"); + HostConfig h = lookup("orcz"); + assertNotNull(h); + assertEquals("/tmp/TEST", + h.getValue(SshConstants.IDENTITY_FILE)); + // No variable name + assertEquals("/tmp/${}/foo", h.getValue(SshConstants.CERTIFICATE_FILE)); + // User doesn't get env var substitution: + assertUser("${TST_VAR}", h); + // Unterminated: + assertEquals("/tmp/${TST_VAR/bar", + h.getValue(SshConstants.IDENTITY_AGENT)); + } + + @Test + public void testNegativeMatch() throws Exception { + config("Host foo.bar !foobar.baz *.baz\n" + "Port 29418\n"); + HostConfig h = lookup("foo.bar"); + assertNotNull(h); + assertPort(29418, h); + h = lookup("foobar.baz"); + assertNotNull(h); + assertPort(22, h); + h = lookup("foo.baz"); + assertNotNull(h); + assertPort(29418, h); + } + + @Test + public void testNegativeMatch2() throws Exception { + // Negative match after the positive match. + config("Host foo.bar *.baz !foobar.baz\n" + "Port 29418\n"); + HostConfig h = lookup("foo.bar"); + assertNotNull(h); + assertPort(29418, h); + h = lookup("foobar.baz"); + assertNotNull(h); + assertPort(22, h); + h = lookup("foo.baz"); + assertNotNull(h); + assertPort(29418, h); + } + + @Test + public void testNoMatch() throws Exception { + config("Host !host1 !host2\n" + "Port 29418\n"); + HostConfig h = lookup("host1"); + assertNotNull(h); + assertPort(22, h); + h = lookup("host2"); + assertNotNull(h); + assertPort(22, h); + h = lookup("host3"); + assertNotNull(h); + assertPort(22, h); + } + + @Test + public void testMultipleMatch() throws Exception { + config("Host foo.bar\nPort 29418\nIdentityFile /foo\n\n" + + "Host *.bar\nPort 22\nIdentityFile /bar\n" + + "Host foo.bar\nPort 47\nIdentityFile /baz\n"); + HostConfig h = lookup("foo.bar"); + assertNotNull(h); + assertPort(29418, h); + assertArrayEquals(new Object[] { "/foo", "/bar", "/baz" }, + h.getValues(SshConstants.IDENTITY_FILE).toArray()); + } + + @Test + public void testWhitespace() throws Exception { + config("Host foo \tbar baz\nPort 29418\n"); + HostConfig h = lookup("foo"); + assertNotNull(h); + assertPort(29418, h); + h = lookup("bar"); + assertNotNull(h); + assertPort(29418, h); + h = lookup("baz"); + assertNotNull(h); + assertPort(29418, h); + h = lookup("\tbar"); + assertNotNull(h); + assertPort(22, h); + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index a6f9e4814..590567fe7 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -111,7 +111,8 @@ Export-Package: org.eclipse.jgit.annotations;version="6.0.0", org.eclipse.jgit.test", org.eclipse.jgit.internal.transport.ssh;version="6.0.0"; x-friends:="org.eclipse.jgit.ssh.apache, - org.eclipse.jgit.ssh.jsch", + org.eclipse.jgit.ssh.jsch, + org.eclipse.jgit.test", org.eclipse.jgit.lib;version="6.0.0"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.util.sha1, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java index a7a143328..4cffcc5dd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java @@ -151,6 +151,18 @@ public OpenSshConfigFile(@NonNull File home, @NonNull File config, @NonNull public HostEntry lookup(@NonNull String hostName, int port, String userName) { + return lookup(hostName, port, userName, false); + } + + @Override + @NonNull + public HostEntry lookupDefault(@NonNull String hostName, int port, + String userName) { + return lookup(hostName, port, userName, true); + } + + private HostEntry lookup(@NonNull String hostName, int port, + String userName, boolean fillDefaults) { final State cache = refresh(); String cacheKey = toCacheKey(hostName, port, userName); HostEntry h = cache.hosts.get(cacheKey); @@ -169,7 +181,8 @@ public HostEntry lookup(@NonNull String hostName, int port, } }); } - fullConfig.substitute(hostName, port, userName, localUserName, home); + fullConfig.substitute(hostName, port, userName, localUserName, home, + fillDefaults); cache.hosts.put(cacheKey, fullConfig); return fullConfig; } @@ -725,12 +738,12 @@ private List replaceTilde(List values, File home) { } void substitute(String originalHostName, int port, String userName, - String localUserName, File home) { - int p = port >= 0 ? port : positive(getValue(SshConstants.PORT)); - if (p < 0) { + String localUserName, File home, boolean fillDefaults) { + int p = port > 0 ? port : positive(getValue(SshConstants.PORT)); + if (p <= 0) { p = SshConstants.SSH_DEFAULT_PORT; } - String u = userName != null && !userName.isEmpty() ? userName + String u = !StringUtils.isEmptyOrNull(userName) ? userName : getValue(SshConstants.USER); if (u == null || u.isEmpty()) { u = localUserName; @@ -747,6 +760,8 @@ void substitute(String originalHostName, int port, String userName, options.put(SshConstants.HOST_NAME, hostName); r.update('h', hostName); } + } else if (fillDefaults) { + setValue(SshConstants.HOST_NAME, originalHostName); } if (multiOptions != null) { List values = multiOptions @@ -803,6 +818,19 @@ void substitute(String originalHostName, int port, String userName, } // Match is not implemented and would need to be done elsewhere // anyway. + if (fillDefaults) { + String s = options.get(SshConstants.USER); + if (StringUtils.isEmptyOrNull(s)) { + options.put(SshConstants.USER, u); + } + if (positive(options.get(SshConstants.PORT)) <= 0) { + options.put(SshConstants.PORT, Integer.toString(p)); + } + if (positive( + options.get(SshConstants.CONNECTION_ATTEMPTS)) <= 0) { + options.put(SshConstants.CONNECTION_ATTEMPTS, "1"); //$NON-NLS-1$ + } + } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java index 04a4922bb..d98bd2307 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java @@ -36,6 +36,48 @@ public interface SshConfigStore { @NonNull HostConfig lookup(@NonNull String hostName, int port, String userName); + /** + * Locate the configuration for a specific host request and if the + * configuration has no values for {@link SshConstants#HOST_NAME}, + * {@link SshConstants#PORT}, {@link SshConstants#USER}, or + * {@link SshConstants#CONNECTION_ATTEMPTS}, fill those values with defaults + * from the arguments: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ssh config keyvalue from argument
{@code HostName}{@code hostName}
{@code Port}{@code port > 0 ? port : 22}
{@code User}{@code userName}
{@code ConnectionAttempts}{@code 1}
+ * + * @param hostName + * host name to look up + * @param port + * port number; <= 0 if none + * @param userName + * the user name, may be {@code null} or empty if none given + * @return the configuration for the requested name. + * @since 6.0 + */ + @NonNull + HostConfig lookupDefault(@NonNull String hostName, int port, + String userName); + /** * A host entry from the ssh config. Any merging of global values and of * several matching host entries, %-substitutions, and ~ replacement have From 3a7db8b7820a3ed3ad2e8b6be90beb9042618e74 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 31 Oct 2021 17:29:04 +0100 Subject: [PATCH 11/24] Simplify SshdFtpChannel Apache MINA sshd has simpler API for reading directories, and it has a functional interface suitable for us. So no need to use our own interface, or to deal with low-level abstractions like CloseableHandle. Change-Id: Ic125c587535670504983f157a696b41ed6a76bb7 Signed-off-by: Thomas Wolf --- .../META-INF/MANIFEST.MF | 1 + .../jgit/transport/sshd/SshdSession.java | 84 +++++++------------ 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF index 339ba3aae..3e5feeb84 100644 --- a/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ssh.apache/META-INF/MANIFEST.MF @@ -73,6 +73,7 @@ Import-Package: net.i2p.crypto.eddsa;version="[0.3.0,0.4.0)", org.apache.sshd.common.util.buffer;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.closeable;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.io;version="[2.7.0,2.8.0)", + org.apache.sshd.common.util.io.functors;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.io.resource;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.logging;version="[2.7.0,2.8.0)", org.apache.sshd.common.util.net;version="[2.7.0,2.8.0)", 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 33b234b1f..fb7500ffd 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, 2020 Thomas Wolf and others + * Copyright (C) 2018, 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 @@ -28,7 +28,6 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -44,9 +43,9 @@ import org.apache.sshd.common.future.CloseFuture; import org.apache.sshd.common.future.SshFutureListener; import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.functors.IOFunction; 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; @@ -416,20 +415,6 @@ public void destroy() { } } - /** - * Helper interface like {@link Supplier}, but possibly raising an - * {@link IOException}. - * - * @param - * return type - */ - @FunctionalInterface - private interface FtpOperation { - - T call() throws IOException; - - } - private class SshdFtpChannel implements FtpChannel { private SftpClient ftp; @@ -485,9 +470,9 @@ private String absolute(String path) { return path; } - private T map(FtpOperation op) throws IOException { + private T map(IOFunction op) throws IOException { try { - return op.call(); + return op.apply(null); } catch (IOException e) { if (e instanceof SftpException) { throw new FtpChannel.FtpException(e.getLocalizedMessage(), @@ -499,7 +484,7 @@ private T map(FtpOperation op) throws IOException { @Override public void cd(String path) throws IOException { - cwd = map(() -> ftp.canonicalPath(absolute(path))); + cwd = map(x -> ftp.canonicalPath(absolute(path))); if (cwd.isEmpty()) { cwd += '/'; } @@ -512,39 +497,28 @@ public String pwd() throws IOException { @Override public Collection ls(String path) throws IOException { - return map(() -> { + return map(x -> { List result = new ArrayList<>(); - try (CloseableHandle handle = ftp.openDir(absolute(path))) { - AtomicReference atEnd = new AtomicReference<>( - Boolean.FALSE); - while (!atEnd.get().booleanValue()) { - List chunk = ftp.readDir(handle, - atEnd); - if (chunk == null) { - break; + for (SftpClient.DirEntry remote : ftp.readDir(absolute(path))) { + result.add(new DirEntry() { + + @Override + public String getFilename() { + return remote.getFilename(); } - for (SftpClient.DirEntry remote : chunk) { - result.add(new DirEntry() { - @Override - public String getFilename() { - return remote.getFilename(); - } - - @Override - public long getModifiedTime() { - return remote.getAttributes() - .getModifyTime().toMillis(); - } - - @Override - public boolean isDirectory() { - return remote.getAttributes().isDirectory(); - } - - }); + @Override + public long getModifiedTime() { + return remote.getAttributes().getModifyTime() + .toMillis(); } - } + + @Override + public boolean isDirectory() { + return remote.getAttributes().isDirectory(); + } + + }); } return result; }); @@ -552,7 +526,7 @@ public boolean isDirectory() { @Override public void rmdir(String path) throws IOException { - map(() -> { + map(x -> { ftp.rmdir(absolute(path)); return null; }); @@ -561,7 +535,7 @@ public void rmdir(String path) throws IOException { @Override public void mkdir(String path) throws IOException { - map(() -> { + map(x -> { ftp.mkdir(absolute(path)); return null; }); @@ -569,17 +543,17 @@ public void mkdir(String path) throws IOException { @Override public InputStream get(String path) throws IOException { - return map(() -> ftp.read(absolute(path))); + return map(x -> ftp.read(absolute(path))); } @Override public OutputStream put(String path) throws IOException { - return map(() -> ftp.write(absolute(path))); + return map(x -> ftp.write(absolute(path))); } @Override public void rm(String path) throws IOException { - map(() -> { + map(x -> { ftp.remove(absolute(path)); return null; }); @@ -587,7 +561,7 @@ public void rm(String path) throws IOException { @Override public void rename(String from, String to) throws IOException { - map(() -> { + map(x -> { String src = absolute(from); String dest = absolute(to); try { From a92ff5369b30e4d90de0cd8ce444ac2ef7b40ed1 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 5 Nov 2021 00:13:52 +0100 Subject: [PATCH 12/24] Update jetty to 9.4.44.v20210927 Change-Id: Iaa1478af0fe0ccfa1daf1cf44e4eef609e7ad8bb --- WORKSPACE | 30 +++++++------- .../org.eclipse.jgit.target/jgit-4.17.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.18.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.19.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.20.target | 40 +++++++++---------- .../org.eclipse.jgit.target/jgit-4.21.target | 40 +++++++++---------- .../projects/jetty-9.4.x.tpd | 38 +++++++++--------- pom.xml | 2 +- 8 files changed, 135 insertions(+), 135 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index f4694b3ae..fb9212fa0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -243,55 +243,55 @@ maven_jar( sha1 = "69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a", ) -JETTY_VER = "9.4.43.v20210629" +JETTY_VER = "9.4.44.v20210927" maven_jar( name = "jetty-servlet", artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER, - sha1 = "ee000c7dcdbe7b4ef94e3fa67be8f56a46915944", - src_sha1 = "50236764fe1d3619ca07f346e148189c4f5b801a", + sha1 = "1cb43a0d74b7395c7207dbf3dc2ca97eac89f5fd", + src_sha1 = "2bbc54fc1835c963744a4e82ba2541e94fcbcf9b", ) maven_jar( name = "jetty-security", artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER, - sha1 = "ae1958da077c46bac61be9b8de2b45a3aa112353", - src_sha1 = "6e5271e91da37e381f566e0db07ab4d936d86104", + sha1 = "ecb80b8e008daa46e95e5691b2611d4007922497", + src_sha1 = "d67d4705a08d9b76592b3e177e2bb1aac968d832", ) maven_jar( name = "jetty-server", artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER, - sha1 = "8ba04f6b5d00223983684a563a3edaa12282bcf0", - src_sha1 = "51600567dbd082fb03feeb9c786f5e7cc9e0a17d", + sha1 = "0bf2de0d31925a8ca71ad80f721236850b636e0d", + src_sha1 = "3582cbf081cf3652f6507093585c2a0f3b8738bb", ) maven_jar( name = "jetty-http", artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER, - sha1 = "5171466337a6da7efbf317490b9c4574c0b4b640", - src_sha1 = "52f477161fd0fc90869f48a145aa2c86624c496e", + sha1 = "37f0e30cdc02128e40d095ad63cb18e10ecb7726", + src_sha1 = "7f1a6e3ab54e541f33b8ed100d553d4034d2e3a9", ) maven_jar( name = "jetty-io", artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER, - sha1 = "acf672c64ac21851069c5b5b789e5c185a25933f", - src_sha1 = "824d5cffce7a72af7c11d9cd87f86184e2a05c17", + sha1 = "a2ec01e2b5552b777a3d7085163f80756ef8c1ce", + src_sha1 = "6262966b3cd10ff6b98f0bed428640bbbe4f7e79", ) maven_jar( name = "jetty-util", artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER, - sha1 = "97306fd3c76171602caad2acc54ca779c9240d5f", - src_sha1 = "dffff7271c248d4e21e2b1629c57896b8e631051", + sha1 = "3c7151c5a04a93119988b48a1577a972d90f8990", + src_sha1 = "f7f0420221772bc63ebae21571bb9925ca971a82", ) maven_jar( name = "jetty-util-ajax", artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER, - sha1 = "2500d180c6e8e28eb3b75372b6ea9d457cf37658", - src_sha1 = "682470f5ad074e64fc0e9c93bdc2784482f79362", + sha1 = "ed2f30e8eef939ab2825e607d83f82f85167e2c0", + src_sha1 = "1a48ae7a45683d20afb90784d1db314be2c73c92", ) BOUNCYCASTLE_VER = "1.69" 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 5356dea1c..8c513d375 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target index cf8d12fdb..a4904da44 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target index 3e6ee56b8..3f65d62b7 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target index ce9d8bca2..2567d190c 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target index 41954429d..f2664a2ce 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd index 9e8936a96..8a143ce24 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.x "https://archive.eclipse.org/jetty/updates/jetty-bundles-9.x/jetty-bundles-9.x/9.4.43.v20210629/" { - org.eclipse.jetty.client [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.client.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.continuation [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.continuation.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.http [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.http.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.io [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.io.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.security [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.security.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.server [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.server.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.servlet [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.servlet.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.util [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.util.source [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.util.ajax [9.4.43.v20210629,9.4.43.v20210629] - org.eclipse.jetty.util.ajax.source [9.4.43.v20210629,9.4.43.v20210629] +location jetty-9.4.x "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.44.v20210927/" { + org.eclipse.jetty.client [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.client.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.continuation [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.continuation.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.http [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.http.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.io [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.io.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.security [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.security.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.server [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.server.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.servlet [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.servlet.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.util [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.util.source [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.util.ajax [9.4.44.v20210927,9.4.44.v20210927] + org.eclipse.jetty.util.ajax.source [9.4.44.v20210927,9.4.44.v20210927] } diff --git a/pom.xml b/pom.xml index 11789693f..c65f330ec 100644 --- a/pom.xml +++ b/pom.xml @@ -161,7 +161,7 @@ 1.20 4.3.1 3.1.0 - 9.4.43.v20210629 + 9.4.44.v20210927 0.15.3 4.5.13 4.4.14 From 5cbf70fd97dcc1956b7e1e375f9c3040494a28c4 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 7 Nov 2021 00:47:14 +0100 Subject: [PATCH 13/24] Update README * Java 11 now * Mention new bundle org.eclipse.jgit.ssh.apache.agent * Be honest about missing features: there are quite a few Change-Id: Ie08a2b4581024febe1983a59414cf69845ebff96 Signed-off-by: Thomas Wolf --- README.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 091accd7f..f1f485adc 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,13 @@ The CI builds use Maven and run on [Jenkins](https://ci.eclipse.org/jgit/). - __org.eclipse.jgit.ssh.apache__ - Client support for the ssh protocol based on + Client support for the SSH protocol based on [Apache Mina sshd](https://mina.apache.org/sshd-project/). +- __org.eclipse.jgit.ssh.apache.agent__ + + Optional support for SSH agents for org.eclipse.jgit.ssh.apache. + - __org.eclipse.jgit.ui__ Simple UI for displaying git log. @@ -83,7 +87,7 @@ __org.eclipse.jgit.junit.ssh__: Helpers for unit testing - Only the timestamp of the index is used by JGit if the index is dirty. -- JGit requires at least a Java 8 JDK. +- JGit 6.0 and newer requires at least Java 11. Older versions require at least Java 1.8. - CRLF conversion is performed depending on the `core.autocrlf` setting, however Git for Windows by default stores that setting during @@ -123,7 +127,7 @@ __org.eclipse.jgit.junit.ssh__: Helpers for unit testing - Object transport Fetch via ssh, git, http, Amazon S3 and bundles. - Push via ssh, git and Amazon S3. JGit does not yet deltify + Push via ssh, git, http, and Amazon S3. JGit does not yet deltify the pushed packs so they may be a lot larger than C Git packs. - Garbage collection @@ -145,9 +149,17 @@ __org.eclipse.jgit.junit.ssh__: Helpers for unit testing There are some missing features: -- verifying signed commits -- signing tags - signing push +- shallow and partial cloning +- support for remote helpers +- support for credential helpers +- support for multiple working trees (git-worktree) +- using external diff tools +- support for HTTPS client certificates +- SHA-256 object IDs +- git protocol V2 (client side): packfile-uris +- multi-pack index +- split index ## Support From a74dfb0901bdae125c351a2efec425d1721bc325 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 10 Nov 2021 14:51:29 +0100 Subject: [PATCH 14/24] Update Orbit to S20211108222137 and update dependencies: - com.google.gson to 2.8.8.v20211029-0838 - com.googlecode.javaewah to 1.1.13.v20211029-0839 - net.i2p.crypto.eddsa to 0.3.0.v20210923-1401 - org.apache.ant to 1.10.12.v20211102-1452 - org.apache.commons.compress to 1.21.0.v20211103-2100 - org.bouncycastle.bcprov to 1.69.0.v20210923-1401 - org.junit to 4.13.2.v20211018-1956 Change-Id: I90ca64f6d9f2a15c9a5d9a27d48956182f1698b4 --- WORKSPACE | 16 ++-- org.eclipse.jgit.ant/pom.xml | 1 - .../org.eclipse.jgit.target/jgit-4.17.target | 32 ++++---- .../org.eclipse.jgit.target/jgit-4.17.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.18.target | 32 ++++---- .../org.eclipse.jgit.target/jgit-4.18.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.19.target | 32 ++++---- .../org.eclipse.jgit.target/jgit-4.19.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.20.target | 32 ++++---- .../org.eclipse.jgit.target/jgit-4.20.tpd | 2 +- .../org.eclipse.jgit.target/jgit-4.21.target | 32 ++++---- .../org.eclipse.jgit.target/jgit-4.21.tpd | 2 +- .../orbit/S20211108222137.tpd | 73 +++++++++++++++++++ pom.xml | 15 +++- 14 files changed, 177 insertions(+), 98 deletions(-) create mode 100644 org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20211108222137.tpd diff --git a/WORKSPACE b/WORKSPACE index fb9212fa0..ccd7315d4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -105,8 +105,8 @@ maven_jar( maven_jar( name = "javaewah", - artifact = "com.googlecode.javaewah:JavaEWAH:1.1.12", - sha1 = "9feecc2b24d6bc9ff865af8d082f192238a293eb", + artifact = "com.googlecode.javaewah:JavaEWAH:1.1.13", + sha1 = "32cd724a42dc73f99ca08453d11a4bb83e0034c7", ) maven_jar( @@ -177,8 +177,8 @@ maven_jar( maven_jar( name = "commons-compress", - artifact = "org.apache.commons:commons-compress:1.20", - sha1 = "b8df472b31e1f17c232d2ad78ceb1c84e00c641b", + artifact = "org.apache.commons:commons-compress:1.21", + sha1 = "4ec95b60d4e86b5c95a0e919cb172a0af98011ef", ) maven_jar( @@ -195,8 +195,8 @@ maven_jar( maven_jar( name = "junit", - artifact = "junit:junit:4.13", - sha1 = "e49ccba652b735c93bd6e6f59760d8254cf597dd", + artifact = "junit:junit:4.13.2", + sha1 = "8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12", ) maven_jar( @@ -239,8 +239,8 @@ maven_jar( maven_jar( name = "gson", - artifact = "com.google.code.gson:gson:2.8.7", - sha1 = "69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a", + artifact = "com.google.code.gson:gson:2.8.8", + sha1 = "431fc3cbc0ff81abdbfde070062741089c3ba874", ) JETTY_VER = "9.4.44.v20210927" diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml index ae4b52d73..eaef00702 100644 --- a/org.eclipse.jgit.ant/pom.xml +++ b/org.eclipse.jgit.ant/pom.xml @@ -38,7 +38,6 @@ org.apache.ant ant - 1.10.11 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 8c513d375..06090e674 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,7 +1,7 @@ - + @@ -25,8 +25,8 @@ - - + + @@ -35,22 +35,22 @@ - - + + - - - - + + + + - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -93,7 +93,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 bab33ba2e..16527013b 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/R20210825222808-2021-09.tpd" +include "orbit/S20211108222137.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 a4904da44..1d6792229 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,7 +1,7 @@ - + @@ -25,8 +25,8 @@ - - + + @@ -35,22 +35,22 @@ - - + + - - - - + + + + - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -93,7 +93,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 095ea81aa..e732ad6d1 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/R20210825222808-2021-09.tpd" +include "orbit/S20211108222137.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.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target index 3f65d62b7..8b96f5269 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target @@ -1,7 +1,7 @@ - + @@ -25,8 +25,8 @@ - - + + @@ -35,22 +35,22 @@ - - + + - - - - + + + + - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -93,7 +93,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd index 52e457076..0e77c8872 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd @@ -1,7 +1,7 @@ target "jgit-4.19-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/R20210825222808-2021-09.tpd" +include "orbit/S20211108222137.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.20.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target index 2567d190c..a3aca8d79 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.target @@ -1,7 +1,7 @@ - + @@ -25,8 +25,8 @@ - - + + @@ -35,22 +35,22 @@ - - + + - - - - + + + + - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -93,7 +93,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd index d2cf94f2d..081864849 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20.tpd @@ -1,7 +1,7 @@ target "jgit-4.20" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/R20210825222808-2021-09.tpd" +include "orbit/S20211108222137.tpd" location "https://download.eclipse.org/releases/2021-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target index f2664a2ce..5f4498723 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.target @@ -1,7 +1,7 @@ - + @@ -25,8 +25,8 @@ - - + + @@ -35,22 +35,22 @@ - - + + - - - - + + + + - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -93,7 +93,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd index ba3ddb80c..53848b269 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.21.tpd @@ -1,7 +1,7 @@ target "jgit-4.21" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/R20210825222808-2021-09.tpd" +include "orbit/S20211108222137.tpd" location "https://download.eclipse.org/releases/2021-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20211108222137.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20211108222137.tpd new file mode 100644 index 000000000..0f0701099 --- /dev/null +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20211108222137.tpd @@ -0,0 +1,73 @@ +target "S20211108222137" with source configurePhase +// see https://download.eclipse.org/tools/orbit/downloads/ + +location "https://download.eclipse.org/tools/orbit/downloads/drops/S20211108222137/repository" { + com.google.gson [2.8.8.v20211029-0838,2.8.8.v20211029-0838] + com.google.gson.source [2.8.8.v20211029-0838,2.8.8.v20211029-0838] + com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902] + com.jcraft.jsch.source [0.1.55.v20190404-1902,0.1.55.v20190404-1902] + com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305] + com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305] + com.sun.jna [5.8.0.v20210503-0343,5.8.0.v20210503-0343] + com.sun.jna.source [5.8.0.v20210503-0343,5.8.0.v20210503-0343] + com.sun.jna.platform [5.8.0.v20210406-1004,5.8.0.v20210406-1004] + com.sun.jna.platform.source [5.8.0.v20210406-1004,5.8.0.v20210406-1004] + javaewah [1.1.13.v20211029-0839,1.1.13.v20211029-0839] + javaewah.source [1.1.13.v20211029-0839,1.1.13.v20211029-0839] + javax.servlet [3.1.0.v201410161800,3.1.0.v201410161800] + javax.servlet.source [3.1.0.v201410161800,3.1.0.v201410161800] + net.bytebuddy.byte-buddy [1.9.0.v20181107-1410,1.9.0.v20181107-1410] + net.bytebuddy.byte-buddy-agent [1.9.0.v20181106-1534,1.9.0.v20181106-1534] + net.bytebuddy.byte-buddy-agent.source [1.9.0.v20181106-1534,1.9.0.v20181106-1534] + net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410] + net.i2p.crypto.eddsa [0.3.0.v20210923-1401,0.3.0.v20210923-1401] + net.i2p.crypto.eddsa.source [0.3.0.v20210923-1401,0.3.0.v20210923-1401] + org.apache.ant [1.10.12.v20211102-1452,1.10.12.v20211102-1452] + org.apache.ant.source [1.10.12.v20211102-1452,1.10.12.v20211102-1452] + org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422] + org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422] + org.apache.commons.compress [1.21.0.v20211103-2100,1.21.0.v20211103-2100] + org.apache.commons.compress.source [1.21.0.v20211103-2100,1.21.0.v20211103-2100] + org.apache.commons.logging [1.2.0.v20180409-1502,1.2.0.v20180409-1502] + org.apache.commons.logging.source [1.2.0.v20180409-1502,1.2.0.v20180409-1502] + org.apache.httpcomponents.httpclient [4.5.13.v20210128-2225,4.5.13.v20210128-2225] + org.apache.httpcomponents.httpclient.source [4.5.13.v20210128-2225,4.5.13.v20210128-2225] + org.apache.httpcomponents.httpcore [4.4.14.v20210128-2225,4.4.14.v20210128-2225] + org.apache.httpcomponents.httpcore.source [4.4.14.v20210128-2225,4.4.14.v20210128-2225] + org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815] + org.apache.sshd.osgi [2.7.0.v20210623-0618,2.7.0.v20210623-0618] + org.apache.sshd.osgi.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618] + org.apache.sshd.sftp [2.7.0.v20210623-0618,2.7.0.v20210623-0618] + org.apache.sshd.sftp.source [2.7.0.v20210623-0618,2.7.0.v20210623-0618] + org.assertj [3.20.2.v20210706-1104,3.20.2.v20210706-1104] + org.assertj.source [3.20.2.v20210706-1104,3.20.2.v20210706-1104] + org.bouncycastle.bcpg [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.bouncycastle.bcpg.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.bouncycastle.bcpkix [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.bouncycastle.bcpkix.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.bouncycastle.bcprov [1.69.0.v20210923-1401,1.69.0.v20210923-1401] + org.bouncycastle.bcprov.source [1.69.0.v20210923-1401,1.69.0.v20210923-1401] + org.bouncycastle.bcutil [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.bouncycastle.bcutil.source [1.69.0.v20210713-1924,1.69.0.v20210713-1924] + org.hamcrest [2.2.0.v20210711-0821,2.2.0.v20210711-0821] + org.hamcrest.source [2.2.0.v20210711-0821,2.2.0.v20210711-0821] + org.hamcrest.core [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.core.source [1.3.0.v20180420-1519,1.3.0.v20180420-1519] + org.hamcrest.library [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + org.hamcrest.library.source [1.3.0.v20180524-2246,1.3.0.v20180524-2246] + org.junit [4.13.2.v20211018-1956,4.13.2.v20211018-1956] + org.junit.source [4.13.2.v20211018-1956,4.13.2.v20211018-1956] + org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.kohsuke.args4j.source [2.33.0.v20160323-2218,2.33.0.v20160323-2218] + org.mockito [2.23.0.v20200310-1642,2.23.0.v20200310-1642] + org.mockito.source [2.23.0.v20200310-1642,2.23.0.v20200310-1642] + org.objenesis [2.6.0.v20180420-1519,2.6.0.v20180420-1519] + org.objenesis.source [2.6.0.v20180420-1519,2.6.0.v20180420-1519] + org.slf4j.api [1.7.30.v20200204-2150,1.7.30.v20200204-2150] + org.slf4j.api.source [1.7.30.v20200204-2150,1.7.30.v20200204-2150] + org.slf4j.binding.log4j12 [1.7.30.v20201108-2042,1.7.30.v20201108-2042] + org.slf4j.binding.log4j12.source [1.7.30.v20201108-2042,1.7.30.v20201108-2042] + org.tukaani.xz [1.9.0.v20210624-1259,1.9.0.v20210624-1259] + org.tukaani.xz.source [1.9.0.v20210624-1259,1.9.0.v20210624-1259] +} diff --git a/pom.xml b/pom.xml index c65f330ec..ee6b0f029 100644 --- a/pom.xml +++ b/pom.xml @@ -151,14 +151,15 @@ ${project.build.directory}/META-INF/MANIFEST.MF 5.12.0.202106070339-r + 1.10.12 2.7.0 0.1.55 1.1.1 - 1.1.12 - 4.13 + 1.1.13 + 4.13.2 1C 2.33 - 1.20 + 1.21 4.3.1 3.1.0 9.4.44.v20210927 @@ -169,7 +170,7 @@ 1.2.15 3.3.1 2.5.0 - 2.8.7 + 2.8.8 1.69 4.3.0 3.1.2 @@ -670,6 +671,12 @@ ${servlet-api-version}
+ + org.apache.ant + ant + ${ant-version} + + org.apache.commons commons-compress From b84738c3693081d3ed9e8e1ba4a5db6e5ac3abf8 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 10 Nov 2021 15:56:36 +0100 Subject: [PATCH 15/24] Update version of last release defining the API baseline to 5.13.0 Change-Id: I48e0d677a466a364fdd699cdb00014a9a65d082a --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ee6b0f029..311f0a5a6 100644 --- a/pom.xml +++ b/pom.xml @@ -150,7 +150,7 @@ 11 ${project.build.directory}/META-INF/MANIFEST.MF - 5.12.0.202106070339-r + 5.13.0.202109080827-r 1.10.12 2.7.0 0.1.55 From e7838b9c080011817ddb59c53298237a9c24a7a6 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Wed, 27 Oct 2021 14:03:10 +0200 Subject: [PATCH 16/24] [sshd agent] Introduce ConnectorDescriptor Once a factory supports different SSH agents on the same platform, which is planned for Windows once we use Apache MINA sshd 2.8.0, client code may need to have a way to specify which SSH agent shall be used when the SSH config doesn't define anything. Add a mechanism by which a ConnectorFactory can tell what Connectors it may provide. Client code can use this to set the identityAgent parameter of ConnectorFactory.create() to the wanted default if it would be null otherwise. A ConnectorDescriptor is a pair of strings: an internal name, and a display name. The latter is included because client code might want to communicate agent names to the user, be it in error messages or in some chooser dialog where a user could define which of several alternative SSH agents should be used as default. The internal name is intended to be used in the IdentityAgent directive in ~/.ssh/config. Also make the ConnectorFactory discovered via the ServiceLoader accessible and overrideable. Provide static get/setDefault() methods, similar to the SshSessionFactory itself. Change-Id: Ie3d077395d32dfddc72bc8627e92b23636938182 Signed-off-by: Thomas Wolf --- .../sshd/agent/connector/Texts.properties | 3 +- .../sshd/agent/connector/Factory.java | 24 ++++ .../agent/connector/PageantConnector.java | 17 +++ .../transport/sshd/agent/connector/Texts.java | 2 + .../connector/UnixDomainSocketConnector.java | 17 +++ .../sshd/agent/ConnectorFactoryProvider.java | 26 ++-- .../transport/sshd/SshdSessionFactory.java | 6 +- .../sshd/agent/ConnectorFactory.java | 116 +++++++++++++++++- .../jgit/transport/SshSessionFactory.java | 2 +- 9 files changed, 199 insertions(+), 14 deletions(-) diff --git a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties index f884adc08..6fce08366 100644 --- a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties +++ b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties @@ -13,4 +13,5 @@ msgSendFailed=Sending {0} bytes to SSH agent failed; {0} bytes not written msgSendFailed2=Sending {0} bytes to SSH agent failed: {1} - {2} msgSharedMemoryFailed=Could not set up shared memory for communicating with Pageant msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2} - +pageant=Pageant +unixDefaultAgent=ssh-agent diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java index 1cbf0e41b..d7409b0c3 100644 --- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java @@ -11,6 +11,8 @@ import java.io.File; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import org.eclipse.jgit.transport.sshd.agent.Connector; import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory; @@ -41,4 +43,26 @@ public boolean isSupported() { public String getName() { return NAME; } + + /** + * {@inheritDoc} + *

+ * This factory returns on Windows a + * {@link org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor + * ConnectorDescriptor} for the internal name "pageant"; on Unix one for + * "SSH_AUTH_SOCK". + *

+ */ + @Override + public Collection getSupportedConnectors() { + return Collections.singleton(getDefaultConnector()); + } + + @Override + public ConnectorDescriptor getDefaultConnector() { + if (SystemReader.getInstance().isWindows()) { + return PageantConnector.DESCRIPTOR; + } + return UnixDomainSocketConnector.DESCRIPTOR; + } } diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java index 59fe2fc24..b0e3bce72 100644 --- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/PageantConnector.java @@ -12,12 +12,29 @@ import java.io.IOException; import org.eclipse.jgit.transport.sshd.agent.AbstractConnector; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor; /** * A connector using Pageant's shared memory IPC mechanism. */ public class PageantConnector extends AbstractConnector { + /** + * {@link ConnectorDescriptor} for the {@link PageantConnector}. + */ + public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() { + + @Override + public String getIdentityAgent() { + return "pageant"; //$NON-NLS-1$ + } + + @Override + public String getDisplayName() { + return Texts.get().pageant; + } + }; + private final PageantLibrary lib; /** diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java index 6b6626154..fb45b30dd 100644 --- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java @@ -42,5 +42,7 @@ public static Texts get() { /***/ public String msgSendFailed2; /***/ public String msgSharedMemoryFailed; /***/ public String msgShortRead; + /***/ public String pageant; + /***/ public String unixDefaultAgent; } diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java index 4c698d974..3b75f3a7d 100644 --- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java +++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/UnixDomainSocketConnector.java @@ -24,6 +24,7 @@ import org.apache.sshd.common.SshException; import org.eclipse.jgit.transport.sshd.agent.AbstractConnector; +import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor; import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; @@ -38,6 +39,22 @@ */ public class UnixDomainSocketConnector extends AbstractConnector { + /** + * {@link ConnectorDescriptor} for the {@link UnixDomainSocketConnector}. + */ + public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() { + + @Override + public String getIdentityAgent() { + return ENV_SSH_AUTH_SOCK; + } + + @Override + public String getDisplayName() { + return Texts.get().unixDefaultAgent; + } + }; + private static final Logger LOG = LoggerFactory .getLogger(UnixDomainSocketConnector.class); diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java index 9984f9976..aba7a7645 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/agent/ConnectorFactoryProvider.java @@ -19,7 +19,7 @@ */ public final class ConnectorFactoryProvider { - private static final ConnectorFactory FACTORY = loadDefaultFactory(); + private static volatile ConnectorFactory INSTANCE = loadDefaultFactory(); private static ConnectorFactory loadDefaultFactory() { ServiceLoader loader = ServiceLoader @@ -35,17 +35,27 @@ private static ConnectorFactory loadDefaultFactory() { } - private ConnectorFactoryProvider() { - // No instantiation - } - /** - * Retrieves the default {@link ConnectorFactory} obtained via the - * {@link ServiceLoader}. + * Retrieves the currently set default {@link ConnectorFactory}. * * @return the {@link ConnectorFactory}, or {@code null} if none. */ public static ConnectorFactory getDefaultFactory() { - return FACTORY; + return INSTANCE; + } + + /** + * Sets the default {@link ConnectorFactory}. + * + * @param factory + * {@link ConnectorFactory} to use, or {@code null} to use the + * factory discovered via the {@link ServiceLoader}. + */ + public static void setDefaultFactory(ConnectorFactory factory) { + INSTANCE = factory == null ? loadDefaultFactory() : factory; + } + + private ConnectorFactoryProvider() { + // No instantiation } } 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 336418009..58cf8e1dd 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 @@ -56,7 +56,6 @@ import org.eclipse.jgit.internal.transport.sshd.OpenSshServerKeyDatabase; import org.eclipse.jgit.internal.transport.sshd.PasswordProviderWrapper; import org.eclipse.jgit.internal.transport.sshd.SshdText; -import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider; import org.eclipse.jgit.internal.transport.sshd.agent.JGitSshAgentFactory; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.SshConfigStore; @@ -456,12 +455,15 @@ protected ServerKeyDatabase createServerKeyDatabase(@NonNull File homeDir, /** * Gets a {@link ConnectorFactory}. If this returns {@code null}, SSH agents * are not supported. + *

+ * The default implementation uses {@link ConnectorFactory#getDefault()} + *

* * @return the factory, or {@code null} if no SSH agent support is desired * @since 6.0 */ protected ConnectorFactory getConnectorFactory() { - return ConnectorFactoryProvider.getDefaultFactory(); + return ConnectorFactory.getDefault(); } /** diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java index b351d89ef..da98ea7fe 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/agent/ConnectorFactory.java @@ -11,8 +11,10 @@ import java.io.File; import java.io.IOException; +import java.util.Collection; import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.internal.transport.sshd.agent.ConnectorFactoryProvider; /** * A factory for creating {@link Connector}s. This is a service provider @@ -24,14 +26,45 @@ */ public interface ConnectorFactory { + /** + * Retrieves the currently set default {@link ConnectorFactory}. This is the + * factory that is used unless overridden by the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + * + * @return the current default factory; may be {@code null} if none is set + * and the {@link java.util.ServiceLoader} cannot find any suitable + * implementation + */ + static ConnectorFactory getDefault() { + return ConnectorFactoryProvider.getDefaultFactory(); + } + + /** + * Sets a default {@link ConnectorFactory}. This is the factory that is used + * unless overridden by the + * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory}. + *

+ * If no default factory is set programmatically, an implementation is + * discovered via the {@link java.util.ServiceLoader}. + *

+ * + * @param factory + * {@link ConnectorFactory} to set, or {@code null} to revert to + * the default behavior of using the + * {@link java.util.ServiceLoader}. + */ + static void setDefault(ConnectorFactory factory) { + ConnectorFactoryProvider.setDefaultFactory(factory); + } + /** * Creates a new {@link Connector}. * * @param identityAgent * identifies the wanted agent connection; if {@code null}, the * factory is free to provide a {@link Connector} to a default - * agent. The value will typically come from the IdentityAgent - * setting in ~/.ssh/config. + * agent. The value will typically come from the + * {@code IdentityAgent} setting in {@code ~/.ssh/config}. * @param homeDir * the current local user's home directory as configured in the * {@link org.eclipse.jgit.transport.sshd.SshdSessionFactory} @@ -58,4 +91,83 @@ Connector create(String identityAgent, File homeDir) */ String getName(); + /** + * {@link ConnectorDescriptor}s describe available {@link Connector}s a + * {@link ConnectorFactory} may provide. + *

+ * A {@link ConnectorFactory} may support connecting to different SSH + * agents. Agents are identified by name; a user can choose a specific agent + * for instance via the {@code IdentityAgent} setting in + * {@code ~/.ssh/config}. + *

+ *

+ * OpenSSH knows two built-in names: "none" for not using any agent, and + * "SSH_AUTH_SOCK" for using an agent that communicates over a Unix domain + * socket given by the value of environment variable {@code SSH_AUTH_SOCK}. + * Other agents can be specified in OpenSSH by specifying the socket file + * directly. (The "standard" OpenBSD OpenSSH knows only this communication + * mechanism.) "SSH_AUTH_SOCK" is also the default in OpenBSD OpenSSH if + * nothing is configured. + *

+ *

+ * A particular {@link ConnectorFactory} may support more communication + * mechanisms or different agents. For instance, a factory on Windows might + * support Pageant, Win32-OpenSSH, or even git bash ssh-agent, and might + * accept internal names like "pageant", "openssh", "SSH_AUTH_SOCK" in + * {@link ConnectorFactory#create(String, File)} to choose among them. + *

+ * The {@link ConnectorDescriptor} interface and the + * {@link ConnectorFactory#getSupportedConnectors()} and + * {@link ConnectorFactory#getDefaultConnector()} methods provide a way for + * code using a {@link ConnectorFactory} to learn what the factory supports + * and thus implement some way by which a user can influence the default + * behavior if {@code IdentityAgent} is not set or + * {@link ConnectorFactory#create(String, File)} is called with + * {@code identityAgent == null}. + */ + interface ConnectorDescriptor { + + /** + * Retrieves the internal name of a supported {@link Connector}. The + * internal name is the one a user can specify for instance in the + * {@code IdentityAgent} setting in {@code ~/.ssh/config} to select the + * connector. + * + * @return the internal name; not empty + */ + @NonNull + String getIdentityAgent(); + + /** + * Retrieves a display name for a {@link Connector}, suitable for + * showing in a UI. + * + * @return the display name; properly localized and not empty + */ + @NonNull + String getDisplayName(); + } + + /** + * Tells which kinds of SSH agents this {@link ConnectorFactory} supports. + *

+ * An implementation of this method should document the possible values it + * returns. + *

+ * + * @return an immutable collection of {@link ConnectorDescriptor}s, + * including {@link #getDefaultConnector()} and not including a + * descriptor for internal name "none" + */ + @NonNull + Collection getSupportedConnectors(); + + /** + * Tells what kind of {@link Connector} this {@link ConnectorFactory} + * creates if {@link ConnectorFactory#create(String, File)} is called with + * {@code identityAgent == null}. + * + * @return a {@link ConnectorDescriptor} for the default connector + */ + ConnectorDescriptor getDefaultConnector(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java index e216a56ac..1e98a56f7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java @@ -36,7 +36,7 @@ */ public abstract class SshSessionFactory { - private static SshSessionFactory INSTANCE = loadSshSessionFactory(); + private static volatile SshSessionFactory INSTANCE = loadSshSessionFactory(); private static SshSessionFactory loadSshSessionFactory() { ServiceLoader loader = ServiceLoader.load(SshSessionFactory.class); From d4296d96bb13ec100f396271c3a9c9c9c9b31dad Mon Sep 17 00:00:00 2001 From: Marco Miller Date: Thu, 11 Nov 2021 09:17:54 -0500 Subject: [PATCH 17/24] Upgrade plexus-compiler version to 2.9.0 Change-Id: I24ac698d3ccc01e79464b061068944ca82e8383b Signed-off-by: Marco Miller --- pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 311f0a5a6..f7629411b 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ 3.0.0-M5 ${maven-surefire-plugin-version} 3.8.1 - 2.8.8 + 2.9.0 2.2 3.20.2 @@ -870,6 +870,11 @@ plexus-compiler-eclipse ${plexus-compiler-version}
+ + org.codehaus.plexus + plexus-compiler-api + ${plexus-compiler-version} + org.eclipse.jdt ecj From ffb5cac361b1c6751f392dd2bce773cb5571b846 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Mon, 15 Nov 2021 10:37:36 +0100 Subject: [PATCH 18/24] Add missing .gitignore in o.e.j.ssh.apache.agent Ignore /bin and /target. Change-Id: I38f3748273b5243c54e010bfceac745084755f45 Signed-off-by: Thomas Wolf --- org.eclipse.jgit.ssh.apache.agent/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 org.eclipse.jgit.ssh.apache.agent/.gitignore diff --git a/org.eclipse.jgit.ssh.apache.agent/.gitignore b/org.eclipse.jgit.ssh.apache.agent/.gitignore new file mode 100644 index 000000000..934e0e06f --- /dev/null +++ b/org.eclipse.jgit.ssh.apache.agent/.gitignore @@ -0,0 +1,2 @@ +/bin +/target From c4b3ec72faf891120fdd93246503d0bee339f349 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 13 Nov 2021 13:08:14 +0100 Subject: [PATCH 19/24] OpenSshConfigFile: update token replacements It appears that the OpenSSH documentation[1] has changed; it now allows more flags for a number of keys. [1] https://man.openbsd.org/ssh_config.5#TOKENS Change-Id: I55df174f86a3fd4a6ef22687dc433ac9f9ad181d Signed-off-by: Thomas Wolf --- .../transport/ssh/OpenSshConfigFile.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java index 4cffcc5dd..4d1864a92 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java @@ -767,13 +767,15 @@ void substitute(String originalHostName, int port, String userName, List values = multiOptions .get(SshConstants.IDENTITY_FILE); if (values != null) { - values = substitute(values, "dhlru", r, true); //$NON-NLS-1$ + values = substitute(values, Replacer.DEFAULT_TOKENS, r, + true); values = replaceTilde(values, home); multiOptions.put(SshConstants.IDENTITY_FILE, values); } values = multiOptions.get(SshConstants.CERTIFICATE_FILE); if (values != null) { - values = substitute(values, "dhlru", r, true); //$NON-NLS-1$ + values = substitute(values, Replacer.DEFAULT_TOKENS, r, + true); values = replaceTilde(values, home); multiOptions.put(SshConstants.CERTIFICATE_FILE, values); } @@ -782,6 +784,8 @@ void substitute(String originalHostName, int port, String userName, List values = listOptions .get(SshConstants.USER_KNOWN_HOSTS_FILE); if (values != null) { + values = substitute(values, Replacer.DEFAULT_TOKENS, r, + true); values = replaceTilde(values, home); listOptions.put(SshConstants.USER_KNOWN_HOSTS_FILE, values); } @@ -790,29 +794,29 @@ void substitute(String originalHostName, int port, String userName, // HOSTNAME already done above String value = options.get(SshConstants.IDENTITY_AGENT); if (value != null) { - value = r.substitute(value, "dhlru", true); //$NON-NLS-1$ + value = r.substitute(value, Replacer.DEFAULT_TOKENS, true); value = toFile(value, home).getPath(); options.put(SshConstants.IDENTITY_AGENT, value); } value = options.get(SshConstants.CONTROL_PATH); if (value != null) { - value = r.substitute(value, "ChLlnpru", true); //$NON-NLS-1$ + value = r.substitute(value, Replacer.DEFAULT_TOKENS, true); value = toFile(value, home).getPath(); options.put(SshConstants.CONTROL_PATH, value); } value = options.get(SshConstants.LOCAL_COMMAND); if (value != null) { - value = r.substitute(value, "CdhlnprTu", false); //$NON-NLS-1$ + value = r.substitute(value, "CdhLlnprTu", false); //$NON-NLS-1$ options.put(SshConstants.LOCAL_COMMAND, value); } value = options.get(SshConstants.REMOTE_COMMAND); if (value != null) { - value = r.substitute(value, "Cdhlnpru", false); //$NON-NLS-1$ + value = r.substitute(value, Replacer.DEFAULT_TOKENS, false); options.put(SshConstants.REMOTE_COMMAND, value); } value = options.get(SshConstants.PROXY_COMMAND); if (value != null) { - value = r.substitute(value, "hpr", false); //$NON-NLS-1$ + value = r.substitute(value, "hnpr", false); //$NON-NLS-1$ options.put(SshConstants.PROXY_COMMAND, value); } } @@ -880,6 +884,15 @@ public String toString() { } private static class Replacer { + + /** + * Tokens applicable to most keys. + * + * @see man + * ssh_config + */ + public static final String DEFAULT_TOKENS = "CdhLlnpru"; //$NON-NLS-1$ + private final Map replacements = new HashMap<>(); public Replacer(String host, int port, String user, From af0126e1d01100fad673b6d0a56a99633383a198 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 13 Nov 2021 13:09:01 +0100 Subject: [PATCH 20/24] OpenSshConfigFile: line comments and quoted strings Bring our SSH config parser up-to-date with respect to changes in OpenSSH. In particular, they fixed[1] the handling of line comments such that #-characters inside strings are not considered. This means that we have to parse strings with escaped quotes correctly. [1] https://bugzilla.mindrot.org/show_bug.cgi?id=3288 Change-Id: Ifbd9014127e8d51e7c8792e237f3fc2a9a0719d2 Signed-off-by: Thomas Wolf --- .../transport/ssh/jsch/OpenSshConfigTest.java | 4 +- .../transport/ssh/OpenSshConfigFileTest.java | 35 +++- .../transport/ssh/OpenSshConfigFile.java | 160 +++++++++++++----- 3 files changed, 154 insertions(+), 45 deletions(-) diff --git a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/ssh/jsch/OpenSshConfigTest.java b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/ssh/jsch/OpenSshConfigTest.java index 4399ad9be..9a61cc4c5 100644 --- a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/ssh/jsch/OpenSshConfigTest.java +++ b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/ssh/jsch/OpenSshConfigTest.java @@ -138,7 +138,7 @@ public void testQuoteParsing() throws Exception { assertEquals(2222, osc.lookup("unquoted").getPort()); assertEquals(2222, osc.lookup("hosts").getPort()); assertEquals(" spaced\ttld ", osc.lookup("spaced").getHostName()); - assertEquals("bad.tld\"", osc.lookup("bad").getHostName()); + assertEquals("bad.tld", osc.lookup("bad").getHostName()); } @Test @@ -229,7 +229,7 @@ public void testAlias_PreferredAuthentications() throws Exception { @Test public void testAlias_InheritPreferredAuthentications() throws Exception { config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" - + "\tPreferredAuthentications publickey, hostbased\n"); + + "\tPreferredAuthentications 'publickey, hostbased'\n"); final Host h = osc.lookup("orcz"); assertNotNull(h); assertEquals("publickey,hostbased", h.getPreferredAuthentications()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java index 27bae3747..11741b41a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFileTest.java @@ -166,7 +166,38 @@ public void testQuoteParsing() throws Exception { assertPort(2222, lookup("unquoted")); assertPort(2222, lookup("hosts")); assertHost(" spaced\ttld ", lookup("spaced")); - assertHost("bad.tld\"", lookup("bad")); + assertHost("bad.tld", lookup("bad")); + } + + @Test + public void testAdvancedParsing() throws Exception { + // Escaped quotes, and line comments + config("Host foo\n" + + " HostName=\"foo\\\"d.tld\"\n" + + " User= someone#foo\n" + + "Host bar\n" + + " User ' some one#two' # Comment\n" + + " GlobalKnownHostsFile '/a folder/with spaces/hosts' '/other/more hosts' # Comment\n" + + "Host foobar\n" + + " User a\\ u\\ thor\n" + + "Host backslash\n" + + " User some\\one\\\\\\ foo\n" + + "Host backslash_before_quote\n" + + " User \\\"someone#\"el#se\" #Comment\n" + + "Host backslash_in_quote\n" + + " User 'some\\one\\\\\\ foo'\n"); + assertHost("foo\"d.tld", lookup("foo")); + assertUser("someone#foo", lookup("foo")); + HostConfig c = lookup("bar"); + assertUser(" some one#two", c); + assertArrayEquals( + new Object[] { "/a folder/with spaces/hosts", + "/other/more hosts" }, + c.getValues("GlobalKnownHostsFile").toArray()); + assertUser("a u thor", lookup("foobar")); + assertUser("some\\one\\ foo", lookup("backslash")); + assertUser("\"someone#el#se", lookup("backslash_before_quote")); + assertUser("some\\one\\\\ foo", lookup("backslash_in_quote")); } @Test @@ -258,7 +289,7 @@ public void testAlias_PreferredAuthentications() throws Exception { @Test public void testAlias_InheritPreferredAuthentications() throws Exception { config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" - + "\tPreferredAuthentications publickey, hostbased\n"); + + "\tPreferredAuthentications 'publickey, hostbased'\n"); final HostConfig h = lookup("orcz"); assertNotNull(h); assertEquals("publickey,hostbased", diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java index 4d1864a92..2dbb7859b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java @@ -230,27 +230,39 @@ private List parse(BufferedReader reader) String line; while ((line = reader.readLine()) != null) { - // OpenSsh ignores trailing comments on a line. Anything after the - // first # on a line is trimmed away (yes, even if the hash is - // inside quotes). - // - // See https://github.com/openssh/openssh-portable/commit/2bcbf679 - int i = line.indexOf('#'); - if (i >= 0) { - line = line.substring(0, i); - } - line = line.trim(); + line = line.strip(); if (line.isEmpty()) { continue; } String[] parts = line.split("[ \t]*[= \t]", 2); //$NON-NLS-1$ - // Although the ssh-config man page doesn't say so, the openssh - // parser does allow quoted keywords. - String keyword = dequote(parts[0].trim()); + String keyword = parts[0].strip(); + if (keyword.isEmpty()) { + continue; + } + switch (keyword.charAt(0)) { + case '#': + continue; + case '"': + // Although the ssh-config man page doesn't say so, the openssh + // parser does allow quoted keywords. + List dequoted = parseList(keyword); + keyword = dequoted.isEmpty() ? "" : dequoted.get(0); //$NON-NLS-1$ + break; + default: + // Keywords never contain hashes, nor whitespace + int i = keyword.indexOf('#'); + if (i >= 0) { + keyword = keyword.substring(0, i); + } + break; + } + if (keyword.isEmpty()) { + continue; + } // man 5 ssh-config says lines had the format "keyword arguments", // with no indication that arguments were optional. However, let's // not crap out on missing arguments. See bug 444319. - String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$ + String argValue = parts.length > 1 ? parts[1].strip() : ""; //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase(SshConstants.HOST, keyword)) { current = new HostEntry(parseList(argValue)); @@ -262,7 +274,9 @@ private List parse(BufferedReader reader) List args = validate(keyword, parseList(argValue)); current.setValue(keyword, args); } else if (!argValue.isEmpty()) { - argValue = validate(keyword, dequote(argValue)); + List args = parseList(argValue); + String arg = args.isEmpty() ? "" : args.get(0); //$NON-NLS-1$ + argValue = validate(keyword, arg); current.setValue(keyword, argValue); } } @@ -273,6 +287,7 @@ private List parse(BufferedReader reader) /** * Splits the argument into a list of whitespace-separated elements. * Elements containing whitespace must be quoted and will be de-quoted. + * Backslash-escapes are handled for quotes and blanks. * * @param argument * argument part of the configuration line as read from the @@ -280,37 +295,107 @@ private List parse(BufferedReader reader) * @return a {@link List} of elements, possibly empty and possibly * containing empty elements, but not containing {@code null} */ - private List parseList(String argument) { + private static List parseList(String argument) { List result = new ArrayList<>(4); int start = 0; int length = argument.length(); while (start < length) { // Skip whitespace - if (Character.isWhitespace(argument.charAt(start))) { + char ch = argument.charAt(start); + if (Character.isWhitespace(ch)) { start++; - continue; - } - if (argument.charAt(start) == '"') { - int stop = argument.indexOf('"', ++start); - if (stop < start) { - // No closing double quote: skip - break; - } - result.add(argument.substring(start, stop)); - start = stop + 1; + } else if (ch == '#') { + break; // Comment start } else { - int stop = start + 1; - while (stop < length - && !Character.isWhitespace(argument.charAt(stop))) { - stop++; - } - result.add(argument.substring(start, stop)); - start = stop + 1; + // Parse one token now. + start = parseToken(argument, start, length, result); } } return result; } + /** + * Parses a token up to the next whitespace not inside a string quoted by + * single or double quotes. Inside a string, quotes can be escaped by + * backslash characters. Outside of a string, "\ " can be used to include a + * space in a token; inside a string "\ " is taken literally as '\' followed + * by ' '. + * + * @param argument + * to parse the token out of + * @param from + * index at the beginning of the token + * @param to + * index one after the last character to look at + * @param result + * a list collecting tokens to which the parsed token is added + * @return the index after the token + */ + private static int parseToken(String argument, int from, int to, + List result) { + StringBuilder b = new StringBuilder(); + int i = from; + char quote = 0; + boolean escaped = false; + SCAN: while (i < to) { + char ch = argument.charAt(i); + switch (ch) { + case '"': + case '\'': + if (quote == 0) { + if (escaped) { + b.append(ch); + } else { + quote = ch; + } + } else if (!escaped && quote == ch) { + quote = 0; + } else { + b.append(ch); + } + escaped = false; + break; + case '\\': + if (escaped) { + b.append(ch); + } + escaped = !escaped; + break; + case ' ': + if (quote == 0) { + if (escaped) { + b.append(ch); + escaped = false; + } else { + break SCAN; + } + } else { + if (escaped) { + b.append('\\'); + } + b.append(ch); + escaped = false; + } + break; + default: + if (escaped) { + b.append('\\'); + } + if (quote == 0 && Character.isWhitespace(ch)) { + break SCAN; + } + b.append(ch); + escaped = false; + break; + } + i++; + } + if (b.length() > 0) { + result.add(b.toString()); + } + return i; + } + /** * Hook to perform validation on a single value, or to sanitize it. If this * throws an (unchecked) exception, parsing of the file is abandoned. @@ -358,13 +443,6 @@ private static boolean patternMatchesHost(String pattern, String name) { return pattern.equals(name); } - private static String dequote(String value) { - if (value.startsWith("\"") && value.endsWith("\"") //$NON-NLS-1$ //$NON-NLS-2$ - && value.length() > 1) - return value.substring(1, value.length() - 1); - return value; - } - private static String stripWhitespace(String value) { final StringBuilder b = new StringBuilder(); int length = value.length(); From 180bc67e28f333a8b23413a0789b6563c3a5b9de Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 13 Nov 2021 13:09:58 +0100 Subject: [PATCH 21/24] ssh: use a single SecureRandom instance for hashing hostnames According to Spotbugs, that's better practice. It's questionable whether it makes a big difference, though, especially since the hash is the cryptographically weak SHA1. Change-Id: Id293de2bad809d9cc19230bd720184786dc6c226 Signed-off-by: Thomas Wolf --- .../internal/transport/sshd/OpenSshServerKeyDatabase.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java index 85e406f42..d8bf449ac 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/internal/transport/sshd/OpenSshServerKeyDatabase.java @@ -34,6 +34,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; @@ -138,6 +139,8 @@ public class OpenSshServerKeyDatabase private final List defaultFiles = new ArrayList<>(); + private Random prng; + /** * Creates a new {@link OpenSshServerKeyDatabase}. * @@ -680,7 +683,9 @@ private String createHostKeyLine(Collection patterns, // or to Apache MINA sshd. NamedFactory digester = KnownHostDigest.SHA1; Mac mac = digester.create(); - SecureRandom prng = new SecureRandom(); + if (prng == null) { + prng = new SecureRandom(); + } byte[] salt = new byte[mac.getDefaultBlockSize()]; for (SshdSocketAddress address : patterns) { if (result.length() > 0) { From 057f1d9123f43aceecf413acc5e759e4a97dc8e4 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 13 Nov 2021 18:10:13 +0100 Subject: [PATCH 22/24] ssh: Handle "ProxyJump none" from SSH config file Since OpenSSH 7.8, the ProxyJump directive accepts the value "none"[1] to override and clear a setting that might otherwise be contributed by another (wildcard) host entry. [1] https://bugzilla.mindrot.org/show_bug.cgi?id=2869 Change-Id: Ia35e82c6f8c58d5c6b8040cda7a07b220f43fc21 Signed-off-by: Thomas Wolf --- .../jgit/transport/sshd/ApacheSshTest.java | 15 +++++++++++++ .../jgit/transport/sshd/SshdSession.java | 3 ++- .../eclipse/jgit/transport/SshConstants.java | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) 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 85626d8ee..ccaf98ced 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 @@ -354,6 +354,21 @@ public void testJumpHost() throws Exception { } } + @Test + public void testJumpHostNone() throws Exception { + // Should not try to go through the non-existing proxy + cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, // + "Host server", // + "HostName localhost", // + "Port " + testPort, // + "User " + TEST_USER, // + "IdentityFile " + privateKey1.getAbsolutePath(), // + "ProxyJump none", // + "", // + "Host *", // + "ProxyJump " + TEST_USER + "@localhost:1234"); + } + @Test public void testJumpHostWrongKeyAtProxy() throws Exception { // Test that we find the proxy server's URI in the exception message 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 fb7500ffd..c270b4495 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 @@ -219,7 +219,8 @@ private List determineHops(List currentHops, HostConfigEntry hostConfig, String host) throws IOException { if (currentHops.isEmpty()) { String jumpHosts = hostConfig.getProperty(SshConstants.PROXY_JUMP); - if (!StringUtils.isEmptyOrNull(jumpHosts)) { + if (!StringUtils.isEmptyOrNull(jumpHosts) + && !SshConstants.NONE.equals(jumpHosts)) { try { return parseProxyJump(jumpHosts); } catch (URISyntaxException e) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java index 5cd5b334a..212a4e46c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java @@ -191,6 +191,26 @@ private SshConstants() { /** Flag value. */ public static final String FALSE = "false"; + /** + * Property value. Some keys accept a special 'none' value to override and + * clear a setting otherwise contributed by another host entry, for instance + * {@link #PROXY_COMMAND} or {@link #PROXY_JUMP}. Example: + * + *
+	 * Host bastion.example.org
+	 *   ProxyJump none
+	 *
+	 * Host *.example.org
+	 *   ProxyJump bastion.example.org
+	 * 
+ *

+ * OpenSSH supports this since OpenSSH 7.8. + *

+ * + * @since 6.0 + */ + public static final String NONE = "none"; + // Default identity file names /** Name of the default RSA private identity file. */ @@ -202,7 +222,7 @@ private SshConstants() { /** Name of the default ECDSA private identity file. */ public static final String ID_ECDSA = "id_ecdsa"; - /** Name of the default ECDSA private identity file. */ + /** Name of the default ED25519 private identity file. */ public static final String ID_ED25519 = "id_ed25519"; /** All known default identity file names. */ From 78b7d9e4fa1bdd3ba27b39190b45f7bdf0b61fff Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sat, 13 Nov 2021 19:00:58 +0100 Subject: [PATCH 23/24] Typo fix in o.e.j.ssh.{jsch,apache}/README.md Change-Id: Ia7da92421f8fecb2a175eb23ecfd04a67e0ca8cc Signed-off-by: Thomas Wolf --- org.eclipse.jgit.ssh.apache/README.md | 2 +- org.eclipse.jgit.ssh.jsch/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.ssh.apache/README.md b/org.eclipse.jgit.ssh.apache/README.md index 3bbda523b..cba87ac9c 100644 --- a/org.eclipse.jgit.ssh.apache/README.md +++ b/org.eclipse.jgit.ssh.apache/README.md @@ -58,4 +58,4 @@ To use a different SSH implementation: JGit has built-in support for not using any Java SSH implementation but an external SSH executable. To use an external SSH executable, set environment variable **GIT_SSH** to the path of the executable. JGit will create a sub-process to run the executable and -communicate with this sup-process to perform the git operation. +communicate with this sub-process to perform the git operation. diff --git a/org.eclipse.jgit.ssh.jsch/README.md b/org.eclipse.jgit.ssh.jsch/README.md index 48f43f0f8..b64bef3f7 100644 --- a/org.eclipse.jgit.ssh.jsch/README.md +++ b/org.eclipse.jgit.ssh.jsch/README.md @@ -56,4 +56,4 @@ FetchCommand fetch = git.fetch() JGit has built-in support for not using any Java SSH implementation but an external SSH executable. To use an external SSH executable, set environment variable **GIT_SSH** to the path of the executable. JGit will create a sub-process to run the executable and -communicate with this sup-process to perform the git operation. +communicate with this sub-process to perform the git operation. From ee28780bf2dfe8574905835d43b5bb0738ad81ad Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Mon, 1 Nov 2021 00:53:10 +0100 Subject: [PATCH 24/24] Make BinaryBlobException stackless We use BinaryBlobException to signal a binary blob was found and never make use of its stack trace. Suppress filling in the stack trace to avoid the performance penalty coming with that. See https://shipilev.net/blog/2014/exceptional-performance/ Change-Id: Iae1f1c19a1fa8aef4f6569822557171130299958 --- .../src/org/eclipse/jgit/errors/BinaryBlobException.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java index 58a121403..768931ca0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/BinaryBlobException.java @@ -22,4 +22,9 @@ public class BinaryBlobException extends Exception { * Construct a BinaryBlobException. */ public BinaryBlobException() {} + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } }