changeset 1228:9490a3052935

Merge with stable.
author Augie Fackler <raf@durin42.com>
date Fri, 01 Aug 2014 09:42:53 -0400
parents a10a4fc69364 (diff) 9819b4d6451e (current diff)
children 5c2917375961
files .hgtags hgsubversion/compathacks.py hgsubversion/replay.py hgsubversion/stupid.py hgsubversion/svnmeta.py
diffstat 26 files changed, 484 insertions(+), 479 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags
+++ b/.hgtags
@@ -9,5 +9,6 @@ 0cbf9fd89672e73165e1bb4db1ec8f7f65b95c94
 07234759a3f750029ccaa001837d42fa12dd33ee 1.4
 77b22e5b4ea6c248e079afd0f1e544cb5690ce20 1.5
 d0f3a5c2cb56ce65d9ef1c611c8bfbebdc3bef34 1.5.1
+7d47a0f731354505ed9ae8d60d2a6996e8c3294f 1.6
 8caf1226adecb322e90ddb3817c604fa2fe8a66d 1.6.1
 36f6d51b4edc31f1f9ce2d0d02965a85dd26a455 1.6.2
--- a/hgsubversion/__init__.py
+++ b/hgsubversion/__init__.py
@@ -33,27 +33,9 @@ demandimport.ignore.extend([
     'svn.ra',
     ])
 
-try:
-    from mercurial import templatekw
-    # force demandimport to load templatekw
-    templatekw.keywords
-except ImportError:
-    templatekw = None
-
-try:
-    from mercurial import revset
-    # force demandimport to load revset
-    revset.methods
-except ImportError:
-    revset = None
-
-try:
-    from mercurial import subrepo
-    # require svnsubrepo and hg >= 1.7.1
-    subrepo.svnsubrepo
-    hgutil.checknlink
-except (ImportError, AttributeError), e:
-    subrepo = None
+from mercurial import templatekw
+from mercurial import revset
+from mercurial import subrepo
 
 import svncommands
 import util
@@ -124,9 +106,8 @@ except AttributeError:
 except ImportError:
     pass
 
-def extsetup():
+def extsetup(ui):
     """insert command wrappers for a bunch of commands"""
-    # add the ui argument to this function once we drop support for 1.3
 
     docvals = {'extension': 'hgsubversion'}
     for cmd, (generic, target, fixdoc, ppopts, opts) in wrapcmds.iteritems():
@@ -163,20 +144,13 @@ def extsetup():
          lambda: open(os.path.join(helpdir, 'subversion.rst')).read()),
     )
 
-    # in 1.6 and earler the help table is a tuple
-    if getattr(help.helptable, 'extend', None):
-        help.helptable.extend(entries)
-    else:
-        help.helptable = help.helptable + entries
+    help.helptable.extend(entries)
 
-    if templatekw:
-        templatekw.keywords.update(util.templatekeywords)
+    templatekw.keywords.update(util.templatekeywords)
 
-    if revset:
-        revset.symbols.update(util.revsets)
+    revset.symbols.update(util.revsets)
 
-    if subrepo:
-        subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
+    subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
 
 def reposetup(ui, repo):
     if repo.local():
@@ -184,7 +158,7 @@ def reposetup(ui, repo):
         for tunnel in ui.configlist('hgsubversion', 'tunnels'):
             hg.schemes['svn+' + tunnel] = svnrepo
 
-    if revset and ui.configbool('hgsubversion', 'nativerevs'):
+    if ui.configbool('hgsubversion', 'nativerevs'):
         extensions.wrapfunction(revset, 'stringset', util.revset_stringset)
 
 _old_local = hg.schemes['file']
--- a/hgsubversion/compathacks.py
+++ b/hgsubversion/compathacks.py
@@ -1,4 +1,5 @@
-"""Functions to work around API changes inside Mercurial."""
+"""Functions to work around API changes."""
+
 
 def branchset(repo):
     """Return the set of branches present in a repo.
--- a/hgsubversion/help/subversion.rst
+++ b/hgsubversion/help/subversion.rst
@@ -45,8 +45,8 @@ issue ``hg clone http://python-nose.goog
 works with any directory with a Subversion repository, and is known as a single
 directory clone. Normally, converted changesets will be marked as belonging to
 the ``default`` branch, but this can be changed by using the ``-b/--branch``
-option when using Mercurial 1.5 or later. To force single directory clone, use
-hgsubversion.layout option (see below for detailed help) ::
+option. To force single directory clone, use hgsubversion.layout option (see
+below for detailed help) ::
 
  $ hg clone --layout single svn+http://python-nose.googlecode.com/svn nose-hg
 
@@ -85,8 +85,6 @@ An example::
 
   $ hg log --template='{rev}:{node|short} {author|user}\nsvn: {svnrev}\n'
 
-The template keywords are available when using Mercurial 1.5 or later.
-
 For finding changesets from Subversion, hgsubversion extends revsets
 to provide two new selectors:
 
@@ -100,9 +98,7 @@ For example::
   $ hg log -r 'fromsvn()'
   $ hg log -r 'svnrev(500)'
 
-Revsets are available when using Mercurial 1.6 or later and are
-accepted by several Mercurial commands for specifying revisions. See
-``hg help revsets`` for details.
+See ``hg help revsets`` for details.
 
 Support for externals
 ---------------------
@@ -146,7 +142,7 @@ related Subversion repository.
 Alternatively, one can use the ``hgsubversion.externals`` in hgrc to
 specify ``subrepos`` as the externals mode. In this mode, ``.hgsub``
 and ``.hgsubstate`` files will be used instead of
-``.hgsvnexternals``. This feature requires Mercurial 1.7.1 or later.
+``.hgsvnexternals``.
 
 
 Using Subrepositories
@@ -183,8 +179,6 @@ with the revision identifier replaced wi
 
 This mode has the following limitations:
 
-* Require Mercurial >= 1.7.1 to work correctly on all platforms.
-
 * "hgsubversion" subrepositories require hgsubversion extension to be
   available. To operate transparently on ``svn:externals`` we have to
   stay as close as possible to their original property
@@ -365,10 +359,10 @@ settings:
     when necessary.
 
   ``hgsubversion.externals``
-    Set to ``subrepos`` to switch to subrepos-based externals support
-    (requires Mercurial 1.7.1 or later.) Default is ``svnexternals``,
-    which uses a custom hgsubversion-specific format and works on
-    older versions of Mercurial. Use ``ignore`` to avoid converting externals.
+    Set to ``subrepos`` to switch to subrepos-based externals support. Default
+    is ``svnexternals``, which uses a custom hgsubversion-specific format and
+    works on older versions of Mercurial. Use ``ignore`` to avoid converting
+    externals.
 
 The following options only have an effect on the initial clone of a repository:
 
--- a/hgsubversion/layouts/base.py
+++ b/hgsubversion/layouts/base.py
@@ -48,7 +48,7 @@ class BaseLayout(object):
         """
         self.__unimplemented('remotepath')
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         """Return a list of locations within svn to search for tags
 
         Should be returned in reverse-sorted order.
--- a/hgsubversion/layouts/custom.py
+++ b/hgsubversion/layouts/custom.py
@@ -64,7 +64,7 @@ class CustomLayout(base.BaseLayout):
             subdir += '/'
         return subdir + self.remotename(branch)
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/detect.py
+++ b/hgsubversion/layouts/detect.py
@@ -58,7 +58,7 @@ def layout_from_config(ui, allow_auto=Fa
         raise hgutil.Abort("unknown layout '%s'" % layout)
     return layout
 
-def layout_from_file(meta_data_dir, ui=None):
+def layout_from_file(metapath, ui=None):
     """ Load the layout in use from the metadata file.
 
     If you pass the ui arg, we will also write the layout to the
