changeset 1303:553c40023729 stable

Merge default into stable for a release.
author Augie Fackler <raf@durin42.com>
date Fri, 30 Jan 2015 10:00:12 -0500 (2015-01-30)
parents b5520673f6f2 (current diff) 9d5cff8d7f67 (diff)
children c1756971f882
files hgsubversion/layouts/persist.py
diffstat 15 files changed, 180 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/layouts/__init__.py
+++ b/hgsubversion/layouts/__init__.py
@@ -13,14 +13,12 @@ from mercurial import util as hgutil
 
 import custom
 import detect
-import persist
 import single
 import standard
 
 __all__ = [
     "detect",
     "layout_from_name",
-    "persist",
     ]
 
 # This is the authoritative store of what layouts are available.
@@ -33,7 +31,7 @@ NAME_TO_CLASS = {
 }
 
 
-def layout_from_name(name, ui):
+def layout_from_name(name, meta):
     """Returns a layout module given the layout name
 
     You should use one of the layout.detect.* functions to get the
@@ -42,5 +40,5 @@ def layout_from_name(name, ui):
     """
 
     if name not in NAME_TO_CLASS:
-        raise hgutil.Abort('Unknown hgsubversion layout: %s' %name)
-    return NAME_TO_CLASS[name](ui)
+        raise hgutil.Abort('Unknown hgsubversion layout: %s' % name)
+    return NAME_TO_CLASS[name](meta)
--- a/hgsubversion/layouts/base.py
+++ b/hgsubversion/layouts/base.py
@@ -9,8 +9,8 @@ from mercurial import util as hgutil
 
 class BaseLayout(object):
 
