Create an abstract Manifest base class

This will help as we add support for another manifest type.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/command.py b/command.py
index a941b95..5ca43f2 100644
--- a/command.py
+++ b/command.py
@@ -17,6 +17,8 @@
 import optparse
 import sys
 
+import manifest_loader
+
 from error import NoSuchProjectError
 
 class Command(object):
@@ -24,7 +26,6 @@
   """
 
   common = False
-  manifest = None
   _optparse = None
 
   def WantPager(self, opt):
@@ -57,6 +58,13 @@
     """
     raise NotImplementedError
  
+  @property
+  def manifest(self):
+    return self.GetManifest()
+
+  def GetManifest(self, reparse=False):
+    return manifest_loader.GetManifest(self.repodir, reparse)
+
   def GetProjects(self, args, missing_ok=False):
     """A list of projects that match the arguments.
     """
diff --git a/main.py b/main.py
index 70ddeff..a60641d 100755
--- a/main.py
+++ b/main.py
@@ -32,11 +32,9 @@
 from command import InteractiveCommand
 from command import MirrorSafeCommand
 from command import PagedCommand
-from editor import Editor
 from error import ManifestInvalidRevisionError
 from error import NoSuchProjectError
 from error import RepoChangedException
-from manifest_xml import XmlManifest
 from pager import RunPager
 
 from subcmds import all as all_commands
@@ -97,8 +95,6 @@
       sys.exit(1)
 
     cmd.repodir = self.repodir
-    cmd.manifest = XmlManifest(cmd.repodir)
-    Editor.globalConfig = cmd.manifest.globalConfig
 
     if not isinstance(cmd, MirrorSafeCommand) and cmd.manifest.IsMirror:
       print >>sys.stderr, \
diff --git a/manifest.py b/manifest.py
new file mode 100644
index 0000000..bf801df
--- /dev/null
+++ b/manifest.py
@@ -0,0 +1,37 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from editor import Editor
+from git_config import GitConfig
+from project import MetaProject
+
+class Manifest(object):
+  """any manifest format"""
+
+  def __init__(self, repodir):
+    self.repodir = os.path.abspath(repodir)
+    self.topdir = os.path.dirname(self.repodir)
+    self.globalConfig = GitConfig.ForUser()
+    Editor.globalConfig = self.globalConfig
+
+    self.repoProject = MetaProject(self, 'repo',
+      gitdir   = os.path.join(repodir, 'repo/.git'),
+      worktree = os.path.join(repodir, 'repo'))
+
+  @property
+  def IsMirror(self):
+    return self.manifestProject.config.GetBoolean('repo.mirror')
diff --git a/manifest_loader.py b/manifest_loader.py
new file mode 100644
index 0000000..85ad3ea
--- /dev/null
+++ b/manifest_loader.py
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from manifest_xml import XmlManifest
+
+def ParseManifest(repodir):
+  return XmlManifest(repodir)
+
+_manifest = None
+
+def GetManifest(repodir, reparse=False):
+  global _manifest
+  if _manifest is None or reparse:
+    _manifest = ParseManifest(repodir)
+  return _manifest
diff --git a/manifest_xml.py b/manifest_xml.py
index 7d02f9d..971cf21 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -18,6 +18,7 @@
 import xml.dom.minidom
 
 from git_config import GitConfig, IsId
+from manifest import Manifest
 from project import RemoteSpec, Project, MetaProject, R_HEADS, HEAD
 from error import ManifestParseError
 
@@ -46,19 +47,13 @@
     url += '/%s.git' % projectName
     return RemoteSpec(self.name, url, self.reviewUrl)
 
-class XmlManifest(object):
+class XmlManifest(Manifest):
   """manages the repo configuration file"""
 
   def __init__(self, repodir):
-    self.repodir = os.path.abspath(repodir)
-    self.topdir = os.path.dirname(self.repodir)
-    self.manifestFile = os.path.join(self.repodir, MANIFEST_FILE_NAME)
-    self.globalConfig = GitConfig.ForUser()
+    Manifest.__init__(self, repodir)
 
-    self.repoProject = MetaProject(self, 'repo',
-      gitdir   = os.path.join(repodir, 'repo/.git'),
-      worktree = os.path.join(repodir, 'repo'))
-
+    self._manifestFile = os.path.join(repodir, MANIFEST_FILE_NAME)
     self.manifestProject = MetaProject(self, 'manifests',
       gitdir   = os.path.join(repodir, 'manifests.git'),
       worktree = os.path.join(repodir, 'manifests'))
@@ -72,18 +67,18 @@
     if not os.path.isfile(path):
       raise ManifestParseError('manifest %s not found' % name)
 
-    old = self.manifestFile
+    old = self._manifestFile
     try:
-      self.manifestFile = path
+      self._manifestFile = path
       self._Unload()
       self._Load()
     finally:
