Merge branch 'stable-5.0'

* stable-5.0:
  Prepare 5.0.1-SNAPSHOT builds
  JGit v5.0.0.201806131550-r
  JGit v5.0.0.201806131210-r
  Downgrade Apache httpclient to 4.5.2.v20170210-0925
  RefUpdateTest: Refactor to not use deprecated Repository#getAllRefs
  Propagate failure of ssh command to caller of SshSupport
  Make JGit describe behaves same as c-git for lightweight tags
  Fix issues with LFS on GitHub (SSH)

Change-Id: I0471440919adfdbfc72996711d9e0bbd1f3cf477
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java
index 5320af0..f90a003 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/errors/LfsConfigInvalidException.java
@@ -63,4 +63,17 @@ public LfsConfigInvalidException(String msg) {
 		super(msg);
 	}
 
+	/**
+	 * Constructor for LfsConfigInvalidException.
+	 *
+	 * @param msg
+	 *            the error description
+	 * @param e
+	 *            cause of this exception
+	 * @since 5.0
+	 */
+	public LfsConfigInvalidException(String msg, Exception e) {
+		super(msg, e);
+	}
+
 }
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
index 3ac6992..955eca0 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
@@ -49,6 +49,7 @@
 
 import java.io.IOException;
 import java.net.ProxySelector;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.LinkedList;
@@ -56,6 +57,7 @@
 import java.util.TreeMap;
 
 import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.CommandFailedException;
 import org.eclipse.jgit.lfs.LfsPointer;
 import org.eclipse.jgit.lfs.Protocol;
 import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
