Fail clone if initial branch doesn't exist in remote repository

jgit clone --branch foo <url>

did not fail if the remote branch "foo" didn't exist in the remote
repository being cloned.

Bug: 546580
Change-Id: I55648ad3a39da4a5711dfa8e6d6682bb8190a6d6
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
index 2f09b7f..4cbd61c 100644
--- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CloneTest.java
@@ -11,7 +11,9 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -25,6 +27,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.RefSpec;
@@ -64,6 +67,45 @@
 		assertEquals("expected 1 branch", 1, branches.size());
 	}
 
+	@Test
+	public void testCloneInitialBranch() throws Exception {
+		createInitialCommit();
+
+		File gitDir = db.getDirectory();
+		String sourceURI = gitDir.toURI().toString();
+		File target = createTempDirectory("target");
+		String cmd = "git clone --branch master " + sourceURI + " "
+				+ shellQuote(target.getPath());
+		String[] result = execute(cmd);
+		assertArrayEquals(new String[] {
+				"Cloning into '" + target.getPath() + "'...", "", "" }, result);
+
+		Git git2 = Git.open(target);
+		List<Ref> branches = git2.branchList().call();
+		assertEquals("expected 1 branch", 1, branches.size());
+
+		Repository db2 = git2.getRepository();
+		ObjectId head = db2.resolve("HEAD");
+		assertNotNull(head);
+		assertNotEquals(ObjectId.zeroId(), head);
+		ObjectId master = db2.resolve("master");
+		assertEquals(head, master);
+	}
+
+	@Test
+	public void testCloneInitialBranchMissing() throws Exception {
+		createInitialCommit();
+
+		File gitDir = db.getDirectory();
+		String sourceURI = gitDir.toURI().toString();
+		File target = createTempDirectory("target");
+		String cmd = "git clone --branch foo " + sourceURI + " "
+				+ shellQuote(target.getPath());
+		Die e = assertThrows(Die.class, () -> execute(cmd));
+		assertEquals("Remote branch 'foo' not found in upstream origin",
+				e.getMessage());
+	}
+
 	private RevCommit createInitialCommit() throws Exception {
 		JGitTestUtil.writeTrashFile(db, "hello.txt", "world");
 		git.add().addFilepattern("hello.txt").call();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
index fe94b03..f28915d 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java
@@ -18,6 +18,7 @@
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.errors.InvalidRemoteException;
+import org.eclipse.jgit.api.errors.TransportException;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.TextProgressMonitor;
@@ -110,6 +111,8 @@
 			db = command.call().getRepository();
 			if (msgs && db.resolve(Constants.HEAD) == null)
 				outw.println(CLIText.get().clonedEmptyRepository);
+		} catch (TransportException e) {
+			throw die(e.getMessage(), e);
 		} catch (InvalidRemoteException e) {
 			throw die(MessageFormat.format(CLIText.get().doesNotExist,
 					sourceUri), e);
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 1924621..6d4a077 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -568,6 +568,7 @@
 reftableDirExists=reftable dir exists and is nonempty
 reftableRecordsMustIncrease=records must be increasing: last {0}, this {1}
 refUpdateReturnCodeWas=RefUpdate return code was: {0}
+remoteBranchNotFound=Remote branch ''{0}'' not found in upstream origin
 remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated
 remoteDoesNotHaveSpec=Remote does not have {0} available for fetch.
 remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index aba86fc..cf7bc1f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -297,6 +297,7 @@
 			command.setTagOpt(
 					fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW);
 		}
+		command.setInitialBranch(branch);
 		configure(command);
 
 		return command.call();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 033dd60..90c1515 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -74,6 +74,8 @@
 
 	private boolean isForceUpdate;
 
+	private String initialBranch;
+
 	/**
 	 * Callback for status of fetch operation.
 	 *
@@ -209,7 +211,7 @@
 			transport.setFetchThin(thin);
 			configure(transport);
 			FetchResult result = transport.fetch(monitor,
-					applyOptions(refSpecs));
+					applyOptions(refSpecs), initialBranch);
 			if (!repo.isBare()) {
 				fetchSubmodules(result);
 			}
@@ -488,6 +490,24 @@
 	}
 
 	/**
+	 * Set the initial branch
+	 *
+	 * @param branch
+	 *            the initial branch to check out when cloning the repository.
+	 *            Can be specified as ref name (<code>refs/heads/master</code>),
+	 *            branch name (<code>master</code>) or tag name
+	 *            (<code>v1.2.3</code>). The default is to use the branch
+	 *            pointed to by the cloned repository's HEAD and can be
+	 *            requested by passing {@code null} or <code>HEAD</code>.
+	 * @return {@code this}
+	 * @since 5.11
+	 */
+	public FetchCommand setInitialBranch(String branch) {
+		this.initialBranch = branch;
+		return this;
+	}
+
+	/**
 	 * Register a progress callback.
 	 *
 	 * @param callback
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 aaba8d6..af7d50a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -596,6 +596,7 @@
 	/***/ public String reftableDirExists;
 	/***/ public String reftableRecordsMustIncrease;
 	/***/ public String refUpdateReturnCodeWas;