-    def __init__(self, ui):
-        self.ui = ui
+    def __init__(self, meta):
+        self.meta = meta
 
     def __unimplemented(self, method_name):
         raise NotImplementedError(
@@ -48,7 +48,8 @@ class BaseLayout(object):
         """
         self.__unimplemented('remotepath')
 
-    def taglocations(self, metapath):
+    @property
+    def taglocations(self):
         """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
@@ -12,13 +12,13 @@ import base
 
 class CustomLayout(base.BaseLayout):
 
-    def __init__(self, ui):
-        base.BaseLayout.__init__(self, ui)
+    def __init__(self, meta):
+        base.BaseLayout.__init__(self, meta)
 
         self.svn_to_hg = {}
         self.hg_to_svn = {}
 
-        for hg_branch, svn_path in ui.configitems('hgsubversionbranch'):
+        for hg_branch, svn_path in meta.ui.configitems('hgsubversionbranch'):
 
             hg_branch = hg_branch.strip()
             if hg_branch == 'default' or not hg_branch:
@@ -64,7 +64,8 @@ class CustomLayout(base.BaseLayout):
             subdir += '/'
         return subdir + self.remotename(branch)
 
-    def taglocations(self, metapath):
+    @property
+    def taglocations(self):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/detect.py
+++ b/hgsubversion/layouts/detect.py
@@ -12,7 +12,7 @@ from mercurial import util as hgutil
 
 import __init__ as layouts
 
-def layout_from_subversion(svn, revision=None, ui=None):
+def layout_from_subversion(svn, revision=None, meta=None):
     """ Guess what layout to use based on directories under the svn root.
 
     This is intended for use during bootstrapping.  It guesses which
@@ -39,10 +39,10 @@ def layout_from_subversion(svn, revision
         layout = 'standard'
     else:
         layout = 'single'
-    ui.setconfig('hgsubversion', 'layout', layout)
+    meta.ui.setconfig('hgsubversion', 'layout', layout)
     return layout
 
-def layout_from_config(ui, allow_auto=False):
+def layout_from_config(meta, allow_auto=False):
     """ Load the layout we are using based on config
 
     We will read the config from the ui object.  Pass allow_auto=True
@@ -51,32 +51,29 @@ def layout_from_config(ui, allow_auto=Fa
     detect the layout as auto.
     """
 
-    layout = ui.config('hgsubversion', 'layout', default='auto')
+    layout = meta.ui.config('hgsubversion', 'layout', default='auto')
     if layout == 'auto' and not allow_auto:
         raise hgutil.Abort('layout not yet determined')
     elif layout not in layouts.NAME_TO_CLASS and layout != 'auto':
         raise hgutil.Abort("unknown layout '%s'" % layout)
     return layout
 
-def layout_from_file(metapath, ui=None):
+def layout_from_file(meta):
     """ Load the layout in use from the metadata file.
-
-    If you pass the ui arg, we will also write the layout to the
-    config for that ui.
-
     """
 
-    layout = None
-    layoutfile = os.path.join(metapath, 'layout')
-    if os.path.exists(layoutfile):
-        f = open(layoutfile)
-        layout = f.read().strip()
-        f.close()
-        if ui:
-            ui.setconfig('hgsubversion', 'layout', layout)
+    # import late to avoid trouble when running the test suite
+    try:
+        from hgext_hgsubversion import util
+    except ImportError:
+        from hgsubversion import util
+
+    layout = util.load(meta.layout_file)
+    if layout:
+        meta.ui.setconfig('hgsubversion', 'layout', layout)
     return layout
 
-def layout_from_commit(subdir, revpath, branch, ui):
+def layout_from_commit(subdir, revpath, branch, meta):
     """ Guess what the layout is based existing commit info
 
     Specifically, this compares the subdir for the repository and the
@@ -93,7 +90,7 @@ def layout_from_commit(subdir, revpath, 
 
     candidates = set()
     for layout in layouts.NAME_TO_CLASS:
-        layoutobj = layouts.layout_from_name(layout, ui)
+        layoutobj = layouts.layout_from_name(layout, meta)
         try:
             remotepath = layoutobj.remotepath(branch, subdir)
         except KeyError:
@@ -104,7 +101,7 @@ def layout_from_commit(subdir, revpath, 
     if len(candidates) == 1:
         return candidates.pop()
     elif candidates:
-        config_layout = layout_from_config(ui, allow_auto=True)
+        config_layout = layout_from_config(meta, allow_auto=True)
         if config_layout in candidates:
             return config_layout
 
deleted file mode 100644
--- a/hgsubversion/layouts/persist.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""Code for persisting the layout config in various locations.
-
-Basically, if you want to save the layout, this is where you should go
-to do it.
-
-"""
-
-import os.path
-
-def layout_to_file(metapath, layout):
-    """Save the given layout to a file under the given metapath"""
-
-    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,8 @@ class SingleLayout(base.BaseLayout):
     def remotepath(self, branch, subdir='/'):
         return subdir or '/'
 
-    def taglocations(self, metapath):
+    @property
+    def taglocations(self):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/standard.py
+++ b/hgsubversion/layouts/standard.py
@@ -5,80 +5,70 @@ import base
 class StandardLayout(base.BaseLayout):
     """The standard trunk, branches, tags layout"""
 
-    def __init__(self, ui):
-        base.BaseLayout.__init__(self, ui)
+    def __init__(self, meta):
+        base.BaseLayout.__init__(self, meta)
 
         self._tag_locations = None
 
-        self._branch_dir = ui.config('hgsubversion', 'branchdir', 'branches')
-        if self._branch_dir[0] == '/':
-            self._branch_dir = self._branch_dir[1:]
-        if self._branch_dir[-1] != '/':
-            self._branch_dir += '/'
-
-        self._infix = ui.config('hgsubversion', 'infix', '').strip('/')
-        if self._infix:
-            self._infix = '/' + self._infix
-
-        self._trunk = 'trunk%s' % self._infix
+        # branchdir is expected to be stripped of leading slashes but retain
+        # its last slash
+        meta._gen_cachedconfig('branchdir', 'branches',
+                               pre=lambda x: '/'.join(p for p in x.split('/')
+                                                      if p) + '/')
+
+        # infix is expected to be stripped of trailing slashes but retain
+        # its first slash
+        def _infix_transform(x):
+            x = '/'.join(p for p in x.split('/') if p)
+            if x:
+                x = '/' + x
+            return x
+        meta._gen_cachedconfig('infix', '', pre=_infix_transform)
+
+        # the lambda is to ensure nested paths are handled properly
+        meta._gen_cachedconfig('taglocations', ['tags'], 'tag_locations',
+                               'tagpaths', lambda x: list(reversed(sorted(x))))
+
+    @property
+    def trunk(self):
+        return 'trunk' + self.meta.infix
 
     def localname(self, path):
-        if path == self._trunk:
+        if path == self.trunk:
             return None
-        elif path.startswith(self._branch_dir) and path.endswith(self._infix):
-            path = path[len(self._branch_dir):]
-            if self._infix:
-                path = path[:-len(self._infix)]
+        elif path.startswith(self.meta.branchdir) and path.endswith(self.meta.infix):
+            path = path[len(self.meta.branchdir):]
+            if self.meta.infix:
+                path = path[:-len(self.meta.infix)]
             return path
         return  '../%s' % path
 
     def remotename(self, branch):
         if branch == 'default' or branch is None:
-            path = self._trunk
+            path = self.trunk
         elif branch.startswith('../'):
             path =  branch[3:]
         else:
-            path = ''.join((self._branch_dir, branch, self._infix))
+            path = ''.join((self.meta.branchdir, branch, self.meta.infix))
 
         return path
 
     def remotepath(self, branch, subdir='/'):
         if subdir == '/':
             subdir = ''
-        branchpath = self._trunk
+        branchpath = self.trunk
         if branch and branch != 'default':
             if branch.startswith('../'):
                 branchpath = branch[3:]
             else:
-                branchpath = ''.join((self._branch_dir, branch, self._infix))
+                branchpath = ''.join((self.meta.branchdir, branch,
+                                      self.meta.infix))
 
         return '%s/%s' % (subdir or '', branchpath)
 
-    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
-            # hgext_ logic is already being done in core
-            from hgsubversion import util
-        except ImportError:
-            from hgext_hgsubversion import util
-
-        if self._tag_locations is None:
-
-            tag_locations_file = os.path.join(metapath, 'tag_locations')
-            self._tag_locations = util.load(tag_locations_file)
-
-            if not self._tag_locations:
-                self._tag_locations = self.ui.configlist('hgsubversion',
-                                                        'tagpaths',
-                                                        ['tags'])
-            util.dump(self._tag_locations, tag_locations_file)
-
-            # ensure nested paths are handled properly
-            self._tag_locations.sort()
-            self._tag_locations.reverse()
-
-        return self._tag_locations
+    @property
+    def taglocations(self):
+        return self.meta.taglocations
 
     def get_path_tag(self, path, taglocations):
         for tagspath in taglocations:
@@ -109,22 +99,22 @@ class StandardLayout(base.BaseLayout):
             return candidate, '/'.join(components)
 
         if path == 'trunk' or path.startswith('trunk/'):
-            return self._trunk, path[len(self._trunk) + 1:]
+            return self.trunk, path[len(self.trunk) + 1:]
 
-        if path.startswith(self._branch_dir):
-            path = path[len(self._branch_dir):]
+        if path.startswith(self.meta.branchdir):
+            path = path[len(self.meta.branchdir):]
             components = path.split('/', 1)
-            branch_path = ''.join((self._branch_dir, components[0]))
+            branch_path = ''.join((self.meta.branchdir, components[0]))
             if len(components) == 1:
                 local_path = ''
             else:
                 local_path = components[1]
 
             if local_path == '':
-                branch_path += self._infix
-            elif local_path.startswith(self._infix[1:] + '/'):
-                branch_path += self._infix
-                local_path = local_path[len(self._infix):]
+                branch_path += self.meta.infix
+            elif local_path.startswith(self.meta.infix[1:] + '/'):
+                branch_path += self.meta.infix
+                local_path = local_path[len(self.meta.infix):]
             return branch_path, local_path
 
         components = path.split('/')
--- a/hgsubversion/maps.py
+++ b/hgsubversion/maps.py
@@ -194,6 +194,7 @@ class RevMap(dict):
     def __init__(self, meta):
         dict.__init__(self)
         self.meta = meta
+        self._hashes = None
 
         if os.path.isfile(self.meta.revmap_file):
             self._load()
@@ -201,7 +202,9 @@ class RevMap(dict):
             self._write()
 
     def hashes(self):
-        return dict((v, k) for (k, v) in self.iteritems())
+        if self._hashes is None:
+            self._hashes = dict((v, k) for (k, v) in self.iteritems())
+        return self._hashes
 
     def branchedits(self, branch, rev):
         check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
@@ -256,6 +259,8 @@ class RevMap(dict):
         if revnum < self.meta.firstpulled or not self.meta.firstpulled:
             self.meta.firstpulled = revnum
         dict.__setitem__(self, (revnum, branch), ha)
+        if self._hashes is not None:
+            self._hashes[ha] = (revnum, branch)
 
 
 class FileMap(object):
--- a/hgsubversion/replay.py
+++ b/hgsubversion/replay.py
@@ -177,7 +177,7 @@ def _convert_rev(ui, meta, svn, r, tbdel
         meta.mapbranch(extra)
         current_ctx = context.memctx(meta.repo,
                                      parents,
-                                     util.getmessage(ui, rev),
+                                     meta.getmessage(rev),
                                      files.keys(),
                                      filectxfn,
                                      meta.authors[rev.author],
@@ -214,7 +214,7 @@ def _convert_rev(ui, meta, svn, r, tbdel
 
         current_ctx = context.memctx(meta.repo,
                                      (ha, node.nullid),
-                                     util.getmessage(ui, rev),
+                                     meta.getmessage(rev),
                                      files,
                                      del_all_files,
                                      meta.authors[rev.author],
--- a/hgsubversion/stupid.py
+++ b/hgsubversion/stupid.py
@@ -171,61 +171,23 @@ def filteriterhunks(meta):
                 yield data
     return filterhunks
 
-def patchrepoold(ui, meta, parentctx, patchfp):
-    files = {}
-    try:
-        oldpatchfile = patch.patchfile
-        olditerhunks = patch.iterhunks
-        patch.patchfile = mempatchproxy(parentctx, files)
-        patch.iterhunks = filteriterhunks(meta)
-        try:
-            # We can safely ignore the changed list since we are
-            # handling non-git patches. Touched files are known
-            # by our memory patcher.
-            patch_st = patch.applydiff(ui, patchfp, {}, strip=0)
-        finally:
-            patch.patchfile = oldpatchfile
-            patch.iterhunks = olditerhunks
-    except patch.PatchError:
-        # TODO: this happens if the svn server has the wrong mime
-        # type stored and doesn't know a file is binary. It would
-        # be better to do one file at a time and only do a
-        # full fetch on files that had problems.
-        raise BadPatchApply('patching failed')
-    # if this patch didn't apply right, fall back to exporting the
-    # entire rev.
-    if patch_st == -1:
-        assert False, ('This should only happen on case-insensitive'
-                       ' volumes.')
-    elif patch_st == 1:
-        # When converting Django, I saw fuzz on .po files that was
-        # causing revisions to end up failing verification. If that
-        # can be fixed, maybe this won't ever be reached.
-        raise BadPatchApply('patching succeeded with fuzz')
-    return files
-
-try:
-    class svnbackend(patch.repobackend):
-        def getfile(self, fname):
-            # In Mercurial >= 3.2, if fname is missing, data will be None and we
-            # should return None, None in that case. Earlier versions will raise
-            # an IOError which we let propagate up the stack.
-            f = super(svnbackend, self).getfile(fname)
-            if f is None:
-              return None, None
-            data, flags = f
-            if data is None:
-                return None, None
-            islink, isexec = flags
-            if islink:
-                data = 'link ' + data
-            return data, (islink, isexec)
-except AttributeError:
-    svnbackend = None
+class svnbackend(patch.repobackend):
+    def getfile(self, fname):
+        # In Mercurial >= 3.2, if fname is missing, data will be None and we
+        # should return None, None in that case. Earlier versions will raise
+        # an IOError which we let propagate up the stack.
+        f = super(svnbackend, self).getfile(fname)
+        if f is None:
+          return None, None
+        data, flags = f
+        if data is None:
+            return None, None
+        islink, isexec = flags
+        if islink:
+            data = 'link ' + data
+        return data, (islink, isexec)
 
 def patchrepo(ui, meta, parentctx, patchfp):
-    if not svnbackend:
-        return patchrepoold(ui, meta, parentctx, patchfp)
     store = patch.filestore(util.getfilestoresize(ui))
     try:
         touched = set()
@@ -806,7 +768,7 @@ def convert_rev(ui, meta, svn, r, tbdelt
         meta.mapbranch(extra)
         current_ctx = context.memctx(meta.repo,
                                      [parentctx.node(), revlog.nullid],
-                                     util.getmessage(ui, r),
+                                     meta.getmessage(r),
                                      files_touched,
                                      filectxfn,
                                      meta.authors[r.author],
--- a/hgsubversion/svncommands.py
+++ b/hgsubversion/svncommands.py
@@ -189,11 +189,11 @@ 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(meta.metapath)
+                                                       ctx.branch(), meta)
+            existing_layout = layouts.detect.layout_from_file(meta)
             if layout != existing_layout:
-                layouts.persist.layout_to_file(meta.metapath, layout)
-            layoutobj = layouts.layout_from_name(layout, ui)
+                util.dump(layout, meta.layout_file)
+            layoutobj = layouts.layout_from_name(layout, meta)
         elif layout == 'single':
             assert (subdir or '/') == revpath, ('Possible layout detection'
                                                 ' defect in replay')
@@ -219,7 +219,7 @@ def _buildmeta(ui, repo, args, partial=F
         # find commitpath, write to revmap
         commitpath = revpath[len(subdir)+1:]
 
-        tag_locations = layoutobj.taglocations(meta.metapath)
+        tag_locations = layoutobj.taglocations
         found_tag = False
         for location in tag_locations:
             if commitpath.startswith(location + '/'):
--- a/hgsubversion/svnexternals.py
+++ b/hgsubversion/svnexternals.py
@@ -390,6 +390,10 @@ 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)
+        # Mercurial 3.3+ set 'ui' rather than '_ui' -- set that and use 'ui'
+        # everywhere to maintain compatibility across versions
+        if not hgutil.safehasattr(self, 'ui'):
+            self.ui = ctx._repo.ui
 
     def get(self, state, *args, **kwargs):
         # Resolve source first
@@ -398,11 +402,11 @@ class svnsubrepo(subrepo.svnsubrepo):
         try:
             # Getting the root SVN repository URL is expensive.
             # Assume the externals is absolute.
-            source = resolvesource(self._ui, None, source)
+            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)
+            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
--- a/hgsubversion/svnmeta.py
+++ b/hgsubversion/svnmeta.py
@@ -22,44 +22,47 @@ class SVNMeta(object):
         subdir is the subdirectory of the edits *on the svn server*.
         It is needed for stripping paths off in certain cases.
         """
+        # simple and public variables
         self.ui = repo.ui
         self.repo = repo
         self.path = os.path.normpath(repo.join('..'))
+        self.firstpulled = 0
+        self.lastdate = '1970-01-01 00:00:00 -0000'
+        self.addedtags = {}
+        self.deletedtags = {}
+
+        # private variables
         self._skiperror = skiperrorcheck
+        self._tags = None
+        self._layoutobj = None
+        self._revmap = None
+        self._authors = None
+        self._branchmap = None
+        self._tagmap = None
+        self._filemap = None
 
+        # create .hg/svn folder if it doesn't exist
         if not os.path.isdir(self.metapath):
             os.makedirs(self.metapath)
+
+        # properties that need .hg/svn to exist
         self.uuid = uuid
         self.subdir = subdir
-        self._revmap = None
-        self.firstpulled = 0
 
+        # generated properties that have a persistent file stored on disk
         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._gen_cachedconfig('defaultmessage', '')
 
+        # misc
         self.branches = util.load(self.branch_info_file) or {}
         self.prevbranches = dict(self.branches)
-        self._tags = None
-        self._layout = layouts.detect.layout_from_file(self.metapath,
-                                                       ui=self.repo.ui)
-        self._layoutobj = None
-
-        self._authors = None
-
-        self._branchmap = None
-
-        self._tagmap = None
-
-        self._filemap = None
-
-        self.lastdate = '1970-01-01 00:00:00 -0000'
-        self.addedtags = {}
-        self.deletedtags = {}
+        self._layout = layouts.detect.layout_from_file(self)
 
-    def _get_cachedconfig(self, name, filename, configname, default):
+    def _get_cachedconfig(self, name, filename, configname, default, pre):
         """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.
@@ -67,6 +70,7 @@ class SVNMeta(object):
             filename: name of file in .hg/svn
             configname: commandline option name
             default: default value
+            pre: transformation to apply to a value before caching it.
         """
         varname = '_' + name
         if getattr(self, varname) is None:
@@ -97,6 +101,10 @@ class SVNMeta(object):
             if c is not None and c != val and c != default:
                 val = c
 
+            # apply transformation if necessary
+            if pre:
+                val = pre(val)
+
             # set the value as the one from disk (or default if not found)
             setattr(self, varname, val)
 
@@ -112,7 +120,7 @@ class SVNMeta(object):
         util.dump(value, f)
 
     def _gen_cachedconfig(self, name, default=None, filename=None,
-                          configname=None):
+                          configname=None, pre=None):
         """Generate an attribute for reading (and caching) config data.
 
         This method constructs a new attribute on self with the given name.
@@ -125,29 +133,34 @@ class SVNMeta(object):
             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))
+        prop = property(lambda x: self._get_cachedconfig(name,
+                                                         filename,
+                                                         configname,
+                                                         default,
+                                                         pre=pre),
+                        lambda x, y: self._set_cachedconfig(y,
+                                                            name,
+                                                            filename))
         setattr(SVNMeta, name, prop)
 
+    @property
+    def layout_file(self):
+        return os.path.join(self.metapath, 'layout')
+
     @property
     def layout(self):
         # this method can't determine the layout, but it needs to be
         # resolved into something other than auto before this ever
         # 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.metapath, self._layout)
+            self._layout = layouts.detect.layout_from_config(self)
+            util.dump(self._layout, self.layout_file)
         return self._layout
 
     @property
     def layoutobj(self):
         if not self._layoutobj:
-            self._layoutobj = layouts.layout_from_name(self.layout, self.ui)
+            self._layoutobj = layouts.layout_from_name(self.layout, self)
         return self._layoutobj
 
     @property
@@ -350,9 +363,19 @@ class SVNMeta(object):
             path = path[1:]
         return path
 
-    @property
-    def taglocations(self):
-        return self.layoutobj.taglocations(self.metapath)
+    def getmessage(self, rev):
+        msg = rev.message
+
+        if msg:
+            try:
+                msg.decode('utf-8')
+                return msg
+
+            except UnicodeDecodeError:
+                # ancient svn failed to enforce utf8 encoding
+                return msg.decode('iso-8859-1').encode('utf-8')
+        else:
+            return self.defaultmessage
 
     def get_path_tag(self, path):
         """If path could represent the path to a tag, returns the
@@ -362,7 +385,7 @@ class SVNMeta(object):
         (or tag) we have, for our purposes.
         """
         path = self.normalize(path)
-        return self.layoutobj.get_path_tag(path, self.taglocations)
+        return self.layoutobj.get_path_tag(path, self.layoutobj.taglocations)
 
     def split_branch_path(self, path, existing=True):
         """Figure out which branch inside our repo this path represents, and
@@ -694,7 +717,7 @@ class SVNMeta(object):
             revnum, branch = self.get_source_rev(ctx=parentctx)[:2]
         ctx = context.memctx(self.repo,
                              (parentctx.node(), node.nullid),
-                             util.getmessage(self.ui, rev),
+                             self.getmessage(rev),
                              ['.hgtags', ],
                              hgtagsfn,
                              self.authors[rev.author],
@@ -762,7 +785,7 @@ class SVNMeta(object):
 
             ctx = context.memctx(self.repo,
                                  (parent.node(), node.nullid),
-                                 util.getmessage(self.ui, rev),
+                                 self.getmessage(rev),
                                  ['.hgtags'],
                                  fctxfun,
                                  self.authors[rev.author],
@@ -784,7 +807,7 @@ class SVNMeta(object):
         self.mapbranch(extra, True)
         ctx = context.memctx(self.repo,
                              (node, revlog.nullid),
-                             util.getmessage(self.ui, rev),
+                             self.getmessage(rev),
                              [],
                              lambda x, y, z: None,
                              self.authors[rev.author],
--- a/hgsubversion/util.py
+++ b/hgsubversion/util.py
@@ -251,20 +251,6 @@ def outgoing_common_and_heads(repo, reve
         return ([sourcecx.node()], [sourcerev])
     return ([sourcerev], [sourcerev]) # nothing outgoing
 
-def getmessage(ui, rev):
-    msg = rev.message
-
-    if msg:
-        try:
-            msg.decode('utf-8')
-            return msg
-
-        except UnicodeDecodeError:
-            # ancient svn failed to enforce utf8 encoding
-            return msg.decode('iso-8859-1').encode('utf-8')
-    else:
-        return ui.config('hgsubversion', 'defaultmessage', '')
-
 def describe_commit(ui, h, b):
     ui.note(' committed to "%s" as %s\n' % ((b or 'default'), node.short(h)))
 
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -261,6 +261,8 @@ def push(repo, dest, force, revs):
 
                 # Don't trust the pre-rebase repo and context.
                 repo = getlocalpeer(ui, {}, meta.path)
+                meta = repo.svnmeta(svn.uuid, svn.subdir)
+                hashes = meta.revmap.hashes()
                 tip_ctx = repo[tip_ctx.node()]
                 for c in tip_ctx.descendants():
                     rebasesrc = c.extra().get('rebase_source')
@@ -289,10 +291,8 @@ def push(repo, dest, force, revs):
 
             # 5. Pull the latest changesets from subversion, which will
             # include the one we just committed (and possibly others).
-            r = pull(repo, dest, force=force)
+            r = pull(repo, dest, force=force, meta=meta)
             assert not r or r == 0
-            meta = repo.svnmeta(svn.uuid, svn.subdir)
-            hashes = meta.revmap.hashes()
 
             # 6. Move our tip to the latest pulled tip
             for c in tip_ctx.descendants():
@@ -379,7 +379,7 @@ def exchangepush(orig, repo, remote, for
     else:
         return orig(repo, remote, force, revs, newbranch, bookmarks=bookmarks)
 
-def pull(repo, source, heads=[], force=False):
+def pull(repo, source, heads=[], force=False, meta=None):
     """pull new revisions from Subversion"""
     assert source.capable('subversion')
     svn_url = source.svnurl
@@ -394,15 +394,16 @@ def pull(repo, source, heads=[], force=F
             repo.ui.note('fetching stupidly...\n')
 
         svn = source.svn
-        meta = repo.svnmeta(svn.uuid, svn.subdir)
+        if meta is None:
+            meta = repo.svnmeta(svn.uuid, svn.subdir)
 
         stopat_rev = util.parse_revnum(svn, checkout)
 
-        layout = layouts.detect.layout_from_config(repo.ui, allow_auto=True)
+        layout = layouts.detect.layout_from_config(meta, allow_auto=True)
         if layout == 'auto':
             layout = layouts.detect.layout_from_subversion(svn,
                                                            (stopat_rev or None),
-                                                           repo.ui)
+                                                           meta)
             repo.ui.note('using %s layout\n' % layout)
 
         branch = repo.ui.config('hgsubversion', 'branch')
@@ -461,7 +462,7 @@ def pull(repo, source, heads=[], force=F
                 converted = False
                 while not converted:
                     try:
-                        msg = util.getmessage(ui, r).strip()
+                        msg = meta.getmessage(r).strip()
                         if msg:
                             msg = [s.strip() for s in msg.splitlines() if s][0]
                         if getattr(ui, 'termwidth', False):