@@ -67,7 +67,7 @@ def layout_from_file(meta_data_dir, ui=N
     """
 
     layout = None
-    layoutfile = os.path.join(meta_data_dir, 'layout')
+    layoutfile = os.path.join(metapath, 'layout')
     if os.path.exists(layoutfile):
         f = open(layoutfile)
         layout = f.read().strip()
--- a/hgsubversion/layouts/persist.py
+++ b/hgsubversion/layouts/persist.py
@@ -7,10 +7,10 @@ to do it.
 
 import os.path
 
-def layout_to_file(meta_data_dir, layout):
-    """Save the given layout to a file under the given meta_data_dir"""
+def layout_to_file(metapath, layout):
+    """Save the given layout to a file under the given metapath"""
 
-    layoutfile = os.path.join(meta_data_dir, 'layout')
+    layoutfile = os.path.join(metapath, 'layout')
     f = open(layoutfile, 'w')
     f.write(layout)
     f.close()
--- a/hgsubversion/layouts/single.py
+++ b/hgsubversion/layouts/single.py
@@ -14,7 +14,7 @@ class SingleLayout(base.BaseLayout):
     def remotepath(self, branch, subdir='/'):
         return subdir or '/'
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/standard.py
+++ b/hgsubversion/layouts/standard.py
@@ -1,5 +1,4 @@
 import os.path
-import pickle
 
 import base
 
@@ -55,7 +54,7 @@ class StandardLayout(base.BaseLayout):
 
         return '%s/%s' % (subdir or '', branchpath)
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         # import late to avoid trouble when running the test suite
         try:
             # newer versions of mercurial >= 2.8 will import this because the
@@ -66,17 +65,14 @@ class StandardLayout(base.BaseLayout):
 
         if self._tag_locations is None:
 
-            tag_locations_file = os.path.join(meta_data_dir, 'tag_locations')
+            tag_locations_file = os.path.join(metapath, 'tag_locations')
+            self._tag_locations = util.load(tag_locations_file)
 
-            if os.path.exists(tag_locations_file):
-                f = open(tag_locations_file)
-                self._tag_locations = pickle.load(f)
-                f.close()
-            else:
+            if not self._tag_locations:
                 self._tag_locations = self.ui.configlist('hgsubversion',
                                                         'tagpaths',
                                                         ['tags'])
-            util.pickle_atomic(self._tag_locations, tag_locations_file)
+            util.dump(self._tag_locations, tag_locations_file)
 
             # ensure nested paths are handled properly
             self._tag_locations.sort()
--- a/hgsubversion/maps.py
+++ b/hgsubversion/maps.py
@@ -14,12 +14,12 @@ class AuthorMap(dict):
 
     If the 'hgsubversion.defaultauthors' configuration option is set to false,
     attempting to obtain an unknown author will fail with an Abort.
-    
+
     If the 'hgsubversion.caseignoreauthors' configuration option is set to true,
     the userid from Subversion is always compared lowercase.
     '''
 
-    def __init__(self, ui, path, defaulthost=None):
+    def __init__(self, meta):
         '''Initialise a new AuthorMap.
 
         The ui argument is used to print diagnostic messages.
@@ -27,17 +27,19 @@ class AuthorMap(dict):
         The path argument is the location of the backing store,
         typically .hg/svn/authors.
         '''
-        self.ui = ui
-        self.path = path
-        self.use_defaultauthors = self.ui.configbool('hgsubversion', 'defaultauthors', True)
-        self.caseignoreauthors = self.ui.configbool('hgsubversion', 'caseignoreauthors', False)
-        if defaulthost:
-            self.defaulthost = '@%s' % defaulthost.lstrip('@')
-        else:
-            self.defaulthost = ''
+        self.meta = meta
+        self.defaulthost = ''
+        if meta.defaulthost:
+            self.defaulthost = '@%s' % meta.defaulthost.lstrip('@')
+
         self.super = super(AuthorMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.authors_file)
+
+        # append authors specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'authormap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         ''' Load mappings from a file at the specified path. '''
@@ -47,10 +49,10 @@ class AuthorMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.authors_file:
+            writing = open(self.meta.authors_file, 'a')
 
-        self.ui.debug('reading authormap from %s\n' % path)
+        self.meta.ui.debug('reading authormap from %s\n' % path)
         f = open(path, 'r')
         for number, line_org in enumerate(f):
 
@@ -62,21 +64,21 @@ class AuthorMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in author map %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
 
-            if self.caseignoreauthors:
+            if self.meta.caseignoreauthors:
                 src = src.lower()
 
             if writing:
                 if not src in self:
-                    self.ui.debug('adding author %s to author map\n' % src)
+                    self.meta.ui.debug('adding author %s to author map\n' % src)
                 elif dst != self[src]:
                     msg = 'overriding author: "%s" to "%s" (%s)\n'
-                    self.ui.status(msg % (self[src], dst, src))
+                    self.meta.ui.status(msg % (self[src], dst, src))
                 writing.write(line_org)
 
             self[src] = dst
@@ -92,21 +94,20 @@ class AuthorMap(dict):
         if author is None:
             author = '(no author)'
 
-        if self.caseignoreauthors:
+        search_author = author
+        if self.meta.caseignoreauthors:
             search_author = author.lower()
-        else:
-            search_author = author
 
         if search_author in self:
             result = self.super.__getitem__(search_author)
-        elif self.use_defaultauthors:
+        elif self.meta.defaultauthors:
             self[author] = result = '%s%s' % (author, self.defaulthost)
             msg = 'substituting author "%s" for default "%s"\n'
-            self.ui.debug(msg % (author, result))
+            self.meta.ui.debug(msg % (author, result))
         else:
             msg = 'author %s has no entry in the author map!'
             raise hgutil.Abort(msg % author)
-        self.ui.debug('mapping author "%s" to "%s"\n' % (author, result))
+        self.meta.ui.debug('mapping author "%s" to "%s"\n' % (author, result))
         return result
 
     def reverselookup(self, author):
@@ -126,27 +127,23 @@ class Tags(dict):
     """
     VERSION = 2
 
-    @classmethod
-    def filepath(cls, repo):
-        return os.path.join(repo.path, 'svn', 'tagmap')
-
-    def __init__(self, repo, endrev=None):
+    def __init__(self, meta, endrev=None):
         dict.__init__(self)
-        self.path = self.filepath(repo)
+        self.meta = meta
         self.endrev = endrev
-        if os.path.isfile(self.path):
-            self._load(repo)
+        if os.path.isfile(self.meta.tagfile):
+            self._load()
         else:
             self._write()
 
-    def _load(self, repo):
-        f = open(self.path)
+    def _load(self):
+        f = open(self.meta.tagfile)
         ver = int(f.readline())
         if ver < self.VERSION:
-            repo.ui.status('tag map outdated, running rebuildmeta...\n')
+            self.meta.ui.status('tag map outdated, running rebuildmeta...\n')
             f.close()
-            os.unlink(self.path)
-            svncommands.rebuildmeta(repo.ui, repo, ())
+            os.unlink(self.meta.tagfile)
+            svncommands.rebuildmeta(self.meta.ui, self.meta.repo, ())
             return
         elif ver != self.VERSION:
             raise hgutil.Abort('tagmap too new -- please upgrade')
@@ -163,7 +160,7 @@ class Tags(dict):
 
     def _write(self):
         assert self.endrev is None
-        f = open(self.path, 'w')
+        f = open(self.meta.tagfile, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
@@ -184,7 +181,7 @@ class Tags(dict):
         if not tag:
             raise hgutil.Abort('tag cannot be empty')
         ha, revision = info
-        f = open(self.path, 'a')
+        f = open(self.meta.tagfile, 'a')
         f.write('%s %s %s\n' % (node.hex(ha), revision, tag))
         f.close()
         dict.__setitem__(self, tag, ha)
@@ -194,32 +191,15 @@ class RevMap(dict):
 
     VERSION = 1
 
-    def __init__(self, repo):
+    def __init__(self, meta):
         dict.__init__(self)
-        self.path = self.mappath(repo)
-        self.repo = repo
-        self.ypath = os.path.join(repo.path, 'svn', 'lastpulled')
-        # TODO(durin42): Consider moving management of the youngest
-        # file to svnmeta itself rather than leaving it here.
-        # must load youngest file first, or else self._load() can
-        # clobber the info
-        _yonngest_str = util.load_string(self.ypath, '0')
-        self._youngest = int(_yonngest_str.strip())
-        self.oldest = 0
-        if os.path.isfile(self.path):
+        self.meta = meta
+
+        if os.path.isfile(self.meta.revmap_file):
             self._load()
         else:
             self._write()
 
-    def _set_youngest(self, rev):
-        self._youngest = max(self._youngest, rev)
-        util.save_string(self.ypath, str(self._youngest) + '\n')
-
-    def _get_youngest(self):
-        return self._youngest
-
-    youngest = property(_get_youngest, _set_youngest)
-
     def hashes(self):
         return dict((v, k) for (k, v) in self.iteritems())
 
@@ -227,14 +207,10 @@ class RevMap(dict):
         check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
         return sorted(filter(check, self.iteritems()), reverse=True)
 
-    @staticmethod
-    def mappath(repo):
-        return os.path.join(repo.path, 'svn', 'rev_map')
-
     @classmethod
-    def readmapfile(cls, repo, missingok=True):
+    def readmapfile(cls, path, missingok=True):
         try:
-            f = open(cls.mappath(repo))
+            f = open(path)
         except IOError, err:
             if not missingok or err.errno != errno.ENOENT:
                 raise
@@ -245,34 +221,34 @@ class RevMap(dict):
         return f
 
     def _load(self):
-        for l in self.readmapfile(self.repo):
+        for l in self.readmapfile(self.meta.revmap_file):
             revnum, ha, branch = l.split(' ', 2)
             if branch == '\n':
                 branch = None
             else:
                 branch = branch[:-1]
             revnum = int(revnum)
-            if revnum > self.youngest or not self.youngest:
-                self.youngest = revnum
-            if revnum < self.oldest or not self.oldest:
-                self.oldest = revnum
+            if revnum > self.meta.lastpulled or not self.meta.lastpulled:
+                self.meta.lastpulled = revnum
+            if revnum < self.meta.firstpulled or not self.meta.firstpulled:
+                self.meta.firstpulled = revnum
             dict.__setitem__(self, (revnum, branch), node.bin(ha))
 
     def _write(self):
-        f = open(self.path, 'w')
+        f = open(self.meta.revmap_file, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
     def __setitem__(self, key, ha):
         revnum, branch = key
-        f = open(self.path, 'a')
+        f = open(self.meta.revmap_file, 'a')
         b = branch or ''
         f.write(str(revnum) + ' ' + node.hex(ha) + ' ' + b + '\n')
         f.close()
-        if revnum > self.youngest or not self.youngest:
-            self.youngest = revnum
-        if revnum < self.oldest or not self.oldest:
-            self.oldest = revnum
+        if revnum > self.meta.lastpulled or not self.meta.lastpulled:
+            self.meta.lastpulled = revnum
+        if revnum < self.meta.firstpulled or not self.meta.firstpulled:
+            self.meta.firstpulled = revnum
         dict.__setitem__(self, (revnum, branch), ha)
 
 
@@ -280,7 +256,7 @@ class FileMap(object):
 
     VERSION = 1
 
-    def __init__(self, ui, path):
+    def __init__(self, meta):
         '''Initialise a new FileMap.
 
         The ui argument is used to print diagnostic messages.
@@ -288,15 +264,19 @@ class FileMap(object):
         The path argument is the location of the backing store,
         typically .hg/svn/filemap.
         '''
-        self.ui = ui
-        self.path = path
+        self.meta = meta
         self.include = {}
         self.exclude = {}
-        if os.path.isfile(self.path):
+        if os.path.isfile(self.meta.filemap_file):
             self._load()
         else:
             self._write()
 
+        # append file mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'filemap')
+        if clmap:
+            self.load(clmap)
+
     def _rpairs(self, name):
         e = len(name)
         while e != -1:
@@ -335,19 +315,19 @@ class FileMap(object):
         mapping = getattr(self, m)
         if path in mapping:
             msg = 'duplicate %s entry in %s: "%s"\n'
-            self.ui.status(msg % (m, fn, path))
+            self.meta.ui.status(msg % (m, fn, path))
             return
         bits = m.rstrip('e'), path
-        self.ui.debug('%sing %s\n' % bits)
+        self.meta.ui.debug('%sing %s\n' % bits)
         # respect rule order
         mapping[path] = len(self)
-        if fn != self.path:
-            f = open(self.path, 'a')
+        if fn != self.meta.filemap_file:
+            f = open(self.meta.filemap_file, 'a')
             f.write(m + ' ' + path + '\n')
             f.close()
 
     def load(self, fn):
-        self.ui.debug('reading file map from %s\n' % fn)
+        self.meta.ui.debug('reading file map from %s\n' % fn)
         f = open(fn, 'r')
         self.load_fd(f, fn)
         f.close()
@@ -363,22 +343,22 @@ class FileMap(object):
                 if cmd in ('include', 'exclude'):
                     self.add(fn, cmd, path)
                     continue
-                self.ui.warn('unknown filemap command %s\n' % cmd)
+                self.meta.ui.warn('unknown filemap command %s\n' % cmd)
             except IndexError:
                 msg = 'ignoring bad line in filemap %s: %s\n'
-                self.ui.warn(msg % (fn, line.rstrip()))
+                self.meta.ui.warn(msg % (fn, line.rstrip()))
 
     def _load(self):
-        self.ui.debug('reading in-repo file map from %s\n' % self.path)
-        f = open(self.path)
+        self.meta.ui.debug('reading in-repo file map from %s\n' % self.meta.filemap_file)
+        f = open(self.meta.filemap_file)
         ver = int(f.readline())
         if ver != self.VERSION:
             raise hgutil.Abort('filemap too new -- please upgrade')
-        self.load_fd(f, self.path)
+        self.load_fd(f, self.meta.filemap_file)
         f.close()
 
     def _write(self):
-        f = open(self.path, 'w')
+        f = open(self.meta.filemap_file, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
@@ -392,12 +372,16 @@ class BranchMap(dict):
     changes on other will now be on default (have no branch name set).
     '''
 
-    def __init__(self, ui, path):
-        self.ui = ui
-        self.path = path
+    def __init__(self, meta):
+        self.meta = meta
         self.super = super(BranchMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.branchmap_file)
+
+        # append branch mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'branchmap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         '''Load mappings from a file at the specified path.'''
@@ -405,10 +389,10 @@ class BranchMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.branchmap_file:
+            writing = open(self.meta.branchmap_file, 'a')
 
-        self.ui.debug('reading branchmap from %s\n' % path)
+        self.meta.ui.debug('reading branchmap from %s\n' % path)
         f = open(path, 'r')
         for number, line in enumerate(f):
 
@@ -423,12 +407,12 @@ class BranchMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in branch map %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
-            self.ui.debug('adding branch %s to branch map\n' % src)
+            self.meta.ui.debug('adding branch %s to branch map\n' % src)
 
             if not dst:
                 # prevent people from assuming such lines are valid
@@ -437,7 +421,7 @@ class BranchMap(dict):
                                    % (number, path))
             elif src in self and dst != self[src]:
                 msg = 'overriding branch: "%s" to "%s" (%s)\n'
-                self.ui.status(msg % (self[src], dst, src))
+                self.meta.ui.status(msg % (self[src], dst, src))
             self[src] = dst
 
         f.close()
@@ -454,12 +438,16 @@ class TagMap(dict):
         the other tag will not be reflected in the hg repository.
     '''
 
-    def __init__(self, ui, path):
-        self.ui = ui
-        self.path = path
+    def __init__(self, meta):
+        self.meta = meta
         self.super = super(TagMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.tagmap_file)
+
+        # append tag mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'tagmap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         '''Load mappings from a file at the specified path.'''
@@ -467,10 +455,10 @@ class TagMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.tagmap_file:
+            writing = open(self.meta.tagmap_file, 'a')
 
-        self.ui.debug('reading tag renames from %s\n' % path)
+        self.meta.ui.debug('reading tag renames from %s\n' % path)
         f = open(path, 'r')
         for number, line in enumerate(f):
 
@@ -485,16 +473,16 @@ class TagMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in tag renames %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
-            self.ui.debug('adding tag %s to tag renames\n' % src)
+            self.meta.ui.debug('adding tag %s to tag renames\n' % src)
 
             if src in self and dst != self[src]:
                 msg = 'overriding tag rename: "%s" to "%s" (%s)\n'
-                self.ui.status(msg % (self[src], dst, src))
+                self.meta.ui.status(msg % (self[src], dst, src))
             self[src] = dst
 
         f.close()
--- a/hgsubversion/replay.py
+++ b/hgsubversion/replay.py
@@ -65,13 +65,13 @@ def _convert_rev(ui, meta, svn, r, tbdel
     editor.current.rev = r
     editor.setsvn(svn)
 
-    if firstrun and meta.revmap.oldest <= 0:
+    if firstrun and meta.firstpulled <= 0:
         # We know nothing about this project, so fetch everything before
         # trying to apply deltas.
         ui.debug('replay: fetching full revision\n')
         svn.get_revision(r.revnum, editor)
     else:
-        svn.get_replay(r.revnum, editor, meta.revmap.oldest)
+        svn.get_replay(r.revnum, editor, meta.firstpulled)
     editor.close()
 
     current = editor.current
--- a/hgsubversion/stupid.py
+++ b/hgsubversion/stupid.py
@@ -158,27 +158,8 @@ def filteriterhunks(meta):
         applycurrent = False
         # Passing False instead of textmode because we should never
         # be ignoring EOL type.
-        if iterhunks.func_code.co_argcount == 1:
-            # Since 1.9 (28762bb767dc)
-            fp = args[0]
-            gen = iterhunks(fp)
-        else:
-            ui, fp = args[:2]
-            if len(args) > 2:
-                sourcefile = args[2]
-            else:
-                sourcefile = kwargs.get('sourcefile', None)
-            if len(args) > 3:
-                textmode = args[3]
-            else:
-                textmode = kwargs.get('textmode', False)
-            if not iterhunks.func_defaults:
-                # Since 1.7 (cfedc529e4a1)
-                gen = iterhunks(ui, fp)
-            elif len(iterhunks.func_defaults) == 1:
-                gen = iterhunks(ui, fp, sourcefile)
-            else:
-                gen = iterhunks(ui, fp, sourcefile, textmode)
+        fp = args[0]
+        gen = iterhunks(fp)
         for data in gen:
             if data[0] == 'file':
                 if data[1][1] in meta.filemap:
@@ -736,7 +717,7 @@ def convert_rev(ui, meta, svn, r, tbdelt
         # path does not support this case with svn >= 1.7. We can fix
         # it, or we can force the existing fetch_branchrev() path. Do
         # the latter for now.
-        incremental = (meta.revmap.oldest > 0 and
+        incremental = (meta.firstpulled > 0 and
                        parentctx.rev() != node.nullrev and
                        not firstrun)
 
--- a/hgsubversion/svncommands.py
+++ b/hgsubversion/svncommands.py
@@ -1,6 +1,5 @@
 import os
 import posixpath
-import cPickle as pickle
 import sys
 import traceback
 import urlparse
@@ -19,6 +18,7 @@ import svnrepo
 import util
 import svnexternals
 import verify
+import svnmeta
 
 
 def updatemeta(ui, repo, args, **opts):
@@ -39,22 +39,6 @@ def rebuildmeta(ui, repo, args, unsafe_s
     return _buildmeta(ui, repo, args, partial=False,
                       skipuuid=unsafe_skip_uuid_check)
 
-def read_if_exists(path):
-     try:
-        fp = open(path, 'rb')
-        d = fp.read()
-        fp.close()
-        return d
-     except IOError, err:
-         if err.errno != errno.ENOENT:
-             raise
-
-def write_if_needed(path, content):
-    if read_if_exists(path) != content:
-        fp = open(path, 'wb')
-        fp.write(content)
-        fp.close()
-
 def _buildmeta(ui, repo, args, partial=False, skipuuid=False):
 
     if repo is None:
@@ -70,19 +54,13 @@ def _buildmeta(ui, repo, args, partial=F
         raise hgutil.Abort('rebuildmeta takes 1 or no arguments')
     url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or
                              repo.ui.config('paths', 'default') or '')
-    svnmetadir = os.path.join(repo.path, 'svn')
-    if not os.path.exists(svnmetadir):
-        os.makedirs(svnmetadir)
-    uuidpath = os.path.join(svnmetadir, 'uuid')
-    uuid = read_if_exists(uuidpath)
-
-    subdirpath = os.path.join(svnmetadir, 'subdir')
-    subdir = read_if_exists(subdirpath)
+
+    meta = svnmeta.SVNMeta(repo, skiperrorcheck=True)
+
     svn = None
-    if subdir is None:
+    if meta.subdir is None:
         svn = svnrepo.svnremoterepo(ui, url).svn
-        subdir = svn.subdir
-        open(subdirpath, 'wb').write(subdir.strip('/'))
+        meta.subdir = svn.subdir
 
     youngest = 0
     startrev = 0
@@ -90,16 +68,18 @@ def _buildmeta(ui, repo, args, partial=F
     branchinfo = {}
     if partial:
         try:
-            youngestpath = os.path.join(svnmetadir, 'lastpulled')
+            # we can't use meta.lastpulled here because we are bootstraping the
+            # lastpulled and want to keep the cached value on disk during a
+            # partial rebuild
             foundpartialinfo = False
+            youngestpath = os.path.join(meta.metapath, 'lastpulled')
             if os.path.exists(youngestpath):
-                youngest = int(util.load_string(youngestpath).strip())
-                sofar = list(maps.RevMap.readmapfile(repo))
+                youngest = util.load(youngestpath)
+                sofar = list(maps.RevMap.readmapfile(meta.revmap_file))
                 if sofar and len(sofar[-1].split(' ', 2)) > 1:
                     lasthash = sofar[-1].split(' ', 2)[1]
                     startrev = repo[lasthash].rev() + 1
-                    branchinfo = pickle.load(open(os.path.join(svnmetadir,
-                                                           'branch_info')))
+                    branchinfo = util.load(meta.branch_info_file)
                     foundpartialinfo = True
             if not foundpartialinfo:
                 ui.status('missing some metadata -- doing a full rebuild\n')
@@ -111,16 +91,12 @@ def _buildmeta(ui, repo, args, partial=F
         except AttributeError:
             ui.status('no metadata available -- doing a full rebuild\n')
 
-
-    lastpulled = open(os.path.join(svnmetadir, 'lastpulled'), 'wb')
-    revmap = open(os.path.join(svnmetadir, 'rev_map'), 'w')
-    revmap.write('1\n')
+    revmap = open(meta.revmap_file, 'w')
+    revmap.write('%d\n' % maps.RevMap.VERSION)
     revmap.writelines(sofar)
     last_rev = -1
-    tagfile = os.path.join(svnmetadir, 'tagmap')
-    if not partial and os.path.exists(maps.Tags.filepath(repo)) :
-        os.unlink(maps.Tags.filepath(repo))
-    tags = maps.Tags(repo)
+    if not partial and os.path.exists(meta.tagfile):
+        os.unlink(meta.tagfile)
 
     layout = None
     layoutobj = None
@@ -164,7 +140,7 @@ def _buildmeta(ui, repo, args, partial=F
             else:
                 closed.add(parentctx.rev())
 
-    lastpulled.write(str(youngest) + '\n')
+    meta.lastpulled = youngest
     ui.progress('prepare', None, total=numrevs)
 
     for rev in xrange(startrev, len(repo)):
@@ -197,11 +173,13 @@ def _buildmeta(ui, repo, args, partial=F
                 # number.
                 tagging = int(convinfo[40:].split('@')[1])
                 tagrev = max(tagged, tagging)
-                tags[tag] = node.bin(ha), tagrev
+                meta.tags[tag] = node.bin(ha), tagrev
 
         # check that the conversion metadata matches expectations
         assert convinfo.startswith('svn:')
         revpath, revision = convinfo[40:].split('@')
+        # use tmp variable for testing
+        subdir = meta.subdir
         if subdir and subdir[0] != '/':
             subdir = '/' + subdir
         if subdir and subdir[-1] == '/':
@@ -212,16 +190,16 @@ def _buildmeta(ui, repo, args, partial=F
         if layout is None:
             layout = layouts.detect.layout_from_commit(subdir, revpath,
                                                        ctx.branch(), ui)
-            existing_layout = layouts.detect.layout_from_file(svnmetadir)
+            existing_layout = layouts.detect.layout_from_file(meta.metapath)
             if layout != existing_layout:
-                layouts.persist.layout_to_file(svnmetadir, layout)
+                layouts.persist.layout_to_file(meta.metapath, layout)
             layoutobj = layouts.layout_from_name(layout, ui)
         elif layout == 'single':
             assert (subdir or '/') == revpath, ('Possible layout detection'
                                                 ' defect in replay')
 
         # write repository uuid if required
-        if uuid is None or validateuuid:
+        if meta.uuid is None or validateuuid:
             validateuuid = False
             uuid = convinfo[4:40]
             if not skipuuid:
@@ -230,7 +208,7 @@ def _buildmeta(ui, repo, args, partial=F
                 if uuid != svn.uuid:
                     raise hgutil.Abort('remote svn repository identifier '
                                        'does not match')
-            write_if_needed(uuidpath, uuid)
+            meta.uuid = uuid
 
         # don't reflect closed branches
         if (ctx.extra().get('close') and not ctx.files() or
@@ -241,7 +219,7 @@ def _buildmeta(ui, repo, args, partial=F
         # find commitpath, write to revmap
         commitpath = revpath[len(subdir)+1:]
 
-        tag_locations = layoutobj.taglocations(svnmetadir)
+        tag_locations = layoutobj.taglocations(meta.metapath)
         found_tag = False
         for location in tag_locations:
             if commitpath.startswith(location + '/'):
@@ -304,9 +282,7 @@ def _buildmeta(ui, repo, args, partial=F
     ui.progress('rebuild', None, total=numrevs)
 
     # save off branch info
-    branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w')
-    pickle.dump(branchinfo, branchinfofile)
-    branchinfofile.close()
+    util.dump(branchinfo, meta.branch_info_file)
 
 
 def help_(ui, args=None, **opts):
--- a/hgsubversion/svnexternals.py
+++ b/hgsubversion/svnexternals.py
@@ -3,26 +3,14 @@ import cStringIO
 import os, re, shutil, stat, subprocess
 from mercurial import util as hgutil
 from mercurial.i18n import _
+from mercurial import subrepo
 
 try:
-    from mercurial import subrepo
-    # require svnsubrepo and hg >= 1.7.1
-    subrepo.svnsubrepo
-    hgutil.checknlink
-except (ImportError, AttributeError), e:
-    subrepo = None
-
-passpegrev = True # see svnsubrepo below
-try:
-    canonpath = hgutil.canonpath
+    from mercurial import scmutil
+    canonpath = scmutil.canonpath
 except (ImportError, AttributeError):
-    passpegrev = False
-    try:
-        from mercurial import scmutil
-        canonpath = scmutil.canonpath
-    except (ImportError, AttributeError):
-        from mercurial import pathutil
-        canonpath = pathutil.canonpath
+    from mercurial import pathutil
+    canonpath = pathutil.canonpath
 
 import util
 
@@ -398,58 +386,53 @@ def parse(ui, ctx):
         raise hgutil.Abort(_('unknown externals modes: %s') % mode)
     return external
 
-if subrepo:
-    class svnsubrepo(subrepo.svnsubrepo):
-        def __init__(self, ctx, path, state):
-            state = (state[0].split(':', 1)[1], state[1])
-            super(svnsubrepo, self).__init__(ctx, path, state)
-
-        def get(self, state, *args, **kwargs):
-            # Resolve source first
-            line = state[0].split(':', 1)[1]
-            source, pegrev = parsedefinition(line)[2:4]
-            try:
-                # Getting the root SVN repository URL is expensive.
-                # Assume the externals is absolute.
-                source = resolvesource(self._ui, None, source)
-            except RelativeSourceError:
-                svnurl = self._ctx._repo.ui.expandpath('default')
-                svnroot = getsvninfo(util.normalize_url(svnurl))[1]
-                source = resolvesource(self._ui, svnroot, source)
-            # hg < 1.9 svnsubrepo calls "svn checkout" with --rev
-            # only, so peg revisions are correctly used. 1.9 and
-            # higher, append the rev as a peg revision to the source
-            # URL, so we cannot add our own. We assume that "-r10
-            # url@2" will be similar to "url@10" most of the time.
-            if pegrev is not None and passpegrev:
-                source = source + '@' + pegrev
-            state = (source, state[1])
-            # hg-1.7.4-c19b9282d3a7 introduced the overwrite argument
-            return super(svnsubrepo, self).get(state, *args, **kwargs)
-
-        def dirty(self, ignoreupdate=False):
-            # You cannot compare anything with HEAD. Just accept it
-            # can be anything.
-            if hasattr(self, '_wcrevs'):
-                wcrevs = self._wcrevs()
-            else:
-                wcrev = self._wcrev()
-                wcrevs = (wcrev, wcrev)
-            if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or
-                self._state[1] in wcrevs or ignoreupdate)
-                and not self._wcchanged()[0]):
-                return False
-            return True
-
-        def commit(self, text, user, date):
-            rev = super(svnsubrepo, self).commit(text, user, date)
-            # Keep unversioned externals unversioned
-            if self._state[1] == 'HEAD':
-                rev = 'HEAD'
-            return rev
-
-        def basestate(self):
-            # basestate() was introduced by bcb973abcc0b in 2.2
-            if self._state[1] == 'HEAD':
-                return 'HEAD'
-            return super(svnsubrepo, self).basestate()
+class svnsubrepo(subrepo.svnsubrepo):
+    def __init__(self, ctx, path, state):
+        state = (state[0].split(':', 1)[1], state[1])
+        super(svnsubrepo, self).__init__(ctx, path, state)
+
+    def get(self, state, *args, **kwargs):
+        # Resolve source first
+        line = state[0].split(':', 1)[1]
+        source, pegrev = parsedefinition(line)[2:4]
+        try:
+            # Getting the root SVN repository URL is expensive.
+            # Assume the externals is absolute.
+            source = resolvesource(self._ui, None, source)
+        except RelativeSourceError:
+            svnurl = self._ctx._repo.ui.expandpath('default')
+            svnroot = getsvninfo(util.normalize_url(svnurl))[1]
+            source = resolvesource(self._ui, svnroot, source)
+        # hg 1.9 and higher, append the rev as a peg revision to
+        # the source URL, so we cannot add our own. We assume
+        # that "-r10 url@2" will be similar to "url@10" most of
+        # the time.
+        state = (source, state[1])
+        return super(svnsubrepo, self).get(state, *args, **kwargs)
+
+    def dirty(self, ignoreupdate=False):
+        # You cannot compare anything with HEAD. Just accept it
+        # can be anything.
+        if hasattr(self, '_wcrevs'):
+            wcrevs = self._wcrevs()
+        else:
+            wcrev = self._wcrev()
+            wcrevs = (wcrev, wcrev)
+        if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or
+            self._state[1] in wcrevs or ignoreupdate)
+            and not self._wcchanged()[0]):
+            return False
+        return True
+
+    def commit(self, text, user, date):
+        rev = super(svnsubrepo, self).commit(text, user, date)
+        # Keep unversioned externals unversioned
+        if self._state[1] == 'HEAD':
+            rev = 'HEAD'
+        return rev
+
+    def basestate(self):
+        # basestate() was introduced by bcb973abcc0b in 2.2
+        if self._state[1] == 'HEAD':
+            return 'HEAD'
+        return super(svnsubrepo, self).basestate()
--- a/hgsubversion/svnmeta.py
+++ b/hgsubversion/svnmeta.py
@@ -1,4 +1,3 @@
-import cPickle as pickle
 import posixpath
 import os
 import tempfile
@@ -17,7 +16,7 @@ import editor
 
 class SVNMeta(object):
 
-    def __init__(self, repo, uuid=None, subdir=None):
+    def __init__(self, repo, uuid=None, subdir=None, skiperrorcheck=False):
         """path is the path to the target hg repo.
 
         subdir is the subdirectory of the edits *on the svn server*.
@@ -26,52 +25,115 @@ class SVNMeta(object):
         self.ui = repo.ui
         self.repo = repo
         self.path = os.path.normpath(repo.join('..'))
+        self._skiperror = skiperrorcheck
 
-        if not os.path.isdir(self.meta_data_dir):
-            os.makedirs(self.meta_data_dir)
+        if not os.path.isdir(self.metapath):
+            os.makedirs(self.metapath)
         self.uuid = uuid
         self.subdir = subdir
-        self.revmap = maps.RevMap(repo)
-
-        author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
-        authors = util.configpath(self.ui, 'authormap')
-        self.usebranchnames = self.ui.configbool('hgsubversion',
-                                                 'usebranchnames', True)
-        branchmap = util.configpath(self.ui, 'branchmap')
-        tagmap = util.configpath(self.ui, 'tagmap')
-        filemap = util.configpath(self.ui, 'filemap')
-
-        self.branches = {}
-        if os.path.exists(self.branch_info_file):
-            f = open(self.branch_info_file)
-            self.branches = pickle.load(f)
-            f.close()
+        self._revmap = None
+        self.firstpulled = 0
+
+        self._gen_cachedconfig('lastpulled', 0, configname=False)
+        self._gen_cachedconfig('defaultauthors', True)
+        self._gen_cachedconfig('caseignoreauthors', False)
+        self._gen_cachedconfig('defaulthost', self.uuid)
+        self._gen_cachedconfig('usebranchnames', True)
+
+        self.branches = util.load(self.branch_info_file) or {}
         self.prevbranches = dict(self.branches)
-        self.tags = maps.Tags(repo)
-        self._layout = layouts.detect.layout_from_file(self.meta_data_dir,
+        self._tags = None
+        self._layout = layouts.detect.layout_from_file(self.metapath,
                                                        ui=self.repo.ui)
         self._layoutobj = None
 
-        self.authors = maps.AuthorMap(self.ui, self.authors_file,
-                                 defaulthost=author_host)
-        if authors: self.authors.load(authors)
+        self._authors = None
 
-        self.branchmap = maps.BranchMap(self.ui, self.branchmapfile)
-        if branchmap:
-            self.branchmap.load(branchmap)
+        self._branchmap = None
 
-        self.tagmap = maps.TagMap(self.ui, self.tagmapfile)
-        if tagmap:
-            self.tagmap.load(tagmap)
+        self._tagmap = None
 
-        self.filemap = maps.FileMap(self.ui, self.filemap_file)
-        if filemap:
-            self.filemap.load(filemap)
+        self._filemap = None
 
         self.lastdate = '1970-01-01 00:00:00 -0000'
         self.addedtags = {}
         self.deletedtags = {}
 
+    def _get_cachedconfig(self, name, filename, configname, default):
+        """Return a cached value for a config option. If the cache is uninitialized
+        then try to read its value from disk. Option can be overridden by the
+        commandline.
+            name: property name, e.g. 'lastpulled'
+            filename: name of file in .hg/svn
+            configname: commandline option name
+            default: default value
+        """
+        varname = '_' + name
+        if getattr(self, varname) is None:
+            # construct the file path from metapath (e.g. .hg/svn) plus the
+            # filename
+            f = os.path.join(self.metapath, filename)
+
+            # load the config property (i.e. command-line or .hgrc)
+            c = None
+            if configname:
+                # a little awkward but we need to convert the option from a
+                # string to whatever type the default value is, so we use the
+                # type of `default` to determine with ui.config method to call
+                c = None
+                if isinstance(default, bool):
+                    c = self.ui.configbool('hgsubversion', configname, default)
+                elif isinstance(default, int):
+                    c = self.ui.configint('hgsubversion', configname, default)
+                elif isinstance(default, list):
+                    c = self.ui.configlist('hgsubversion', configname, default)
+                else:
+                    c = self.ui.config('hgsubversion', configname, default)
+
+            # load the value from disk
+            val = util.load(f, default=default)
+
+            # prefer the non-default, and the one sent from command-line
+            if c is not None and c != val and c != default:
+                val = c
+
+            # set the value as the one from disk (or default if not found)
+            setattr(self, varname, val)
+
+            # save the value to disk by using the setter property
+            setattr(self, name, val)
+
+        return getattr(self, varname)
+
+    def _set_cachedconfig(self, value, name, filename):
+        varname = '_' + name
+        f = os.path.join(self.metapath, filename)
+        setattr(self, varname, value)
+        util.dump(value, f)
+
+    def _gen_cachedconfig(self, name, default=None, filename=None,
+                          configname=None):
+        """Generate an attribute for reading (and caching) config data.
+
+        This method constructs a new attribute on self with the given name.
+        The actual value from the config file will be read lazily, and then
+        cached once that read has occurred. No cache invalidation will happen,
+        so within a session these values shouldn't be required to mutate.
+        """
+        setattr(SVNMeta, '_' + name, None)
+        if filename is None:
+            filename = name
+        if configname is None:
+            configname = name
+        prop = property(lambda x: x._get_cachedconfig(name,
+                                                      filename,
+                                                      configname,
+                                                      default),
+                        lambda x, y: x._set_cachedconfig(y,
+                                                         name,
+                                                         filename))
+        setattr(SVNMeta, name, prop)
+
     @property
     def layout(self):
         # this method can't determine the layout, but it needs to be
@@ -79,7 +141,7 @@ class SVNMeta(object):
         # gets called
         if not self._layout or self._layout == 'auto':
             self._layout = layouts.detect.layout_from_config(self.repo.ui)
-            layouts.persist.layout_to_file(self.meta_data_dir, self._layout)
+            layouts.persist.layout_to_file(self.metapath, self._layout)
         return self._layout
 
     @property
@@ -101,24 +163,23 @@ class SVNMeta(object):
         if subdir:
             subdir = '/'.join(p for p in subdir.split('/') if p)
 
-        subdirfile = os.path.join(self.meta_data_dir, 'subdir')
+        self.__subdir = None
+        subdirfile = os.path.join(self.metapath, 'subdir')
 
         if os.path.isfile(subdirfile):
-            stored_subdir = open(subdirfile).read()
+            stored_subdir = util.load(subdirfile)
             assert stored_subdir is not None
             if subdir is None:
                 self.__subdir = stored_subdir
-            elif subdir != stored_subdir:
+            elif subdir and subdir != stored_subdir:
                 raise hgutil.Abort('unable to work on a different path in the '
                                    'repository')
             else:
                 self.__subdir = subdir
         elif subdir is not None:
-            f = open(subdirfile, 'w')
-            f.write(subdir)
-            f.close()
+            util.dump(subdir, subdirfile)
             self.__subdir = subdir
-        else:
+        elif not self._skiperror:
             raise hgutil.Abort("hgsubversion metadata unavailable; "
                                "please run 'hg svn rebuildmeta'")
 
@@ -130,19 +191,18 @@ class SVNMeta(object):
         return self.__uuid
 
     def _set_uuid(self, uuid):
-        uuidfile = os.path.join(self.meta_data_dir, 'uuid')
+        self.__uuid = None
+        uuidfile = os.path.join(self.metapath, 'uuid')
         if os.path.isfile(uuidfile):
-            stored_uuid = open(uuidfile).read()
+            stored_uuid = util.load(uuidfile)
             assert stored_uuid
             if uuid and uuid != stored_uuid:
                 raise hgutil.Abort('unable to operate on unrelated repository')
             self.__uuid = uuid or stored_uuid
         elif uuid:
-            f = open(uuidfile, 'w')
-            f.write(uuid)
-            f.close()
+            util.dump(uuid, uuidfile)
             self.__uuid = uuid
-        else:
+        elif not self._skiperror:
             raise hgutil.Abort("hgsubversion metadata unavailable; "
                                "please run 'hg svn rebuildmeta'")
 
@@ -150,29 +210,74 @@ class SVNMeta(object):
                     'Error-checked UUID of source Subversion repository.')
 
     @property
-    def meta_data_dir(self):
+    def metapath(self):
         return os.path.join(self.path, '.hg', 'svn')
 
     @property
     def branch_info_file(self):
-        return os.path.join(self.meta_data_dir, 'branch_info')
+        return os.path.join(self.metapath, 'branch_info')
 
     @property
     def authors_file(self):
-        return os.path.join(self.meta_data_dir, 'authors')
+        return os.path.join(self.metapath, 'authors')
+
+    @property
+    def authors(self):
+        if self._authors is None:
+            self._authors = maps.AuthorMap(self)
+        return self._authors
 
     @property
     def filemap_file(self):
-        return os.path.join(self.meta_data_dir, 'filemap')
+        return os.path.join(self.metapath, 'filemap')
+
+    @property
+    def filemap(self):
+        if self._filemap is None:
+            self._filemap = maps.FileMap(self)
+        return self._filemap
+
+    @property
+    def branchmap_file(self):
+        return os.path.join(self.metapath, 'branchmap')
+
+    @property
+    def branchmap(self):
+        if self._branchmap is None:
+            self._branchmap = maps.BranchMap(self)
+        return self._branchmap
 
     @property
-    def branchmapfile(self):
-        return os.path.join(self.meta_data_dir, 'branchmap')
+    def tagfile(self):
+        # called tagmap for backwards compatibility
+        return os.path.join(self.metapath, 'tagmap')
 
     @property
-    def tagmapfile(self):
+    def tags(self):
+        if self._tags is None:
+            self._tags = maps.Tags(self)
+        return self._tags
+
+    @property
+    def tagmap_file(self):
         # called tag-renames for backwards compatibility
-        return os.path.join(self.meta_data_dir, 'tag-renames')
+        return os.path.join(self.metapath, 'tag-renames')
+
+    @property
+    def tagmap(self):
+        if self._tagmap is None:
+            self._tagmap = maps.TagMap(self)
+        return self._tagmap
+
+    @property
+    def revmap_file(self):
+        return os.path.join(self.metapath, 'rev_map')
+
+    @property
+    def revmap(self):
+        if self._revmap is None:
+            self._revmap = maps.RevMap(self)
+        return self._revmap
 
     def fixdate(self, date):
         if date is not None:
@@ -187,7 +292,7 @@ class SVNMeta(object):
         '''Save the Subversion metadata. This should really be called after
         every revision is created.
         '''
-        util.pickle_atomic(self.branches, self.branch_info_file)
+        util.dump(self.branches, self.branch_info_file)
 
     def localname(self, path):
         """Compute the local name for a branch located at path.
@@ -247,7 +352,7 @@ class SVNMeta(object):
 
     @property
     def taglocations(self):
-        return self.layoutobj.taglocations(self.meta_data_dir)
+        return self.layoutobj.taglocations(self.metapath)
 
     def get_path_tag(self, path):
         """If path could represent the path to a tag, returns the
@@ -395,7 +500,7 @@ class SVNMeta(object):
                     return node.hex(self.revmap[tagged])
                 tag = fromtag
             # Reference an existing tag
-            limitedtags = maps.Tags(self.repo, endrev=number - 1)
+            limitedtags = maps.Tags(self, endrev=number - 1)
             if tag in limitedtags:
                 return limitedtags[tag]
         r, br = self.get_parent_svn_branch_and_rev(number - 1, branch, exact)
--- a/hgsubversion/svnrepo.py
+++ b/hgsubversion/svnrepo.py
@@ -71,10 +71,6 @@ def generate_repo_class(ui, repo):
         """
         original = getattr(repo, fn.__name__, None)
 
-        # remove when dropping support for hg < 1.6.
-        if original is None and fn.__name__ == 'findoutgoing':
-            return
-
         def wrapper(self, *args, **opts):
             capable = getattr(args[0], 'capable', lambda x: False)
             if capable('subversion'):
@@ -112,8 +108,8 @@ def generate_repo_class(ui, repo):
         def findoutgoing(self, remote, base=None, heads=None, force=False):
             return wrappers.findoutgoing(repo, remote, heads, force)
 
-        def svnmeta(self, uuid=None, subdir=None):
-            return svnmeta.SVNMeta(self, uuid, subdir)
+        def svnmeta(self, uuid=None, subdir=None, skiperrorcheck=False):
+            return svnmeta.SVNMeta(self, uuid, subdir, skiperrorcheck)
 
     repo.__class__ = svnlocalrepo
 
--- a/hgsubversion/util.py
+++ b/hgsubversion/util.py
@@ -1,8 +1,9 @@
-import cPickle as pickle
+import compathacks
 import errno
 import re
 import os
 import urllib
+import json
 
 from mercurial import cmdutil
 from mercurial import error
@@ -120,57 +121,74 @@ def normalize_url(url):
         url = '%s#%s' % (url, checkout)
     return url
 
+def _scrub(data):
+    if not data and not isinstance(data, list):
+        return ''
+    return data
+
+def _descrub(data):
+    if isinstance(data, list):
+        return tuple(data)
+    if data == '':
+        return None
+    return data
+
+def _convert(input, visitor):
+    if isinstance(input, dict):
+        scrubbed = {}
+        d = dict([(_convert(key, visitor), _convert(value, visitor))
+                  for key, value in input.iteritems()])
+        for key, val in d.iteritems():
+            scrubbed[visitor(key)] = visitor(val)
+        return scrubbed
+    elif isinstance(input, list):
+        return [_convert(element, visitor) for element in input]
+    elif isinstance(input, unicode):
+        return input.encode('utf-8')
+    return input
+
+def dump(data, file_path):
+    """Serialize some data to a path atomically.
+
+    This is present because I kept corrupting my revmap by managing to hit ^C
+    during the serialization of that file.
+    """
+    f = hgutil.atomictempfile(file_path, 'w+b', 0644)
+    json.dump(_convert(data, _scrub), f)
+    f.close()
 
-def load_string(file_path, default=None, limit=1024):
+def load(file_path, default=None, resave=True):
+    """Deserialize some data from a path.
+    """
+    data = default
     if not os.path.exists(file_path):
-        return default
+        return data
+
+    f = open(file_path)
     try:
-        f = open(file_path, 'r')
-        ret = f.read(limit)
+        data = _convert(json.load(f), _descrub)
         f.close()
-    except:
-        return default
-    if ret == '':
-        return default
-    return ret
-
-
-def save_string(file_path, string):
-    if string is None:
-        string = ""
-    f = open(file_path, 'wb')
-    f.write(str(string))
-    f.close()
-
-def pickle_atomic(data, file_path):
-    """pickle some data to a path atomically.
+    except ValueError:
+        try:
+            # Ok, JSON couldn't be loaded, so we'll try the old way of using pickle
+            data = compathacks.pickle_load(f)
+        except:
+            # well, pickle didn't work either, so we reset the file pointer and
+            # read the string
+            f.seek(0)
+            data = f.read()
 
-    This is present because I kept corrupting my revmap by managing to hit ^C
-    during the pickle of that file.
-    """
-    f = hgutil.atomictempfile(file_path, 'w+b', 0644)
-    pickle.dump(data, f)
-    # Older versions of hg have .rename() instead of .close on
-    # atomictempfile.
-    if getattr(hgutil.atomictempfile, 'rename', False):
-        f.rename()
-    else:
+        # convert the file to json immediately
         f.close()
+        if resave:
+            dump(data, file_path)
+    return data
 
 def parseurl(url, heads=[]):
-    parsed = hg.parseurl(url, heads)
-    if len(parsed) == 3:
-        # old hg, remove when we can be 1.5-only
-        svn_url, heads, checkout = parsed
-    else:
-        svn_url, heads = parsed
-        if isinstance(heads, tuple) and len(heads) == 2:
-            # hg 1.6 or later
-            _junk, heads = heads
-        if heads:
-            checkout = heads[0]
-        else:
-            checkout = None
+    checkout = None
+    svn_url, (_junk, heads) = hg.parseurl(url, heads)
+    if heads:
+        checkout = heads[0]
     return svn_url, heads, checkout
 
 
@@ -325,9 +343,11 @@ def revset_fromsvn(repo, subset, x):
 
     rev = repo.changelog.rev
     bin = node.bin
+    meta = repo.svnmeta(skiperrorcheck=True)
     try:
         svnrevs = set(rev(bin(l.split(' ', 2)[1]))
-                      for l in maps.RevMap.readmapfile(repo, missingok=False))
+                      for l in maps.RevMap.readmapfile(meta.revmap_file,
+                                                       missingok=False))
         return filter(svnrevs.__contains__, subset)
     except IOError, err:
         if err.errno != errno.ENOENT:
@@ -350,8 +370,9 @@ def revset_svnrev(repo, subset, x):
 
     rev = rev + ' '
     revs = []
+    meta = repo.svnmeta(skiperrorcheck=True)
     try:
-        for l in maps.RevMap.readmapfile(repo, missingok=False):
+        for l in maps.RevMap.readmapfile(meta.revmap_file, missingok=False):
             if l.startswith(rev):
                 n = l.split(' ', 2)[1]
                 r = repo[node.bin(n)].rev()
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -97,7 +97,7 @@ def incoming(orig, ui, repo, origsource=
     meta = repo.svnmeta(svn.uuid, svn.subdir)
 
     ui.status('incoming changes from %s\n' % other.svnurl)
-    svnrevisions = list(svn.revisions(start=meta.revmap.youngest))
+    svnrevisions = list(svn.revisions(start=meta.lastpulled))
     if opts.get('newest_first'):
         svnrevisions.reverse()
     # Returns 0 if there are incoming changes, 1 otherwise.
@@ -181,7 +181,8 @@ def push(repo, dest, force, revs):
     checkpush = getattr(repo, 'checkpush', None)
     if checkpush:
         try:
-            # The checkpush function changed as of e10000369b47 in mercurial
+            # The checkpush function changed as of e10000369b47 (first
+            # in 3.0) in mercurial
             from mercurial.exchange import pushoperation
             pushop = pushoperation(repo, dest, force, revs, False)
             checkpush(pushop)
@@ -390,7 +391,7 @@ def pull(repo, source, heads=[], force=F
             meta.branchmap['default'] = branch
 
         ui = repo.ui
-        start = meta.revmap.youngest
+        start = meta.lastpulled
         origrevcount = len(meta.revmap)
 
         if start <= 0:
@@ -484,7 +485,7 @@ def pull(repo, source, heads=[], force=F
         util.swap_out_encoding(old_encoding)
 
     if lastpulled is not None:
-        meta.revmap.youngest = lastpulled
+        meta.lastpulled = lastpulled
     revisions = len(meta.revmap) - oldrevisions
 
     if revisions == 0:
@@ -573,11 +574,7 @@ def clone(orig, ui, source, dest=None, *
 
     data = {}
     def hgclonewrapper(orig, ui, *args, **opts):
-        if getattr(hg, 'peer', None):
-            # Since 1.9 (d976542986d2)
-            origsource = args[1]
-        else:
-            origsource = args[0]
+        origsource = args[1]
 
         if isinstance(origsource, str):
             source, branch, checkout = util.parseurl(ui.expandpath(origsource),
@@ -612,14 +609,11 @@ def clone(orig, ui, source, dest=None, *
 
     dstrepo = data.get('dstrepo')
     srcrepo = data.get('srcrepo')
+    dst = dstrepo.local()
 
     if dstrepo.local() and srcrepo.capable('subversion'):
         dst = dstrepo.local()
-        if isinstance(dst, bool):
-            # Apparently <= hg@1.9
-            fd = dstrepo.opener("hgrc", "a", text=True)
-        else:
-            fd = dst.opener("hgrc", "a", text=True)
+        fd = dst.opener("hgrc", "a", text=True)
         preservesections = set(s for s, v in optionmap.itervalues())
         preservesections |= extrasections
         for section in preservesections:
--- a/tests/comprehensive/test_custom_layout.py
+++ b/tests/comprehensive/test_custom_layout.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
--- a/tests/comprehensive/test_rebuildmeta.py
+++ b/tests/comprehensive/test_rebuildmeta.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import unittest
 import sys
 
@@ -19,6 +18,7 @@ from mercurial import ui
 from hgsubversion import compathacks
 from hgsubversion import svncommands
 from hgsubversion import svnmeta
+from hgsubversion import util
 
 # These test repositories have harmless skew in rebuildmeta for the
 # last-pulled-rev because the last rev in svn causes absolutely no
@@ -110,23 +110,29 @@ def _run_assertions(self, name, single, 
     for tf in ('lastpulled', 'rev_map', 'uuid', 'tagmap', 'layout', 'subdir',):
 
         stf = os.path.join(src.path, 'svn', tf)
-        self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf)
+        # the generation of tagmap is lazy so it doesn't strictly need to exist
+        # if it's not being used
+        if not stf.endswith('tagmap'):
+            self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf)
         dtf = os.path.join(dest.path, 'svn', tf)
-        self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf)
-        old, new = open(stf).read(), open(dtf).read()
+        old, new = None, None
+        if not dtf.endswith('tagmap'):
+            self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf)
+        if os.path.isfile(stf) and os.path.isfile(dtf):
+            old, new = util.load(stf, resave=False), util.load(dtf, resave=False)
         if tf == 'lastpulled' and (name,
                                    self.stupid, single) in expect_youngest_skew:
             self.assertNotEqual(old, new,
                                 'rebuildmeta unexpected match on youngest rev!')
             continue
-        self.assertMultiLineEqual(old, new, tf + ' differs')
+        self.assertEqual(old, new, tf + ' differs')
         try:
           self.assertEqual(src.branchmap(), dest.branchmap())
         except AttributeError:
           # hg 2.8 and earlier
           self.assertEqual(src.branchtags(), dest.branchtags())
-    srcbi = pickle.load(open(os.path.join(src.path, 'svn', 'branch_info')))
-    destbi = pickle.load(open(os.path.join(dest.path, 'svn', 'branch_info')))
+    srcbi = util.load(os.path.join(src.path, 'svn', 'branch_info'))
+    destbi = util.load(os.path.join(dest.path, 'svn', 'branch_info'))
     self.assertEqual(sorted(srcbi.keys()), sorted(destbi.keys()))
     revkeys = svnmeta.SVNMeta(dest).revmap.keys()
     for branch in destbi:
--- a/tests/comprehensive/test_stupid_pull.py
+++ b/tests/comprehensive/test_stupid_pull.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
--- a/tests/comprehensive/test_verify_and_startrev.py
+++ b/tests/comprehensive/test_verify_and_startrev.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
--- a/tests/test_fetch_mappings.py
+++ b/tests/test_fetch_mappings.py
@@ -84,10 +84,15 @@ class MapTests(test_util.TestBase):
     def test_author_map_no_overwrite(self):
         cwd = os.path.dirname(__file__)
         orig = os.path.join(cwd, 'fixtures', 'author-map-test.txt')
-        new = open(self.authors, 'w')
+        # create a fake hgsubversion repo
+        repopath = os.path.join(self.wc_path, '.hg')
+        repopath = os.path.join(repopath, 'svn')
+        if not os.path.isdir(repopath):
+            os.makedirs(repopath)
+        new = open(os.path.join(repopath, 'authors'), 'w')
         new.write(open(orig).read())
         new.close()
-        test = maps.AuthorMap(self.ui(), self.authors)
+        test = maps.AuthorMap(self.repo.svnmeta(skiperrorcheck=True))
         fromself = set(test)
         test.load(orig)
         all_tests = set(test)
@@ -285,21 +290,6 @@ class MapTests(test_util.TestBase):
         for r in repo:
             self.assertEquals(verify.verify(ui, repo, rev=r), 0)
 
-    def test_branchmap_no_replacement(self):
-        '''
-        test that empty mappings are rejected
-
-        Empty mappings are lines like 'this ='. The most sensible thing to do
-        is to not convert the 'this' branches. Until we can do that, we settle
-        with aborting.
-        '''
-        repo_path = self.load_svndump('propset-branch.svndump')
-        branchmap = open(self.branchmap, 'w')
-        branchmap.write("closeme =\n")
-        branchmap.close()
-        self.assertRaises(hgutil.Abort,
-                          maps.BranchMap, self.ui(), self.branchmap)
-
     def test_tagmap(self):
         repo_path = self.load_svndump('basic_tag_tests.svndump')
         tagmap = open(self.tagmap, 'w')
--- a/tests/test_push_command.py
+++ b/tests/test_push_command.py
@@ -496,7 +496,7 @@ class PushTests(test_util.TestBase):
                                               copied=False)
         ctx = context.memctx(repo,
                              (repo['default'].node(), node.nullid),
-                             'message',
+                             'mutate already-special file alpha',
                              ['alpha', ],
                              file_callback2,
                              'author',
@@ -521,7 +521,7 @@ class PushTests(test_util.TestBase):
                                               copied=False)
         ctx = context.memctx(repo,
                              (repo['default'].node(), node.nullid),
-                             'message',
+                             'convert alpha back to regular file',
                              ['alpha', ],
                              file_callback3,
                              'author',
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -175,6 +175,8 @@ rename a tag
         repo = self._load_fixture_and_fetch('tag_name_same_as_branch.svndump')
         tm = os.path.join(repo.path, 'svn', 'tagmap')
         open(tm, 'w').write('1\n')
+        # force tags to load since it is lazily loaded when needed
+        repo.svnmeta().tags
         commands.pull(repo.ui, repo)
         self.assertEqual(open(tm).read().splitlines()[0], '2')