ManifestParser: Throw exception if remote does not have fetch attribute

In the repo manifest documentation [1] the fetch attribute is marked
as "#REQUIRED".

If the fetch attribute is not specified, this would previously result in
NullPointerException. Throw a SAXException instead.

[1] https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.txt

Change-Id: Ib8ed8cee6074fe6bf8f9ac6fc7a1664a547d2d49
Signed-off-by: Han-Wen Nienhuys <hanwen@google.com>
Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
index c97b318..4cd89e0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/ManifestParserTest.java
@@ -44,12 +44,15 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
 import org.junit.Test;
+import org.xml.sax.SAXException;
 
 public class ManifestParserTest {
 
@@ -110,4 +113,34 @@ public void testManifestParser() throws Exception {
 				"Filtered projects shouldn't contain any unexpected results",
 				results.isEmpty());
 	}
+
+	@Test
+	public void testManifestParserWithMissingFetchOnRemote() throws Exception {
+		String baseUrl = "https://git.google.com/";
+		StringBuilder xmlContent = new StringBuilder();
+		xmlContent.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+				.append("<manifest>")
+				.append("<remote name=\"remote1\" />")
+				.append("<default revision=\"master\" remote=\"remote1\" />")
+				.append("<project path=\"foo\" name=\"").append("foo")
+				.append("\" groups=\"a,test\" />")
+				.append("<project path=\"bar\" name=\"").append("bar")
+				.append("\" groups=\"notdefault\" />")
+				.append("<project path=\"foo/a\" name=\"").append("a")
+				.append("\" groups=\"a\" />")
+				.append("<project path=\"b\" name=\"").append("b")
+				.append("\" groups=\"b\" />").append("</manifest>");
+
+		ManifestParser parser = new ManifestParser(null, null, "master",
+				baseUrl, null, null);
+		try {
+			parser.read(new ByteArrayInputStream(
+					xmlContent.toString().getBytes(UTF_8)));
+			fail("ManifestParser did not throw exception for missing fetch");
+		} catch (IOException e) {
+			assertTrue(e.getCause() instanceof SAXException);
+			assertTrue(e.getCause().getMessage()
+					.contains("is missing fetch attribute"));
+		}
+	}
 }
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
index 7443ad3..e942038 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
@@ -3,6 +3,7 @@
 errorIncludeNotImplemented=Error: <include> tag not supported as no callback defined.
 errorNoDefault=Error: no default remote in manifest file.
 errorNoDefaultFilename=Error: no default remote in manifest file {0}.
+errorNoFetch=Error: remote {0} is missing fetch attribute.
 errorParsingManifestFile=Error occurred during parsing manifest file.
 errorRemoteUnavailable=Error remote {0} is unavailable.
 invalidManifest=Invalid manifest.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index 26210ec..73b2e6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -289,7 +289,12 @@ public void endDocument() throws SAXException {
 			}
 			String remoteUrl = remoteUrls.get(remote);
 			if (remoteUrl == null) {
-				remoteUrl = baseUrl.resolve(remotes.get(remote).fetch).toString();
+				String fetch = remotes.get(remote).fetch;
+				if (fetch == null) {
+					throw new SAXException(MessageFormat
+							.format(RepoText.get().errorNoFetch, remote));
+				}
+				remoteUrl = baseUrl.resolve(fetch).toString();
 				if (!remoteUrl.endsWith("/")) //$NON-NLS-1$
 					remoteUrl = remoteUrl + "/"; //$NON-NLS-1$
 				remoteUrls.put(remote, remoteUrl);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
index 36b6e3a..02a2565 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
@@ -64,6 +64,7 @@ public static RepoText get() {
 	/***/ public String errorIncludeNotImplemented;
 	/***/ public String errorNoDefault;
 	/***/ public String errorNoDefaultFilename;
+	/***/ public String errorNoFetch;
 	/***/ public String errorParsingManifestFile;
 	/***/ public String errorRemoteUnavailable;
 	/***/ public String invalidManifest;