-      self.manifestFile = old
+      self._manifestFile = old
 
     try:
-      if os.path.exists(self.manifestFile):
-        os.remove(self.manifestFile)
-      os.symlink('manifests/%s' % name, self.manifestFile)
+      if os.path.exists(self._manifestFile):
+        os.remove(self._manifestFile)
+      os.symlink('manifests/%s' % name, self._manifestFile)
     except OSError, e:
       raise ManifestParseError('cannot link manifest %s' % name)
 
@@ -168,10 +163,6 @@
     self._Load()
     return self._default
 
-  @property
-  def IsMirror(self):
-    return self.manifestProject.config.GetBoolean('repo.mirror')
-
   def _Unload(self):
     self._loaded = False
     self._projects = {}
@@ -192,11 +183,11 @@
       local = os.path.join(self.repodir, LOCAL_MANIFEST_NAME)
       if os.path.exists(local):
         try:
-          real = self.manifestFile
-          self.manifestFile = local
+          real = self._manifestFile
+          self._manifestFile = local
           self._ParseManifest(False)
         finally:
-          self.manifestFile = real
+          self._manifestFile = real
 
       if self.IsMirror:
         self._AddMetaProjectMirror(self.repoProject)
@@ -205,17 +196,17 @@
       self._loaded = True
 
   def _ParseManifest(self, is_root_file):
-    root = xml.dom.minidom.parse(self.manifestFile)
+    root = xml.dom.minidom.parse(self._manifestFile)
     if not root or not root.childNodes:
       raise ManifestParseError, \
             "no root node in %s" % \
-            self.manifestFile
+            self._manifestFile
 
     config = root.childNodes[0]
     if config.nodeName != 'manifest':
       raise ManifestParseError, \
             "no <manifest> in %s" % \
-            self.manifestFile
+            self._manifestFile
 
     for node in config.childNodes:
       if node.nodeName == 'remove-project':
@@ -233,7 +224,7 @@
         if self._remotes.get(remote.name):
           raise ManifestParseError, \
                 'duplicate remote %s in %s' % \
-                (remote.name, self.manifestFile)
+                (remote.name, self._manifestFile)
         self._remotes[remote.name] = remote
 
     for node in config.childNodes:
@@ -241,7 +232,7 @@
         if self._default is not None:
           raise ManifestParseError, \
                 'duplicate default in %s' % \
-                (self.manifestFile)
+                (self._manifestFile)
         self._default = self._ParseDefault(node)
     if self._default is None:
       self._default = _Default()
@@ -252,7 +243,7 @@
         if self._projects.get(project.name):
           raise ManifestParseError, \
                 'duplicate project %s in %s' % \
-                (project.name, self.manifestFile)
+                (project.name, self._manifestFile)
         self._projects[project.name] = project
 
   def _AddMetaProjectMirror(self, m):
@@ -324,7 +315,7 @@
     if remote is None:
       raise ManifestParseError, \
             "no remote for project %s within %s" % \
-            (name, self.manifestFile)
+            (name, self._manifestFile)
 
     revisionExpr = node.getAttribute('revision')
     if not revisionExpr:
@@ -332,7 +323,7 @@
     if not revisionExpr:
       raise ManifestParseError, \
             "no revision for project %s within %s" % \
-            (name, self.manifestFile)
+            (name, self._manifestFile)
 
     path = node.getAttribute('path')
     if not path:
@@ -340,7 +331,7 @@
     if path.startswith('/'):
       raise ManifestParseError, \
             "project %s path cannot be absolute in %s" % \
-            (name, self.manifestFile)
+            (name, self._manifestFile)
 
     if self.IsMirror:
       relpath = None
@@ -382,7 +373,7 @@
     if not v:
       raise ManifestParseError, \
             "remote %s not defined in %s" % \
-            (name, self.manifestFile)
+            (name, self._manifestFile)
     return v
 
   def _reqatt(self, node, attname):
@@ -393,5 +384,5 @@
     if not v:
       raise ManifestParseError, \
             "no %s in <%s> within %s" % \
-            (attname, node.nodeName, self.manifestFile)
+            (attname, node.nodeName, self._manifestFile)
     return v
diff --git a/subcmds/help.py b/subcmds/help.py
index c5979fd..01d5fa2 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -163,6 +163,7 @@
         print >>sys.stderr, "repo: '%s' is not a repo command." % name
         sys.exit(1)
 
+      cmd.repodir = self.repodir
       self._PrintCommandHelp(cmd)
 
     else:
diff --git a/subcmds/sync.py b/subcmds/sync.py
index bd07dd9..afd44da 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -214,7 +214,7 @@
         if not syncbuf.Finish():
           sys.exit(1)
 
-        self.manifest._Unload()
+        self.GetManifest(reparse=True)
         all = self.GetProjects(args, missing_ok=True)
         missing = []
         for project in all: