manifest_xml: initial support for <superproject>

At most one superproject may be specified. It will be used
to specify the URL of superproject.

It would have 3 attributes: remote, name, and default.
Only "name" is required while the others have reasonable defaults.

<remote name="superproject-url" review="<url>" />
<superproject remote="superproject-url" name="platform/superproject"/>

TODO: This CL only implements the parsing logic and further work
will be in followup CLs.

Tested the code with the following commands.

$ ./run_tests tests/test_manifest_xml.py
$ ./run_tests -v

Bug: https://crbug.com/gerrit/13709
Tested-by: Raman Tenneti <rtenneti@google.com>
Change-Id: I5b4bba02c8b59601c754cf6b5e4d07a1e16ce167
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/292982
Reviewed-by: Mike Frysinger <vapier@google.com>
diff --git a/manifest_xml.py b/manifest_xml.py
index 9b7a81b..eb8a98c 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -463,6 +463,19 @@
                      ' '.join(self._repo_hooks_project.enabled_repo_hooks))
       root.appendChild(e)
 
+    if self._superproject:
+      root.appendChild(doc.createTextNode(''))
+      e = doc.createElement('superproject')
+      e.setAttribute('name', self._superproject['name'])
+      remoteName = None
+      if d.remote:
+        remoteName = d.remote.name
+      remote = self._superproject.get('remote')
+      if not d.remote or remote.orig_name != remoteName:
+        remoteName = remote.orig_name
+        e.setAttribute('remote', remoteName)
+      root.appendChild(e)
+
     return doc
 
   def ToDict(self, **kwargs):
@@ -473,6 +486,7 @@
         'default',
         'manifest-server',
         'repo-hooks',
+        'superproject',
     }
     # Elements that may be repeated.
     MULTI_ELEMENTS = {
@@ -545,6 +559,11 @@
     return self._repo_hooks_project
 
   @property
+  def superproject(self):
+    self._Load()
+    return self._superproject
+
+  @property
   def notice(self):
     self._Load()
     return self._notice
@@ -591,6 +610,7 @@
     self._remotes = {}
     self._default = None
     self._repo_hooks_project = None
+    self._superproject = {}
     self._notice = None
     self.branch = None
     self._manifest_server = None
@@ -793,6 +813,23 @@
 
         # Store the enabled hooks in the Project object.
         self._repo_hooks_project.enabled_repo_hooks = enabled_repo_hooks
+      if node.nodeName == 'superproject':
+        name = self._reqatt(node, 'name')
+        # There can only be one superproject.
+        if self._superproject.get('name'):
+          raise ManifestParseError(
+              'duplicate superproject in %s' %
+              (self.manifestFile))
+        self._superproject['name'] = name
+        remote_name = node.getAttribute('remote')
+        if not remote_name:
+          remote = self._default.remote
+        else:
+          remote = self._get_remote(node)
+        if remote is None:
+          raise ManifestParseError("no remote for superproject %s within %s" %
+                                   (name, self.manifestFile))
+        self._superproject['remote'] = remote.ToRemoteSpec(name)
       if node.nodeName == 'remove-project':
         name = self._reqatt(node, 'name')