@@ -74,7 +76,7 @@
  */
 public class LfsConnectionFactory {
 
-	private static final int SSH_AUTH_TIMEOUT_SECONDS = 5;
+	private static final int SSH_AUTH_TIMEOUT_SECONDS = 30;
 	private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
 	private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$
 	private static final Map<String, AuthCache> sshAuthCache = new TreeMap<>();
@@ -128,6 +130,7 @@ private static String getLfsUrl(Repository db, String purpose,
 		String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
 				null,
 				ConfigConstants.CONFIG_KEY_URL);
+		Exception ex = null;
 		if (lfsUrl == null) {
 			String remoteUrl = null;
 			for (String remote : db.getRemoteNames()) {
@@ -146,38 +149,44 @@ private static String getLfsUrl(Repository db, String purpose,
 				break;
 			}
 			if (lfsUrl == null && remoteUrl != null) {
-				lfsUrl = discoverLfsUrl(db, purpose, additionalHeaders,
-						remoteUrl);
+				try {
+					lfsUrl = discoverLfsUrl(db, purpose, additionalHeaders,
+							remoteUrl);
+				} catch (URISyntaxException | IOException
+						| CommandFailedException e) {
+					ex = e;
+				}
 			} else {
 				lfsUrl = lfsUrl + Protocol.INFO_LFS_ENDPOINT;
 			}
 		}
 		if (lfsUrl == null) {
+			if (ex != null) {
+				throw new LfsConfigInvalidException(
+						LfsText.get().lfsNoDownloadUrl, ex);
+			}
 			throw new LfsConfigInvalidException(LfsText.get().lfsNoDownloadUrl);
 		}
 		return lfsUrl;
 	}
 
 	private static String discoverLfsUrl(Repository db, String purpose,
-			Map<String, String> additionalHeaders, String remoteUrl) {
-		try {
-			URIish u = new URIish(remoteUrl);
-			if (SCHEME_SSH.equals(u.getScheme())) {
-				Protocol.ExpiringAction action = getSshAuthentication(
-						db, purpose, remoteUrl, u);
-				additionalHeaders.putAll(action.header);
-				return action.href;
-			} else {
-				return remoteUrl + Protocol.INFO_LFS_ENDPOINT;
-			}
-		} catch (Exception e) {
-			return null; // could not discover
+			Map<String, String> additionalHeaders, String remoteUrl)
+			throws URISyntaxException, IOException, CommandFailedException {
+		URIish u = new URIish(remoteUrl);
+		if (u.getScheme() == null || SCHEME_SSH.equals(u.getScheme())) {
+			Protocol.ExpiringAction action = getSshAuthentication(db, purpose,
+					remoteUrl, u);
+			additionalHeaders.putAll(action.header);
+			return action.href;
+		} else {
+			return remoteUrl + Protocol.INFO_LFS_ENDPOINT;
 		}
 	}
 
 	private static Protocol.ExpiringAction getSshAuthentication(
 			Repository db, String purpose, String remoteUrl, URIish u)
-			throws IOException {
+			throws IOException, CommandFailedException {
 		AuthCache cached = sshAuthCache.get(remoteUrl);
 		Protocol.ExpiringAction action = null;
 		if (cached != null && cached.validUntil > System.currentTimeMillis()) {
@@ -226,8 +235,10 @@ private static Protocol.ExpiringAction getSshAuthentication(
 				.create(contentUrl, HttpSupport
 						.proxyFor(ProxySelector.getDefault(), contentUrl));
 		contentServerConn.setRequestMethod(method);
-		action.header
-				.forEach((k, v) -> contentServerConn.setRequestProperty(k, v));
+		if (action.header != null) {
+			action.header.forEach(
+					(k, v) -> contentServerConn.setRequestProperty(k, v));
+		}
 		if (contentUrl.getProtocol().equals(SCHEME_HTTPS)
 				&& !repo.getConfig().getBoolean(HttpConfig.HTTP,
 						HttpConfig.SSL_VERIFY_KEY, true)) {
@@ -241,7 +252,13 @@ private static Protocol.ExpiringAction getSshAuthentication(
 	}
 
 	private static String extractProjectName(URIish u) {
-		String path = u.getPath().substring(1);
+		String path = u.getPath();
+
+		// begins with a slash if the url contains a port (gerrit vs. github).
+		if (path.startsWith("/")) { //$NON-NLS-1$
+			path = path.substring(1);
+		}
+
 		if (path.endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT)) {
 			return path.substring(0, path.length() - 4);
 		} else {
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 564b493..6f57c9d 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 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <?pde?>
 <!-- generated with https://github.com/mbarbero/fr.obeo.releng.targetplatform -->
-<target name="jgit-4.8" sequenceNumber="1528373976">
+<target name="jgit-4.8" sequenceNumber="1528872727">
   <locations>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.jetty.client" version="9.4.8.v20171121"/>
@@ -33,8 +33,6 @@
       <unit id="org.apache.commons.logging.source" version="1.1.1.v201101211721"/>
       <unit id="org.apache.httpcomponents.httpcore" version="4.4.6.v20170210-0925"/>
       <unit id="org.apache.httpcomponents.httpcore.source" version="4.4.6.v20170210-0925"/>
-      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20180410-1551"/>
-      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20180410-1551"/>
       <unit id="org.apache.log4j" version="1.2.15.v201012070815"/>
       <unit id="org.apache.log4j.source" version="1.2.15.v201012070815"/>
       <unit id="org.kohsuke.args4j" version="2.33.0.v20160323-2218"/>
@@ -69,6 +67,11 @@
       <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20180606145124/repository"/>
     </location>
     <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
+      <unit id="org.apache.httpcomponents.httpclient" version="4.5.2.v20170210-0925"/>
+      <unit id="org.apache.httpcomponents.httpclient.source" version="4.5.2.v20170210-0925"/>
+      <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20170919201930/repository/"/>
+    </location>
+    <location includeMode="slicer" includeAllPlatforms="false" includeSource="true" includeConfigurePhase="true" type="InstallableUnit">
       <unit id="org.eclipse.osgi" version="0.0.0"/>
       <repository location="http://download.eclipse.org/releases/oxygen/"/>
     </location>
diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd
index 8497c2c..808b9d6 100644
--- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd
+++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20180606145124-Photon.tpd
@@ -12,8 +12,6 @@
 	org.apache.commons.logging.source [1.1.1.v201101211721,1.1.1.v201101211721]
 	org.apache.httpcomponents.httpcore [4.4.6.v20170210-0925,4.4.6.v20170210-0925]
 	org.apache.httpcomponents.httpcore.source [4.4.6.v20170210-0925,4.4.6.v20170210-0925]
-	org.apache.httpcomponents.httpclient [4.5.2.v20180410-1551,4.5.2.v20180410-1551]
-	org.apache.httpcomponents.httpclient.source [4.5.2.v20180410-1551,4.5.2.v20180410-1551]
 	org.apache.log4j [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.apache.log4j.source [1.2.15.v201012070815,1.2.15.v201012070815]
 	org.kohsuke.args4j [2.33.0.v20160323-2218,2.33.0.v20160323-2218]
@@ -45,4 +43,10 @@
 	org.slf4j.impl.log4j12.source [1.7.2.v20131105-2200,1.7.2.v20131105-2200]
 	com.jcraft.jzlib [1.1.1.v201205102305,1.1.1.v201205102305]
 	com.jcraft.jzlib.source [1.1.1.v201205102305,1.1.1.v201205102305]
+}
+
+location "http://download.eclipse.org/tools/orbit/downloads/drops/R20170919201930/repository/" {
+	// platform uses this outdated version of httpclient in Photon
+	org.apache.httpcomponents.httpclient [4.5.2.v20170210-0925,4.5.2.v20170210-0925]
+	org.apache.httpcomponents.httpclient.source [4.5.2.v20170210-0925,4.5.2.v20170210-0925]
 }
\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
index 79da2da..a422ef9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/DescribeCommandTest.java
@@ -54,7 +54,6 @@
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
-import org.eclipse.jgit.errors.InvalidPatternException;
 import org.eclipse.jgit.junit.RepositoryTestCase;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
@@ -68,13 +67,18 @@ public class DescribeCommandTest extends RepositoryTestCase {
 
 	private Git git;
 
-	@Parameter
+	@Parameter(0)
 	public boolean useAnnotatedTags;
 
-	@Parameters
+	@Parameter(1)
+	public boolean describeUseAllTags;
+
+	@Parameters(name = "git tag -a {0}?-a: with git describe {1}?--tags:")
 	public static Collection<Boolean[]> getUseAnnotatedTagsValues() {
-		return Arrays.asList(new Boolean[][] { { Boolean.TRUE },
-				{ Boolean.FALSE } });
+		return Arrays.asList(new Boolean[][] { { Boolean.TRUE, Boolean.FALSE },
+				{ Boolean.FALSE, Boolean.FALSE },
+				{ Boolean.TRUE, Boolean.TRUE },
+				{ Boolean.FALSE, Boolean.TRUE } });
 	}
 
 	@Override
@@ -99,35 +103,52 @@ public void testDescribe() throws Exception {
 		tag("bob-t2");
 
 		ObjectId c4 = modify("ddd");
+		assertNameStartsWith(c4, "3e563c5");
 
 		assertNull(describe(c1));
 		assertNull(describe(c1, true));
 		assertNull(describe(c1, "a*", "b*", "c*"));
-
-		assertEquals("alice-t1", describe(c2));
-		assertEquals("alice-t1", describe(c2, "alice*"));
 		assertNull(describe(c2, "bob*"));
 		assertNull(describe(c2, "?ob*"));
-		assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
 
-		assertEquals("bob-t2", describe(c3));
-		assertEquals("bob-t2-0-g44579eb", describe(c3, true));
-		assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
-		assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
-		assertEquals("bob-t2", describe(c3, "bob*"));
-		assertEquals("bob-t2", describe(c3, "?ob*"));
-		assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("alice-t1", describe(c2));
+			assertEquals("alice-t1", describe(c2, "alice*"));
+			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
 
-		assertNameStartsWith(c4, "3e563c5");
-		// the value verified with git-describe(1)
-		assertEquals("bob-t2-1-g3e563c5", describe(c4));
-		assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
-		assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
-		assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
-		assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
+			assertEquals("bob-t2", describe(c3));
+			assertEquals("bob-t2-0-g44579eb", describe(c3, true));
+			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
+			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
+			assertEquals("bob-t2", describe(c3, "bob*"));
+			assertEquals("bob-t2", describe(c3, "?ob*"));
+			assertEquals("bob-t2", describe(c3, "a*", "b*", "c*"));
+
+			// the value verified with git-describe(1)
+			assertEquals("bob-t2-1-g3e563c5", describe(c4));
+			assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
+			assertEquals("alice-t1-2-g3e563c5", describe(c4, "alice*"));
+			assertEquals("bob-t2-1-g3e563c5", describe(c4, "bob*"));
+			assertEquals("bob-t2-1-g3e563c5", describe(c4, "a*", "b*", "c*"));
+		} else {
+			assertEquals(null, describe(c2));
+			assertEquals(null, describe(c3));
+			assertEquals(null, describe(c4));
+		}
 
 		// test default target
-		assertEquals("bob-t2-1-g3e563c5", git.describe().call());
+		if (useAnnotatedTags) {
+			assertEquals("bob-t2-1-g3e563c5", git.describe().call());
+			assertEquals("bob-t2-1-g3e563c5",
+					git.describe().setTags(false).call());
+			assertEquals("bob-t2-1-g3e563c5",
+					git.describe().setTags(true).call());
+		} else {
+			assertEquals(null, git.describe().call());
+			assertEquals(null, git.describe().setTags(false).call());
+			assertEquals("bob-t2-1-g3e563c5",
+					git.describe().setTags(true).call());
+		}
 	}
 
 	@Test
@@ -137,7 +158,14 @@ public void testDescribeMultiMatch() throws Exception {
 		tag("v1.1.1");
 		ObjectId c2 = modify("bbb");
 
-		// Ensure that if we're interested in any tags, we get the first match as per Git behaviour
+		if (!useAnnotatedTags && !describeUseAllTags) {
+			assertEquals(null, describe(c1));
+			assertEquals(null, describe(c2));
+			return;
+		}
+
+		// Ensure that if we're interested in any tags, we get the first match
+		// as per Git behaviour
 		assertEquals("v1.0.0", describe(c1));
 		assertEquals("v1.0.0-1-g3747db3", describe(c2));
 
@@ -179,7 +207,11 @@ public void testDescribeBranch() throws Exception {
 		ObjectId c4 = merge(c2);
 
 		assertNameStartsWith(c4, "119892b");
-		assertEquals("t-2-g119892b", describe(c4)); // 2 commits: c4 and c3
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
+		} else {
+			assertEquals(null, describe(c4));
+		}
 		assertNull(describe(c3));
 		assertNull(describe(c3, true));
 	}
@@ -211,14 +243,76 @@ public void t1DominatesT2() throws Exception {
 		branch("b", c1);
 
 		ObjectId c3 = modify("ccc");
+		assertNameStartsWith(c3, "0244e7f");
 
 		ObjectId c4 = merge(c2);
 
 		assertNameStartsWith(c4, "119892b");
-		assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
+
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
+			assertEquals("t1-1-g0244e7f", describe(c3));
+		} else {
+			assertEquals(null, describe(c4));
+			assertEquals(null, describe(c3));
+		}
+	}
+
+	/**
+	 * When t1 annotated dominates t2 lightweight tag
+	 *
+	 * <pre>
+	 * t1 -+-> t2  -
+	 *     |       |
+	 *     +-> c3 -+-> c4
+	 * </pre>
+	 *
+	 * @throws Exception
+	 */
+	@Test
+	public void t1AnnotatedDominatesT2lightweight() throws Exception {
+		ObjectId c1 = modify("aaa");
+		tag("t1", useAnnotatedTags);
+
+		ObjectId c2 = modify("bbb");
+		tag("t2", false);
+
+		assertNameStartsWith(c2, "3747db3");
+		if (useAnnotatedTags && !describeUseAllTags) {
+			assertEquals(
+					"only annotated tag t1 expected to be used for describe",
+					"t1-1-g3747db3", describe(c2)); // 1 commits: t2 overridden
+													// by t1
+		} else if (!useAnnotatedTags && !describeUseAllTags) {
+			assertEquals("no commits to describe expected", null, describe(c2));
+		} else {
+			assertEquals("lightweight tag t2 expected in describe", "t2",
+					describe(c2));
+		}
+
+		branch("b", c1);
+
+		ObjectId c3 = modify("ccc");
 
 		assertNameStartsWith(c3, "0244e7f");
-		assertEquals("t1-1-g0244e7f", describe(c3));
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("t1-1-g0244e7f", describe(c3));
+		}
+
+		ObjectId c4 = merge(c2);
+
+		assertNameStartsWith(c4, "119892b");
+		if (describeUseAllTags) {
+			assertEquals(
+					"2 commits for describe commit increment expected since lightweight tag: c4 and c3",
+					"t2-2-g119892b", describe(c4)); // 2 commits: c4 and c3
+		} else if (!useAnnotatedTags && !describeUseAllTags) {
+			assertEquals("no matching commits expected", null, describe(c4));
+		} else {
+			assertEquals(
+					"3 commits for describe commit increment expected since annotated tag: c4 and c3 and c2",
+					"t1-3-g119892b", describe(c4)); //
+		}
 	}
 
 	/**
@@ -246,7 +340,11 @@ public void t1nearerT2() throws Exception {
 		ObjectId c4 = merge(t1);
 
 		assertNameStartsWith(c4, "bb389a4");
-		assertEquals("t1-3-gbb389a4", describe(c4));
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("t1-3-gbb389a4", describe(c4));
+		} else {
+			assertEquals(null, describe(c4));
+		}
 	}
 
 	/**
@@ -275,7 +373,11 @@ public void t1sameDepthT2() throws Exception {
 		ObjectId c4 = merge(c2);
 
 		assertNameStartsWith(c4, "bb389a4");
-		assertEquals("t2-4-gbb389a4", describe(c4));
+		if (useAnnotatedTags || describeUseAllTags) {
+			assertEquals("t2-4-gbb389a4", describe(c4));
+		} else {
+			assertEquals(null, describe(c4));
+		}
 	}
 
 	private ObjectId merge(ObjectId c2) throws GitAPIException {
@@ -289,10 +391,15 @@ private ObjectId modify(String content) throws Exception {
 	}
 
 	private void tag(String tag) throws GitAPIException {
+		tag(tag, this.useAnnotatedTags);
+	}
+
+	private void tag(String tag, boolean annotatedTag) throws GitAPIException {
 		TagCommand tagCommand = git.tag().setName(tag)
-				.setAnnotated(useAnnotatedTags);
-		if (useAnnotatedTags)
+				.setAnnotated(annotatedTag);
+		if (annotatedTag) {
 			tagCommand.setMessage(tag);
+		}
 		tagCommand.call();
 	}
 
@@ -304,15 +411,17 @@ private static void touch(File f, String contents) throws Exception {
 
 	private String describe(ObjectId c1, boolean longDesc)
 			throws GitAPIException, IOException {
-		return git.describe().setTarget(c1).setLong(longDesc).call();
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setLong(longDesc).call();
 	}
 
 	private String describe(ObjectId c1) throws GitAPIException, IOException {
 		return describe(c1, false);
 	}
 
-	private String describe(ObjectId c1, String... patterns) throws GitAPIException, IOException, InvalidPatternException {
-		return git.describe().setTarget(c1).setMatch(patterns).call();
+	private String describe(ObjectId c1, String... patterns) throws Exception {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setMatch(patterns).call();
 	}
 
 	private static void assertNameStartsWith(ObjectId c4, String prefix) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
index b2fae31..e1adeed 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
@@ -62,6 +62,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
@@ -110,11 +111,21 @@ private void delete(final RefUpdate ref, final Result expected,
 		delete(db, ref, expected, exists, removed);
 	}
 
-	private void delete(Repository repo, final RefUpdate ref, final Result expected,
-			final boolean exists, final boolean removed) throws IOException {
-		assertEquals(exists, repo.getAllRefs().containsKey(ref.getName()));
+	private void delete(Repository repo, final RefUpdate ref,
+			final Result expected, final boolean exists, final boolean removed)
+			throws IOException {
+		assertEquals(exists, getRef(repo, ref.getName()).isPresent());
 		assertEquals(expected, ref.delete());
-		assertEquals(!removed, repo.getAllRefs().containsKey(ref.getName()));
+		assertEquals(!removed, getRef(repo, ref.getName()).isPresent());
+	}
+
+	private Optional<Ref> getRef(Repository repo, String name)
+			throws IOException {
+		return getRef(repo.getRefDatabase().getRefs(), name);
+	}
+
+	private Optional<Ref> getRef(List<Ref> refs, String name) {
+		return refs.stream().filter(r -> r.getName().equals(name)).findAny();
 	}
 
 	@Test
@@ -125,8 +136,7 @@ public void testNoCacheObjectIdSubclass() throws IOException {
 		ru.setNewObjectId(newid);
 		Result update = ru.update();
 		assertEquals(Result.NEW, update);
-		final Ref r = db.getAllRefs().get(newRef);
-		assertNotNull(r);
+		final Ref r = getRef(db, newRef).get();
 		assertEquals(newRef, r.getName());
 		assertNotNull(r.getObjectId());
 		assertNotSame(newid, r.getObjectId());
@@ -378,10 +388,10 @@ public void testDeleteWithoutHead() throws IOException {
 
 	@Test
 	public void testRefKeySameAsName() {
+		@SuppressWarnings("deprecation")
 		Map<String, Ref> allRefs = db.getAllRefs();
 		for (Entry<String, Ref> e : allRefs.entrySet()) {
 			assertEquals(e.getKey(), e.getValue().getName());
-
 		}
 	}
 
@@ -520,8 +530,8 @@ public void testUpdateRefNoChange() throws IOException {
 	 */
 	@Test
 	public void testRefsCacheAfterUpdate() throws Exception {
-		// Do not use the defalt repo for this case.
-		Map<String, Ref> allRefs = db.getAllRefs();
+		// Do not use the default repo for this case.
+		List<Ref> allRefs = db.getRefDatabase().getRefs();
 		ObjectId oldValue = db.resolve("HEAD");
 		ObjectId newValue = db.resolve("HEAD^");
 		// first make HEAD refer to loose ref
@@ -537,9 +547,9 @@ public void testRefsCacheAfterUpdate() throws Exception {
 		update = updateRef.update();
 		assertEquals(Result.FAST_FORWARD, update);
 
-		allRefs = db.getAllRefs();
-		Ref master = allRefs.get("refs/heads/master");
-		Ref head = allRefs.get("HEAD");
+		allRefs = db.getRefDatabase().getRefs();
+		Ref master = getRef(allRefs, "refs/heads/master").get();
+		Ref head = getRef(allRefs, "HEAD").get();
 		assertEquals("refs/heads/master", master.getName());
 		assertEquals("HEAD", head.getName());
 		assertTrue("is symbolic reference", head.isSymbolic());
@@ -557,8 +567,8 @@ public void testRefsCacheAfterUpdate() throws Exception {
 	 */
 	@Test
 	public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
-		// Do not use the defalt repo for this case.
-		Map<String, Ref> allRefs = db.getAllRefs();
+		// Do not use the default repo for this case.
+		List<Ref> allRefs = db.getRefDatabase().getRefs();
 		ObjectId oldValue = db.resolve("HEAD");
 		writeSymref(Constants.HEAD, "refs/heads/newref");
 		RefUpdate updateRef = db.updateRef(Constants.HEAD);
@@ -567,9 +577,9 @@ public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
 		Result update = updateRef.update();
 		assertEquals(Result.NEW, update);
 
-		allRefs = db.getAllRefs();
-		Ref head = allRefs.get("HEAD");
-		Ref newref = allRefs.get("refs/heads/newref");
+		allRefs = db.getRefDatabase().getRefs();
+		Ref head = getRef(allRefs, "HEAD").get();
+		Ref newref = getRef(allRefs, "refs/heads/newref").get();
 		assertEquals("refs/heads/newref", newref.getName());
 		assertEquals("HEAD", head.getName());
 		assertTrue("is symbolic reference", head.isSymbolic());
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 b608ca8..809bdf2 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -629,6 +629,7 @@
 sourceRefDoesntResolveToAnyObject=Source ref {0} doesn''t resolve to any object.
 sourceRefNotSpecifiedForRefspec=Source ref not specified for refspec: {0}
 squashCommitNotUpdatingHEAD=Squash commit -- not updating HEAD
+sshCommandFailed=Execution of ssh command ''{0}'' failed with error ''{1}''
 sshUserNameError=Jsch error: failed to set SSH user name correctly to ''{0}''; using ''{1}'' picked up from SSH config file.
 sslFailureExceptionMessage=Secure connection to {0} could not be stablished because of SSL problems
 sslFailureInfo=A secure connection to {0}\ncould not be established because the server''s certificate could not be validated.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index dc605a9..4d5e499 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -104,6 +104,11 @@ public class DescribeCommand extends GitCommand<String> {
 	private List<IMatcher> matchers = new ArrayList<>();
 
 	/**
+	 * Whether to use all tags (incl. lightweight) or not
+	 */
+	private boolean useTags = false;
+
+	/**
 	 * Constructor for DescribeCommand.
 	 *
 	 * @param repo
@@ -173,6 +178,22 @@ public DescribeCommand setLong(boolean longDesc) {
 		return this;
 	}
 
+	/**
+	 * Instead of using only the annotated tags, use any tag found in refs/tags
+	 * namespace. This option enables matching lightweight (non-annotated) tags
+	 * or not.
+	 *
+	 * @param tags
+	 *            <code>true</code> enables matching lightweight (non-annotated)
+	 *            tags like setting option --tags in c git
+	 * @return {@code this}
+	 * @since 5.0
+	 */
+	public DescribeCommand setTags(boolean tags) {
+		this.useTags = tags;
+		return this;
+	}
+
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
 		return String.format(
@@ -246,13 +267,14 @@ private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException {
 	public String call() throws GitAPIException {
 		try {
 			checkCallable();
-
-			if (target == null)
+			if (target == null) {
 				setTarget(Constants.HEAD);
+			}
 
 			Collection<Ref> tagList = repo.getRefDatabase()
 					.getRefsByPrefix(R_TAGS);
 			Map<ObjectId, List<Ref>> tags = tagList.stream()
+					.filter(this::filterLightweightTags)
 					.collect(Collectors.groupingBy(this::getObjectIdFromRef));
 
 			// combined flags of all the candidate instances
@@ -376,4 +398,22 @@ public int compare(Candidate o1, Candidate o2) {
 			w.close();
 		}
 	}
+
+	/**
+	 * Whether we use lightweight tags or not for describe Candidates
+	 *
+	 * @param ref
+	 *            reference under inspection
+	 * @return true if it should be used for describe or not regarding
+	 *         {@link org.eclipse.jgit.api.DescribeCommand#useTags}
+	 */
+	@SuppressWarnings("null")
+	private boolean filterLightweightTags(Ref ref) {
+		ObjectId id = ref.getObjectId();
+		try {
+			return this.useTags || (id != null && (w.parseTag(id) != null));
+		} catch (IOException e) {
+			return false;
+		}
+	}
 }
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 2ac75e1..f3029bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -690,6 +690,7 @@ public static JGitText get() {
 	/***/ public String sourceRefDoesntResolveToAnyObject;
 	/***/ public String sourceRefNotSpecifiedForRefspec;
 	/***/ public String squashCommitNotUpdatingHEAD;
+	/***/ public String sshCommandFailed;
 	/***/ public String sshUserNameError;
 	/***/ public String sslFailureExceptionMessage;
 	/***/ public String sslFailureInfo;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
index 96123ea..913aa72 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
@@ -42,12 +42,12 @@
  */
 package org.eclipse.jgit.util;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
+import java.text.MessageFormat;
 
 import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.errors.CommandFailedException;
+import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.transport.CredentialsProvider;
 import org.eclipse.jgit.transport.RemoteSession;
 import org.eclipse.jgit.transport.SshSessionFactory;
@@ -76,27 +76,44 @@ public class SshSupport {
 	 * @param command
 	 *            the remote command to execute.
 	 * @param timeout
-	 *            a timeout in seconds.
-	 * @return The first line of output read from stdout. Stderr is discarded.
+	 *            a timeout in seconds. The timeout may be exceeded in corner
+	 *            cases.
+	 * @return The entire output read from stdout.
 	 * @throws IOException
+	 * @throws CommandFailedException
+	 *             if the ssh command execution failed, error message contains
+	 *             the content of stderr.
 	 */
 	public static String runSshCommand(URIish sshUri,
 			@Nullable CredentialsProvider provider, FS fs, String command,
-			int timeout) throws IOException {
+			int timeout) throws IOException, CommandFailedException {
 		RemoteSession session = null;
 		Process process = null;
 		StreamCopyThread errorThread = null;
-		try (MessageWriter stderr = new MessageWriter()) {
+		StreamCopyThread outThread = null;
+		CommandFailedException failure = null;
+		@SuppressWarnings("resource")
+		MessageWriter stderr = new MessageWriter();
+		try (MessageWriter stdout = new MessageWriter()) {
 			session = SshSessionFactory.getInstance().getSession(sshUri,
 					provider, fs, 1000 * timeout);
 			process = session.exec(command, 0);
 			errorThread = new StreamCopyThread(process.getErrorStream(),
 					stderr.getRawStream());
 			errorThread.start();
-			try (BufferedReader reader = new BufferedReader(
-					new InputStreamReader(process.getInputStream(),
-							Constants.CHARSET))) {
-				return reader.readLine();
+			outThread = new StreamCopyThread(process.getInputStream(),
+					stdout.getRawStream());
+			outThread.start();
+			try {
+				// waitFor with timeout has a bug - JSch' exitValue() throws the
+				// wrong exception type :(
+				if (process.waitFor() == 0) {
+					return stdout.toString();
+				} else {
+					return null; // still running after timeout
+				}
+			} catch (InterruptedException e) {
+				return null; // error
 			}
 		} finally {
 			if (errorThread != null) {
@@ -108,12 +125,31 @@ public static String runSshCommand(URIish sshUri,
 					errorThread = null;
 				}
 			}
+			if (outThread != null) {
+				try {
+					outThread.halt();
+				} catch (InterruptedException e) {
+					// Stop waiting and return anyway.
+				} finally {
+					outThread = null;
+				}
+			}
 			if (process != null) {
+				if (process.exitValue() != 0) {
+					failure = new CommandFailedException(process.exitValue(),
+							MessageFormat.format(
+							JGitText.get().sshCommandFailed, command,
+							stderr.toString()));
+				}
 				process.destroy();
 			}
+			stderr.close();
 			if (session != null) {
 				SshSessionFactory.getInstance().releaseSession(session);
 			}
+			if (failure != null) {
+				throw failure;
+			}
 		}
 	}