# HG changeset patch
# User Patrick Mezard <pmezard@gmail.com>
# Date 1247967873 18000
# Node ID 0f7095f53ca381c620e99696ca3c93358cc8e44e
# Parent  c82d5a9acecf60f67a5ca4c897da0501bf0023ab
Extend svnrepos with SubversionRepo and SVNMeta

SubversionRepo and SVNMeta are now hidden behind svnremoterepo and
svnlocalrepo. It unifies the way svn credentials are read from the command line
and configuration file, at the cost of import cycle between svnrepo and
wrappers. It is currently not a big deal thanks to demandimport.

diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py
--- a/hgsubversion/svncommands.py
+++ b/hgsubversion/svncommands.py
@@ -7,7 +7,7 @@ from mercurial import util as hgutil
 
 import maps
 import svnwrap
-import svnmeta
+import svnrepo
 import util
 import utility_commands
 import svnexternals
@@ -16,12 +16,6 @@ import svnexternals
 def verify(ui, repo, *args, **opts):
     '''verify current revision against Subversion repository
     '''
-
-    if not args:
-        url = repo.ui.expandpath('default')
-    else:
-        url = args[0]
-
     ctx = repo[opts.get('rev', '.')]
     if 'close' in ctx.extra():
         ui.write('cannot verify closed branch')
@@ -33,9 +27,11 @@ def verify(ui, repo, *args, **opts):
     srev = int(srev.split('@')[1])
     ui.write('verifying %s against r%i\n' % (ctx, srev))
 
-    url = util.normalize_url(url.rstrip('/'))
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(url, user, passwd)
+    
+    url = repo.ui.expandpath('default')
+    if args:
+        url = args[0]
+    svn = svnrepo.svnremoterepo(ui, url).svn
 
     btypes = {'default': 'trunk'}
     branchpath = btypes.get(ctx.branch(), 'branches/%s' % ctx.branch())
