DescribeCommand: Support configuring the hash abbreviation

Bug: 537883
Signed-off-by: Sebastian Schuberth <sebastian.schuberth@bosch.io>
Change-Id: Ic52dcebc564bbb0d934cc3a6205704b7aeaee30e
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 b460e3f..2051169 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
@@ -10,9 +10,11 @@
 package org.eclipse.jgit.api;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedWriter;
@@ -108,6 +110,21 @@ public void testDescribe() throws Exception {
 			assertEquals("3747db3", describe(c2, false, true));
 			assertEquals("44579eb", describe(c3, false, true));
 			assertEquals("3e563c5", describe(c4, false, true));
+
+			assertEquals("3747db3267", describe(c2, false, true, 10));
+			assertEquals("44579ebe7f", describe(c3, false, true, 10));
+			assertEquals("3e563c5592", describe(c4, false, true, 10));
+
+			assertEquals("3e", describe(c4, false, true, 2));
+			assertEquals("3e563c55927905f21e3bc7c00a3d83a31bf4ed3a",
+					describe(c4, false, true, 40));
+
+			assertThrows(StringIndexOutOfBoundsException.class,
+					() -> describe(c4, false, true, -10));
+			assertThrows(StringIndexOutOfBoundsException.class,
+					() -> describe(c4, false, true, 1));
+			assertThrows(StringIndexOutOfBoundsException.class,
+					() -> describe(c4, false, true, 41));
 		}
 
 		// test default target
@@ -474,10 +491,15 @@ private static void touch(File f, String contents) throws Exception {
 		}
 	}
 
+	private String describe(ObjectId c1, boolean longDesc, boolean always,
+			int abbrev) throws GitAPIException, IOException {
+		return git.describe().setTarget(c1).setTags(describeUseAllTags)
+				.setLong(longDesc).setAlways(always).setAbbrev(abbrev).call();
+	}
+
 	private String describe(ObjectId c1, boolean longDesc, boolean always)
 			throws GitAPIException, IOException {
-		return git.describe().setTarget(c1).setTags(describeUseAllTags)
-				.setLong(longDesc).setAlways(always).call();
+		return describe(c1, longDesc, always, OBJECT_ID_ABBREV_STRING_LENGTH);
 	}
 
 	private String describe(ObjectId c1) throws GitAPIException, IOException {
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 1e524fa..e572773 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -9,6 +9,7 @@
  */
 package org.eclipse.jgit.api;
 
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
 import static org.eclipse.jgit.lib.Constants.R_REFS;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
@@ -89,6 +90,11 @@ public class DescribeCommand extends GitCommand<String> {
 	private boolean always;
 
 	/**
+	 * The prefix length to use when abbreviating a commit hash.
+	 */
+	private int abbrev = OBJECT_ID_ABBREV_STRING_LENGTH;
+
+	/**
 	 * Constructor for DescribeCommand.
 	 *
 	 * @param repo
@@ -205,12 +211,25 @@ public DescribeCommand setAlways(boolean always) {
 		return this;
 	}
 
+	/**
+	 * Sets the prefix length to use when abbreviating an object SHA-1.
+	 *
+	 * @param abbrev
+	 *            minimum length of the abbreviated string. Must be in the range
+	 *            [2, {@value Constants#OBJECT_ID_STRING_LENGTH}].
+	 * @return {@code this}
+	 * @since 6.1
+	 */
+	public DescribeCommand setAbbrev(int abbrev) {
+		this.abbrev = abbrev;
+		return this;
+	}
+
 	private String longDescription(Ref tag, int depth, ObjectId tip)
 			throws IOException {
-		return String.format(
-				"%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
-				Integer.valueOf(depth), w.getObjectReader().abbreviate(tip)
-						.name());
+		return String.format("%s-%d-g%s", formatRefName(tag.getName()), //$NON-NLS-1$
+				Integer.valueOf(depth),
+				w.getObjectReader().abbreviate(tip, abbrev).name());
 	}
 
 	/**
@@ -413,7 +432,9 @@ String describe(ObjectId tip) throws IOException {
 
 			// if all the nodes are dominated by all the tags, the walk stops
 			if (candidates.isEmpty()) {
-				return always ? w.getObjectReader().abbreviate(target).name() : null;
+				return always
+						? w.getObjectReader().abbreviate(target, abbrev).name()
+						: null;
 			}
 
 			Candidate best = Collections.min(candidates,