changeset 1280:c791efb7082a

Merge with stable.
author Augie Fackler <raf@durin42.com>
date Wed, 10 Dec 2014 22:47:18 -0500
parents 2ae577a4cd56 (diff) b5520673f6f2 (current diff)
children e74fad41077b
files hgsubversion/wrappers.py
diffstat 10 files changed, 129 insertions(+), 168 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(
--- 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:
--- 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/standard.py
+++ b/hgsubversion/layouts/standard.py
@@ -5,52 +5,60 @@ 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)
+
+    @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)
 
@@ -69,9 +77,9 @@ class StandardLayout(base.BaseLayout):
             self._tag_locations = util.load(tag_locations_file)
 
             if not self._tag_locations:
-                self._tag_locations = self.ui.configlist('hgsubversion',
-                                                        'tagpaths',
-                                                        ['tags'])
+                self._tag_locations = self.meta.ui.configlist('hgsubversion',
+                                                              'tagpaths',
+                                                              ['tags'])
             util.dump(self._tag_locations, tag_locations_file)
 
             # ensure nested paths are handled properly
@@ -109,22 +117,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/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()
--- 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')
--- a/hgsubversion/svnmeta.py
+++ b/hgsubversion/svnmeta.py
@@ -22,44 +22,46 @@ 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)
 
+        # 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 +69,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 +100,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 +119,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 +132,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
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -398,11 +398,11 @@ def pull(repo, source, heads=[], force=F
 
         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')