@@ -72,11 +68,9 @@ def rebuildmeta(ui, repo, hg_repo_path, 
         dest = args[0]
     elif len(args) > 1:
         raise hgutil.Abort('rebuildmeta takes 1 or no arguments')
-    url = repo.ui.expandpath(dest or 'default-push', dest or 'default')
     uuid = None
-    url = util.normalize_url(url.rstrip('/'))
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(url, user, passwd)
+    url = repo.ui.expandpath(dest or 'default-push', dest or 'default')
+    svn = svnrepo.svnremoterepo(ui, url).svn
     subdir = svn.subdir
     svnmetadir = os.path.join(repo.path, 'svn')
     if not os.path.exists(svnmetadir):
@@ -224,7 +218,7 @@ def update(ui, args, repo, clean=False, 
 
     assert len(args) == 1
     rev = int(args[0])
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
 
     answers = []
     for k, v in meta.revmap.iteritems():
diff --git a/hgsubversion/svnrepo.py b/hgsubversion/svnrepo.py
--- a/hgsubversion/svnrepo.py
+++ b/hgsubversion/svnrepo.py
@@ -21,6 +21,10 @@ import mercurial.repo
 
 import util
 import wrappers
+import svnwrap
+import svnmeta
+
+propertycache = hgutil.propertycache
 
 def generate_repo_class(ui, repo):
     """ This function generates the local repository wrapper. """
@@ -56,20 +60,39 @@ def generate_repo_class(ui, repo):
         def findoutgoing(self, remote, base=None, heads=None, force=False):
             return wrappers.outgoing(repo, remote, heads, force)
 
+        def svnmeta(self, uuid=None, subdir=''):
+            return svnmeta.SVNMeta(self, uuid, subdir)
+
     repo.__class__ = svnlocalrepo
 
 class svnremoterepo(mercurial.repo.repository):
     """ the dumb wrapper for actual Subversion repositories """
 
-    def __init__(self, ui, path):
+    def __init__(self, ui, path=None):
         self.ui = ui
+        if path is None:
+            path = self.ui.config('paths', 'default')
         self.path = path
         self.capabilities = set(['lookup', 'subversion'])
 
-    @property
+    @propertycache
     def svnurl(self):
         return util.normalize_url(self.path)
 
+    @propertycache
+    def svn(self):
+        # DO NOT default the user to hg's getuser(). If you provide
+        # *any* default username to Subversion, it won't use any remembered
+        # username for the desired realm, breaking OS X Keychain support,
+        # GNOME keyring support, and all similar tools.
+        user = self.ui.config('hgsubversion', 'username')
+        passwd = self.ui.config('hgsubversion', 'password')
+        return svnwrap.SubversionRepo(self.svnurl, user, passwd)
+
+    @property
+    def svnuuid(self):
+        return self.svn.uuid
+
     def url(self):
         return self.path
 
diff --git a/hgsubversion/util.py b/hgsubversion/util.py
--- a/hgsubversion/util.py
+++ b/hgsubversion/util.py
@@ -60,15 +60,6 @@ def islocalrepo(url):
     return False
 
 
-def getuserpass(ui):
-    # DO NOT default the user to hg's getuser(). If you provide
-    # *any* default username to Subversion, it won't use any remembered
-    # username for the desired realm, breaking OS X Keychain support,
-    # GNOME keyring support, and all similar tools.
-    return (ui.config('hgsubversion', 'username'),
-            ui.config('hgsubversion', 'password'))
-
-
 def version(ui):
     """Guess the version of hgsubversion.
     """
diff --git a/hgsubversion/utility_commands.py b/hgsubversion/utility_commands.py
--- a/hgsubversion/utility_commands.py
+++ b/hgsubversion/utility_commands.py
@@ -2,8 +2,8 @@ import os
 
 from mercurial import util as hgutil
 
-import svnmeta
 import svnwrap
+import svnrepo
 import util
 
 def genignore(ui, repo, force=False, **opts):
@@ -12,16 +12,15 @@ def genignore(ui, repo, force=False, **o
     ignpath = repo.wjoin('.hgignore')
     if not force and os.path.exists(ignpath):
         raise hgutil.Abort('not overwriting existing .hgignore, try --force?')
-    url = util.normalize_url(repo.ui.config('paths', 'default'))
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(url, user, passwd)
-    meta = svnmeta.SVNMeta(repo, svn.uuid)
+    svn = svnrepo.svnremoterepo(repo.ui).svn
+    meta = repo.svnmeta()
     hashes = meta.revmap.hashes()
     parent = util.parentrev(ui, repo, meta, hashes)
     r, br = hashes[parent.node()]
     branchpath = br and ('branches/%s' % br) or 'trunk'
     ignorelines = ['.hgignore', 'syntax:glob']
-    dirs = [''] + [d[0] for d in svn.list_files(branchpath, r) if d[1] == 'd']
+    dirs = [''] + [d[0] for d in svn.list_files(branchpath, r)
+                   if d[1] == 'd']
     for dir in dirs:
         props = svn.list_props('%s/%s/' % (branchpath, dir), r)
         if 'svn:ignore' not in props:
@@ -35,10 +34,7 @@ def genignore(ui, repo, force=False, **o
 def info(ui, repo, hg_repo_path, **opts):
     """show Subversion details similar to `svn info'
     """
-    url = util.normalize_url(repo.ui.config('paths', 'default'))
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(url, user, passwd)
-    meta = svnmeta.SVNMeta(repo, svn.uuid)
+    meta = repo.svnmeta()
     hashes = meta.revmap.hashes()
     parent = util.parentrev(ui, repo, meta, hashes)
     pn = parent.node()
@@ -54,10 +50,8 @@ def info(ui, repo, hg_repo_path, **opts)
         subdir = subdir.replace('branches/../', '')
     else:
         branchpath = '/branches/%s' % br
-    url = util.normalize_url(repo.ui.config('paths', 'default'))
-    if url[-1] == '/':
-        url = url[:-1]
-    url = '%s%s' % (url, branchpath)
+    remoterepo = svnrepo.svnremoterepo(repo.ui)
+    url = '%s%s' % (remoterepo.svnurl, branchpath)
     author = meta.authors.reverselookup(parent.user())
     # cleverly figure out repo root w/o actually contacting the server
     reporoot = url[:len(url)-len(subdir)]
@@ -86,7 +80,7 @@ def listauthors(ui, args, authors=None, 
     if not len(args):
         ui.status('No repository specified.\n')
         return
-    svn = svnwrap.SubversionRepo(util.normalize_url(args[0]))
+    svn = svnrepo.svnremoterepo(ui, args[0]).svn
     author_set = set()
     for rev in svn.revisions():
         author_set.add(str(rev.author)) # So None becomes 'None'
diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -10,11 +10,11 @@ from mercurial import i18n
 from svn import core
 from svn import delta
 
-import svnmeta
 import replay
 import pushmod
 import stupid as stupidmod
 import svnwrap
+import svnrepo
 import util
 
 pullfuns = {
@@ -34,7 +34,7 @@ def parents(orig, ui, repo, *args, **opt
     """
     if not opts.get('svn', False):
         return orig(ui, repo, *args, **opts)
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
     hashes = meta.revmap.hashes()
     ha = util.parentrev(ui, repo, meta, hashes)
     if ha.node() == node.nullid:
@@ -53,12 +53,10 @@ def incoming(orig, ui, repo, source='def
     if 'subversion' not in other.capabilities:
         return orig(ui, repo, source, **opts)
 
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(other.svnurl, user, passwd)
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
 
     ui.status('incoming changes from %s\n' % other.svnurl)
-    for r in svn.revisions(start=meta.revmap.seen):
+    for r in other.svn.revisions(start=meta.revmap.seen):
         ui.status('\n')
         for label, attr in revmeta:
             l1 = label + ':'
@@ -75,7 +73,7 @@ def outgoing(repo, dest=None, heads=None
 
     # split off #rev; TODO implement --revision/#rev support
     svnurl, revs, checkout = hg.parseurl(dest.svnurl, heads)
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
     parent = repo.parents()[0].node()
     hashes = meta.revmap.hashes()
     return util.outgoing_revisions(repo, hashes, parent)
@@ -86,7 +84,7 @@ def diff(orig, ui, repo, *args, **opts):
     """
     if not opts.get('svn', False) or opts.get('change', None):
         return orig(ui, repo, *args, **opts)
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
     hashes = meta.revmap.hashes()
     if not opts.get('rev', None):
         parent = repo.parents()[0]
@@ -119,9 +117,8 @@ def push(repo, dest, force, revs):
     # split of #rev; TODO: implement --rev/#rev support
     svnurl, revs, checkout = hg.parseurl(svnurl, revs)
     # TODO: do credentials specified in the URL still work?
-    user, passwd = util.getuserpass(ui)
-    svn = svnwrap.SubversionRepo(svnurl, user, passwd)
-    meta = svnmeta.SVNMeta(repo, svn.uuid)
+    svn = svnrepo.svnremoterepo(repo.ui, svnurl).svn
+    meta = repo.svnmeta(svn.uuid)
 
     # Strategy:
     # 1. Find all outgoing commits from this head
@@ -156,7 +153,7 @@ def push(repo, dest, force, revs):
         base_revision = hashes[base_n][0]
         try:
             pushmod.commit(ui, repo, old_ctx, meta, svnurl,
-                           base_revision, user, passwd)
+                           base_revision, svn.username, svn.password)
         except pushmod.NoFilesException:
             ui.warn("Could not push revision %s because it had no changes in svn.\n" %
                      old_ctx)
@@ -194,7 +191,7 @@ def push(repo, dest, force, revs):
                             child = children[0]
                         rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid)))
         # TODO: stop constantly creating the SVNMeta instances.
-        meta = svnmeta.SVNMeta(meta.repo, svn.uuid)
+        meta = repo.svnmeta(svn.uuid)
         hashes = meta.revmap.hashes()
     util.swap_out_encoding(old_encoding)
     return 0
@@ -230,9 +227,8 @@ def pull(repo, source, heads=[], force=F
         repo.ui.note('fetching stupidly...\n')
 
     # TODO: do credentials specified in the URL still work?
-    user, passwd = util.getuserpass(repo.ui)
-    svn = svnwrap.SubversionRepo(svn_url, user, passwd)
-    meta = svnmeta.SVNMeta(repo, svn.uuid, svn.subdir)
+    svn = svnrepo.svnremoterepo(repo.ui, svn_url).svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
 
     start = max(meta.revmap.seen, skipto_rev)
     initializing_repo = meta.revmap.seen <= 0
@@ -320,7 +316,7 @@ def rebase(orig, ui, repo, **opts):
         extra['branch'] = ctx.branch()
     extrafn = opts.get('svnextrafn', extrafn2)
     sourcerev = opts.get('svnsourcerev', repo.parents()[0].node())
-    meta = svnmeta.SVNMeta(repo)
+    meta = repo.svnmeta()
     hashes = meta.revmap.hashes()
     o_r = util.outgoing_revisions(repo, hashes, sourcerev=sourcerev)
     if not o_r: