changeset 1180:cdad3b3e4a09

Merge with stable.
author Augie Fackler <raf@durin42.com>
date Fri, 02 May 2014 10:04:10 -0400
parents a22d4972e01f (diff) e4b737479302 (current diff)
children eb4c7bc23f9e
files .hgtags hgsubversion/wrappers.py
diffstat 23 files changed, 265 insertions(+), 330 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags
+++ b/.hgtags
@@ -9,4 +9,5 @@ 0cbf9fd89672e73165e1bb4db1ec8f7f65b95c94
 07234759a3f750029ccaa001837d42fa12dd33ee 1.4
 77b22e5b4ea6c248e079afd0f1e544cb5690ce20 1.5
 d0f3a5c2cb56ce65d9ef1c611c8bfbebdc3bef34 1.5.1
+7d47a0f731354505ed9ae8d60d2a6996e8c3294f 1.6
 8caf1226adecb322e90ddb3817c604fa2fe8a66d 1.6.1
--- 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,11 +1,17 @@
-"""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.
-
-  Works around branchtags() vanishing between 2.8 and 2.9.
-  """
-  try:
-    return set(repo.branchmap())
-  except AttributeError:
-    return set(repo.branchtags())
+    """Return the set of branches present in a repo.
+
+    Works around branchtags() vanishing between 2.8 and 2.9.
+    """
+    try:
+        return set(repo.branchmap())
+    except AttributeError:
+        return set(repo.branchtags())
+
+def pickle_load(f):
+    import cPickle as pickle
+    f.seek(0)
+    return pickle.load(f)
--- 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
@@ -203,8 +203,7 @@ class RevMap(dict):
         # 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._youngest = util.load(self.ypath, 0)
         self.oldest = 0
         if os.path.isfile(self.path):
             self._load()
@@ -213,7 +212,7 @@ class RevMap(dict):
 
     def _set_youngest(self, rev):
         self._youngest = max(self._youngest, rev)
-        util.save_string(self.ypath, str(self._youngest) + '\n')
+        util.dump(self._youngest, self.ypath)
 
     def _get_youngest(self):
         return self._youngest
--- 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:
--- 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,36 +54,29 @@ 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
     sofar = []
     branchinfo = {}
+    youngestpath = os.path.join(meta.metapath, 'lastpulled')
     if partial:
         try:
-            youngestpath = os.path.join(svnmetadir, 'lastpulled')
             foundpartialinfo = False
             if os.path.exists(youngestpath):
-                youngest = int(util.load_string(youngestpath).strip())
+                youngest = util.load(youngestpath)
                 sofar = list(maps.RevMap.readmapfile(repo))
                 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 +88,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 +137,7 @@ def _buildmeta(ui, repo, args, partial=F
             else:
                 closed.add(parentctx.rev())
 
-    lastpulled.write(str(youngest) + '\n')
+    util.dump(youngest, youngestpath)
     ui.progress('prepare', None, total=numrevs)
 
     for rev in xrange(startrev, len(repo)):
@@ -197,11 +170,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 +187,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 +205,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 +216,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 +279,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
@@ -16,7 +15,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*.
@@ -25,12 +24,13 @@ 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)
+        self._revmap = None
 
         author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
         authors = util.configpath(self.ui, 'authormap')
@@ -40,14 +40,10 @@ class SVNMeta(object):
         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.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
 
@@ -55,11 +51,11 @@ class SVNMeta(object):
                                  defaulthost=author_host)
         if authors: self.authors.load(authors)
 
-        self.branchmap = maps.BranchMap(self.ui, self.branchmapfile)
+        self.branchmap = maps.BranchMap(self.ui, self.branchmap_file)
         if branchmap:
             self.branchmap.load(branchmap)
 
-        self.tagmap = maps.TagMap(self.ui, self.tagmapfile)
+        self.tagmap = maps.TagMap(self.ui, self.tagmap_file)
         if tagmap:
             self.tagmap.load(tagmap)
 
@@ -78,7 +74,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
@@ -100,24 +96,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'")
 
@@ -129,19 +124,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'")
 
@@ -149,29 +143,50 @@ 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 filemap_file(self):
-        return os.path.join(self.meta_data_dir, 'filemap')
+        return os.path.join(self.metapath, 'filemap')
+
+    @property
+    def branchmap_file(self):
+        return os.path.join(self.metapath, 'branchmap')
+
+    @property
+    def tagfile(self):
+        # called tagmap for backwards compatibility
+        return os.path.join(self.metapath, 'tagmap')
 
     @property
-    def branchmapfile(self):
-        return os.path.join(self.meta_data_dir, 'branchmap')
+    def tags(self):
+        if self._tags is None:
+            self._tags = maps.Tags(self.repo)
+        return self._tags
 
     @property
-    def tagmapfile(self):
+    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 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.repo)
+        return self._revmap
 
     def fixdate(self, date):
         if date is not None:
@@ -186,7 +201,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.
@@ -246,7 +261,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
--- 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.
 
-def load_string(file_path, default=None, limit=1024):
+    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(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
 
 
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -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)
@@ -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_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')