DescribeCommand: Support the "always" option

See:

https://git-scm.com/docs/git-describe#Documentation/git-describe.txt---always

Extend the tests accordingly.

Change-Id: Ibfcda338a246c8cba0df6b6e7b9bad76c9f8b593
Signed-off-by: Sebastian Schuberth <sschuberth@gmail.com>
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 bc3ac7a..fd3aa28 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
@@ -109,7 +109,7 @@ public void testDescribe() throws Exception {
 		assertNameStartsWith(c4, "3e563c5");
 
 		assertNull(describe(c1));
-		assertNull(describe(c1, true));
+		assertNull(describe(c1, true, false));
 		assertNull(describe(c1, "a*", "b*", "c*"));
 		assertNull(describe(c2, "bob*"));
 		assertNull(describe(c2, "?ob*"));
@@ -120,7 +120,7 @@ public void testDescribe() throws Exception {
 			assertEquals("alice-t1", describe(c2, "a*", "b*", "c*"));
 
 			assertEquals("bob-t2", describe(c3));
-			assertEquals("bob-t2-0-g44579eb", describe(c3, true));
+			assertEquals("bob-t2-0-g44579eb", describe(c3, true, false));
 			assertEquals("alice-t1-1-g44579eb", describe(c3, "alice*"));
 			assertEquals("alice-t1-1-g44579eb", describe(c3, "a??c?-t*"));
 			assertEquals("bob-t2", describe(c3, "bob*"));
@@ -129,7 +129,7 @@ public void testDescribe() throws Exception {
 
 			// the value verified with git-describe(1)
 			assertEquals("bob-t2-1-g3e563c5", describe(c4));
-			assertEquals("bob-t2-1-g3e563c5", describe(c4, true));
+			assertEquals("bob-t2-1-g3e563c5", describe(c4, true, false));
 			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*"));
@@ -137,6 +137,10 @@ public void testDescribe() throws Exception {
 			assertEquals(null, describe(c2));
 			assertEquals(null, describe(c3));
 			assertEquals(null, describe(c4));
+
+			assertEquals("3747db3", describe(c2, false, true));
+			assertEquals("44579eb", describe(c3, false, true));
+			assertEquals("3e563c5", describe(c4, false, true));
 		}
 
 		// test default target
@@ -169,6 +173,10 @@ public void testDescribeMultiMatch() throws Exception {
 		if (!useAnnotatedTags && !describeUseAllTags) {
 			assertEquals(null, describe(c1));
 			assertEquals(null, describe(c2));
+
+			assertEquals("fd70040", describe(c1, false, true));
+			assertEquals("b89dead", describe(c2, false, true));
+
 			return;
 		}
 
@@ -235,9 +243,11 @@ public void testDescribeBranch() throws Exception {
 			assertEquals("2 commits: c4 and c3", "t-2-g119892b", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("119892b", describe(c4, false, true));
 		}
 		assertNull(describe(c3));
-		assertNull(describe(c3, true));
+		assertNull(describe(c3, true, false));
 	}
 
 	private void branch(String name, ObjectId base) throws GitAPIException {
@@ -279,6 +289,9 @@ public void t1DominatesT2() throws Exception {
 		} else {
 			assertEquals(null, describe(c4));
 			assertEquals(null, describe(c3));
+
+			assertEquals("119892b", describe(c4, false, true));
+			assertEquals("0244e7f", describe(c3, false, true));
 		}
 	}
 
@@ -368,6 +381,8 @@ public void t1nearerT2() throws Exception {
 			assertEquals("t1-3-gbb389a4", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("bb389a4", describe(c4, false, true));
 		}
 	}
 
@@ -401,6 +416,8 @@ public void t1sameDepthT2() throws Exception {
 			assertEquals("t2-4-gbb389a4", describe(c4));
 		} else {
 			assertEquals(null, describe(c4));
+
+			assertEquals("bb389a4", describe(c4, false, true));
 		}
 	}
 
@@ -433,14 +450,14 @@ private static void touch(File f, String contents) throws Exception {
 		}
 	}
 
-	private String describe(ObjectId c1, boolean longDesc)
+	private String describe(ObjectId c1, boolean longDesc, boolean always)
 			throws GitAPIException, IOException {
 		return git.describe().setTarget(c1).setTags(describeUseAllTags)
-				.setLong(longDesc).call();
+				.setLong(longDesc).setAlways(always).call();
 	}
 
 	private String describe(ObjectId c1) throws GitAPIException, IOException {
-		return describe(c1, false);
+		return describe(c1, false, false);
 	}
 
 	private String describe(ObjectId c1, String... patterns) throws Exception {
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 6598124..db3a3d9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -112,6 +112,11 @@ public class DescribeCommand extends GitCommand<String> {
 	private boolean useTags;
 
 	/**
+	 * Whether to show a uniquely abbreviated commit hash as a fallback or not.
+	 */
+	private boolean always;
+
+	/**
 	 * Constructor for DescribeCommand.
 	 *
 	 * @param repo
@@ -197,6 +202,21 @@ public DescribeCommand setTags(boolean tags) {
 		return this;
 	}
 
+	/**
+	 * Always describe the commit by eventually falling back to a uniquely
+	 * abbreviated commit hash if no other name matches.
+	 *
+	 * @param always
+	 *            <code>true</code> enables falling back to a uniquely
+	 *            abbreviated commit hash
+	 * @return {@code this}
+	 * @since 5.4
+	 */
+	public DescribeCommand setAlways(boolean always) {
+		this.always = always;
+		return this;
+	}
+
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
 		return String.format(
@@ -399,8 +419,9 @@ String describe(ObjectId tip) throws IOException {
 			}
 
 			// if all the nodes are dominated by all the tags, the walk stops
-			if (candidates.isEmpty())
-				return null;
+			if (candidates.isEmpty()) {
+				return always ? w.getObjectReader().abbreviate(target).name() : null;
+			}
 
 			Candidate best = Collections.min(candidates,
 					(Candidate o1, Candidate o2) -> o1.depth - o2.depth);