+	/***/ public String remoteBranchNotFound;
 	/***/ public String remoteConfigHasNoURIAssociated;
 	/***/ public String remoteDoesNotHaveSpec;
 	/***/ public String remoteDoesNotSupportSmartHTTPPush;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index bdebfa6..34bad6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -48,6 +48,7 @@
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.revwalk.ObjectWalk;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.StringUtils;
 
 class FetchProcess {
 	/** Transport we will fetch over. */
@@ -79,7 +80,8 @@
 		toFetch = f;
 	}
 
-	void execute(ProgressMonitor monitor, FetchResult result)
+	void execute(ProgressMonitor monitor, FetchResult result,
+			String initialBranch)
 			throws NotSupportedException, TransportException {
 		askFor.clear();
 		localUpdates.clear();
@@ -89,7 +91,7 @@
 
 		Throwable e1 = null;
 		try {
-			executeImp(monitor, result);
+			executeImp(monitor, result, initialBranch);
 		} catch (NotSupportedException | TransportException err) {
 			e1 = err;
 			throw err;
@@ -107,9 +109,22 @@
 		}
 	}
 
+	private boolean isInitialBranchMissing(Map<String, Ref> refsMap,
+			String initialBranch) {
+		if (StringUtils.isEmptyOrNull(initialBranch) || refsMap.isEmpty()) {
+			return false;
+		}
+		if (refsMap.containsKey(initialBranch)
+				|| refsMap.containsKey(Constants.R_HEADS + initialBranch)
+				|| refsMap.containsKey(Constants.R_TAGS + initialBranch)) {
+			return false;
+		}
+		return true;
+	}
+
 	private void executeImp(final ProgressMonitor monitor,
-			final FetchResult result) throws NotSupportedException,
-			TransportException {
+			final FetchResult result, String initialBranch)
+			throws NotSupportedException, TransportException {
 		final TagOpt tagopt = transport.getTagOpt();
 		String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS;
 		String getHead = null;
@@ -126,7 +141,12 @@
 		}
 		conn = transport.openFetch(toFetch, getTags, getHead);
 		try {
-			result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
+			Map<String, Ref> refsMap = conn.getRefsMap();
+			if (isInitialBranchMissing(refsMap, initialBranch)) {
+				throw new TransportException(MessageFormat.format(
+						JGitText.get().remoteBranchNotFound, initialBranch));
+			}
+			result.setAdvertisedRefs(transport.getURI(), refsMap);
 			result.peerUserAgent = conn.getPeerUserAgent();
 			final Set<Ref> matched = new HashSet<>();
 			for (RefSpec spec : toFetch) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 1c998f4..5b781ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -1231,9 +1231,52 @@
 	 *             the remote connection could not be established or object
 	 *             copying (if necessary) failed or update specification was
 	 *             incorrect.
+	 * @since 5.11
 	 */
 	public FetchResult fetch(final ProgressMonitor monitor,
-			Collection<RefSpec> toFetch) throws NotSupportedException,
+			Collection<RefSpec> toFetch)
+			throws NotSupportedException, TransportException {
+		return fetch(monitor, toFetch, null);
+	}
+
+	/**
+	 * Fetch objects and refs from the remote repository to the local one.
+	 * <p>
+	 * This is a utility function providing standard fetch behavior. Local
+	 * tracking refs associated with the remote repository are automatically
+	 * updated if this transport was created from a
+	 * {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs
+	 * defined.
+	 *
+	 * @param monitor
+	 *            progress monitor to inform the user about our processing
+	 *            activity. Must not be null. Use
+	 *            {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress
+	 *            updates are not interesting or necessary.
+	 * @param toFetch
+	 *            specification of refs to fetch locally. May be null or the
+	 *            empty collection to use the specifications from the
+	 *            RemoteConfig. Source for each RefSpec can't be null.
+	 * @param branch
+	 *            the initial branch to check out when cloning the repository.
+	 *            Can be specified as ref name (<code>refs/heads/master</code>),
+	 *            branch name (<code>master</code>) or tag name
+	 *            (<code>v1.2.3</code>). The default is to use the branch
+	 *            pointed to by the cloned repository's HEAD and can be
+	 *            requested by passing {@code null} or <code>HEAD</code>.
+	 * @return information describing the tracking refs updated.
+	 * @throws org.eclipse.jgit.errors.NotSupportedException
+	 *             this transport implementation does not support fetching
+	 *             objects.
+	 * @throws org.eclipse.jgit.errors.TransportException
+	 *             the remote connection could not be established or object
+	 *             copying (if necessary) failed or update specification was
+	 *             incorrect.
+	 * @since 5.11
+	 */
+	public FetchResult fetch(final ProgressMonitor monitor,
+			Collection<RefSpec> toFetch, String branch)
+			throws NotSupportedException,
 			TransportException {
 		if (toFetch == null || toFetch.isEmpty()) {
 			// If the caller did not ask for anything use the defaults.
@@ -1263,7 +1306,7 @@
 		}
 
 		final FetchResult result = new FetchResult();
-		new FetchProcess(this, toFetch).execute(monitor, result);
+		new FetchProcess(this, toFetch).execute(monitor, result, branch);
 
 		local.autoGC(monitor);