changeset 347:537de0300510

Remove the 'outgoing' wrapper, and use the Mercurial infrastructure instead.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Fri, 22 May 2009 15:12:31 +0200
parents 4b992ebdecc6
children af1508b7ad54
files hgsubversion/__init__.py hgsubversion/hg_delta_editor.py hgsubversion/svnrepo.py hgsubversion/svnwrap/tests/__init__.py hgsubversion/svnwrap/tests/fixtures/project_root_at_repo_root.svndump hgsubversion/svnwrap/tests/fixtures/project_root_not_repo_root.svndump hgsubversion/svnwrap/tests/test_svnwrap.py hgsubversion/wrappers.py setup.py tests/__init__.py tests/comprehensive/test_stupid_pull.py tests/fixtures/project_root_at_repo_root.svndump tests/fixtures/project_root_not_repo_root.svndump tests/run.py tests/test_diff.py tests/test_rebuildmeta.py tests/test_svnwrap.py tests/test_util.py tests/test_utility_commands.py
diffstat 19 files changed, 885 insertions(+), 877 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/__init__.py
+++ b/hgsubversion/__init__.py
@@ -113,9 +113,6 @@ def uisetup(ui):
     entry = extensions.wrapcommand(commands.table, 'parents',
                                    wrappers.parent)
     entry[1].append(('', 'svn', None, "show parent svn revision instead"))
-    entry = extensions.wrapcommand(commands.table, 'outgoing',
-                                   wrappers.outgoing)
-    entry[1].append(('', 'svn', None, "show revisions outgoing to subversion"))
     entry = extensions.wrapcommand(commands.table, 'diff',
                                    wrappers.diff)
     entry[1].append(('', 'svn', None,
@@ -184,12 +181,12 @@ def reposetup(ui, repo):
     if repo.local():
        svnrepo.generate_repo_class(ui, repo)
 
-_origschemes = hg.schemes.copy()
+
 def _lookup(url):
     if cmdutil.islocalrepo(url):
         return svnrepo
     else:
-        return _origschemes['file'](url)
+        return hg._local(url)
 
 # install scheme handlers
 hg.schemes.update({ 'file': _lookup, 'http': svnrepo, 'https': svnrepo,
--- a/hgsubversion/hg_delta_editor.py
+++ b/hgsubversion/hg_delta_editor.py
@@ -73,7 +73,7 @@ class HgChangeReceiver(delta.Editor):
         except ValueError:
             return 0
 
-    def __init__(self, path=None, repo=None, ui_=None,
+    def __init__(self, repo=None, path=None, ui_=None,
                  subdir='', author_host='',
                  tag_locations=[],
                  authors=None, filemap=None, uuid=None):
@@ -87,7 +87,7 @@ class HgChangeReceiver(delta.Editor):
         if not ui_:
             ui_ = ui.ui()
         self.ui = ui_
-        self.__setup_repo(repo or path, uuid)
+        self.__setup_repo(uuid, repo, path, subdir)
 
         if not author_host:
             author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
@@ -100,6 +100,7 @@ class HgChangeReceiver(delta.Editor):
         self.usebranchnames = self.ui.configbool('hgsubversion',
                                                   'usebranchnames', True)
 
+        # FIXME: test that this hasn't changed! defer & compare?
         self.subdir = subdir
         if self.subdir and self.subdir[0] == '/':
             self.subdir = self.subdir[1:]
@@ -145,19 +146,19 @@ class HgChangeReceiver(delta.Editor):
             date = self.lastdate
         return date
 
-    def __setup_repo(self, arg, uuid):
+    def __setup_repo(self, uuid, repo, path, subdir):
         """Verify the repo is going to work out for us.
 
         This method will fail an assertion if the repo exists but doesn't have
         the Subversion metadata.
         """
-        if isinstance(arg, basestring):
-            self.repo = hg.repository(self.ui, arg,
-                                      create=(not os.path.exists(arg)))
-            self.path = os.path.normpath(os.path.join(arg, '..'))
-        elif arg:
-            self.repo = arg
-            self.path = os.path.normpath(os.path.join(self.repo.path, '..'))
+        if repo:
+            self.repo = repo
+            self.path = os.path.normpath(self.repo.join('..'))
+        elif path:
+            self.repo = hg.repository(self.ui, path,
+                                      create=(not os.path.exists(path)))
+            self.path = os.path.normpath(os.path.join(path, '..'))
         else: #pragma: no cover
             raise TypeError("editor requires either a path or a repository "
                             "specified")
@@ -165,6 +166,7 @@ class HgChangeReceiver(delta.Editor):
         if not os.path.isdir(self.meta_data_dir):
             os.makedirs(self.meta_data_dir)
         self._set_uuid(uuid)
+        # TODO: validate subdir too
 
         if os.path.isfile(self.revmap_file):
             self.revmap = util.parse_revmap(self.revmap_file)
--- a/hgsubversion/svnrepo.py
+++ b/hgsubversion/svnrepo.py
@@ -27,6 +27,8 @@ import wrappers
 def generate_repo_class(ui, repo):
     """ This function generates the local repository wrapper. """
 
+    superclass = repo.__class__
+
     def localsvn(fn):
         """
         Filter for instance methods which only apply to local Subversion
@@ -42,34 +44,39 @@ def generate_repo_class(ui, repo):
         Filter for instance methods which require the first argument
         to be a remote Subversion repository instance.
         """
-        original = getattr(repo.__class__, fn.__name__)
+        original = getattr(repo, fn.__name__)
         def wrapper(self, *args, **opts):
             capable = getattr(args[0], 'capable', lambda x: False)
             if capable('subversion'):
                 return fn(self, *args, **opts)
             else:
-                return original(self, *args, **opts)
+                return original(*args, **opts)
         wrapper.__name__ = fn.__name__ + '_wrapper'
         wrapper.__doc__ = fn.__doc__
         return wrapper
 
-    class svnlocalrepo(repo.__class__):
+    class svnlocalrepo(superclass):
         @remotesvn
         def push(self, remote, force=False, revs=None):
-            wrappers.push(self, dest=remote.svnurl, force=force, revs=None)
+            return wrappers.push(self, remote, force, revs)
+
+        @remotesvn
+        def pull(self, remote, heads=[], force=False):
+            return wrappers.pull(self, remote, heads, force)
+
+        @remotesvn
+        def findoutgoing(self, remote, base=None, heads=None, force=False):
+            return wrappers.outgoing(repo, remote, heads, force)
 
         @remotesvn
-        def pull(self, remote, heads=None, force=False):
-            lock = self.wlock()
-            try:
-                wrappers.pull(self, source=remote.svnurl,
-                              heads=heads, force=force)
-            finally:
-                lock.release()
+        def findcommonincoming(self, remote, base=None, heads=None,
+                               force=False):
+            raise hgutil.Abort('cannot display incoming changes from '
+                               'Subversion repositories, yet')
 
         @localsvn
         def tags(self):
-            tags = super(svnlocalrepo, self).tags()
+            tags = superclass.tags(self)
             hg_editor = hg_delta_editor.HgChangeReceiver(repo=self)
             for tag, source in hg_editor.tags.iteritems():
                 target = hg_editor.get_parent_revision(source[1]+1, source[0])
deleted file mode 100644
--- a/hgsubversion/svnwrap/tests/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import sys
-import os
-sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
deleted file mode 100644
--- a/hgsubversion/svnwrap/tests/fixtures/project_root_at_repo_root.svndump
+++ /dev/null
@@ -1,352 +0,0 @@
-SVN-fs-dump-format-version: 2
-
-UUID: e72213dc-9746-48d2-846a-a19c2c569b0d
-
-Revision-number: 0
-Prop-content-length: 56
-Content-length: 56
-
-K 8
-svn:date
-V 27
-2008-10-09T14:46:06.470091Z
-PROPS-END
-
-Revision-number: 1
-Prop-content-length: 111
-Content-length: 111
-
-K 7
-svn:log
-V 11
-Empty dirs.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:07.147162Z
-PROPS-END
-
-Node-path: branches
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Node-path: tags
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Node-path: trunk
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Revision-number: 2
-Prop-content-length: 114
-Content-length: 114
-
-K 7
-svn:log
-V 14
-Initial Files.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:08.147874Z
-PROPS-END
-
-Node-path: trunk/alpha
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
-Content-length: 25
-
-PROPS-END
-This is alpha.
-
-
-Node-path: trunk/beta
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 14
-Text-content-md5: 952a13b33256f343982d0a8f0e0af277
-Content-length: 24
-
-PROPS-END
-This is beta.
-
-
-Node-path: trunk/delta
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
-Content-length: 25
-
-PROPS-END
-This is delta.
-
-
-Revision-number: 3
-Prop-content-length: 110
-Content-length: 110
-
-K 7
-svn:log
-V 10
-Tag rev 1.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:10.131807Z
-PROPS-END
-
-Node-path: tags/rev1
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: tags/rev1/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: tags/rev1/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: tags/rev1/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Revision-number: 4
-Prop-content-length: 116
-Content-length: 116
-
-K 7
-svn:log
-V 16
-Branch to crazy.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:12.131174Z
-PROPS-END
-
-Node-path: branches/crazy
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: branches/crazy/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: branches/crazy/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: branches/crazy/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Revision-number: 5
-Prop-content-length: 108
-Content-length: 108
-
-K 7
-svn:log
-V 9
-Add gamma
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:13.143196Z
-PROPS-END
-
-Node-path: trunk/gamma
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 255b705986e84fbb13db9c5a832739ae
-Content-length: 25
-
-PROPS-END
-This is gamma.
-
-
-Revision-number: 6
-Prop-content-length: 108
-Content-length: 108
-
-K 7
-svn:log
-V 9
-Add omega
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:14.144244Z
-PROPS-END
-
-Node-path: branches/crazy/omega
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
-Content-length: 25
-
-PROPS-END
-This is omega.
-
-
-Revision-number: 7
-Prop-content-length: 121
-Content-length: 121
-
-K 7
-svn:log
-V 21
-Branch to more_crazy.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T14:46:16.135900Z
-PROPS-END
-
-Node-path: branches/more_crazy
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: branches/more_crazy/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: branches/more_crazy/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: branches/more_crazy/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Node-path: branches/more_crazy/gamma
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 5
-Node-copyfrom-path: trunk/gamma
-Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
-
-
deleted file mode 100644
--- a/hgsubversion/svnwrap/tests/fixtures/project_root_not_repo_root.svndump
+++ /dev/null
@@ -1,361 +0,0 @@
-SVN-fs-dump-format-version: 2
-
-UUID: 1916bd89-caf8-4f3d-96a7-7d5a7968f6ea
-
-Revision-number: 0
-Prop-content-length: 56
-Content-length: 56
-
-K 8
-svn:date
-V 27
-2008-10-09T15:32:50.842663Z
-PROPS-END
-
-Revision-number: 1
-Prop-content-length: 111
-Content-length: 111
-
-K 7
-svn:log
-V 11
-Empty dirs.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:51.151627Z
-PROPS-END
-
-Node-path: dummyproj
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Node-path: dummyproj/branches
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Node-path: dummyproj/tags
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Node-path: dummyproj/trunk
-Node-kind: dir
-Node-action: add
-Prop-content-length: 10
-Content-length: 10
-
-PROPS-END
-
-
-Revision-number: 2
-Prop-content-length: 114
-Content-length: 114
-
-K 7
-svn:log
-V 14
-Initial Files.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:52.149125Z
-PROPS-END
-
-Node-path: dummyproj/trunk/alpha
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
-Content-length: 25
-
-PROPS-END
-This is alpha.
-
-
-Node-path: dummyproj/trunk/beta
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 14
-Text-content-md5: 952a13b33256f343982d0a8f0e0af277
-Content-length: 24
-
-PROPS-END
-This is beta.
-
-
-Node-path: dummyproj/trunk/delta
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
-Content-length: 25
-
-PROPS-END
-This is delta.
-
-
-Revision-number: 3
-Prop-content-length: 110
-Content-length: 110
-
-K 7
-svn:log
-V 10
-Tag rev 1.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:54.131909Z
-PROPS-END
-
-Node-path: dummyproj/tags/rev1
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: dummyproj/trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: dummyproj/tags/rev1/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: dummyproj/tags/rev1/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: dummyproj/tags/rev1/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Revision-number: 4
-Prop-content-length: 116
-Content-length: 116
-
-K 7
-svn:log
-V 16
-Branch to crazy.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:56.132149Z
-PROPS-END
-
-Node-path: dummyproj/branches/crazy
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: dummyproj/trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: dummyproj/branches/crazy/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: dummyproj/branches/crazy/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: dummyproj/branches/crazy/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Revision-number: 5
-Prop-content-length: 108
-Content-length: 108
-
-K 7
-svn:log
-V 9
-Add gamma
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:57.142842Z
-PROPS-END
-
-Node-path: dummyproj/trunk/gamma
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 255b705986e84fbb13db9c5a832739ae
-Content-length: 25
-
-PROPS-END
-This is gamma.
-
-
-Revision-number: 6
-Prop-content-length: 108
-Content-length: 108
-
-K 7
-svn:log
-V 9
-Add omega
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:32:58.146324Z
-PROPS-END
-
-Node-path: dummyproj/branches/crazy/omega
-Node-kind: file
-Node-action: add
-Prop-content-length: 10
-Text-content-length: 15
-Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
-Content-length: 25
-
-PROPS-END
-This is omega.
-
-
-Revision-number: 7
-Prop-content-length: 121
-Content-length: 121
-
-K 7
-svn:log
-V 21
-Branch to more_crazy.
-K 10
-svn:author
-V 5
-durin
-K 8
-svn:date
-V 27
-2008-10-09T15:33:00.138992Z
-PROPS-END
-
-Node-path: dummyproj/branches/more_crazy
-Node-kind: dir
-Node-action: add
-Node-copyfrom-rev: 1
-Node-copyfrom-path: dummyproj/trunk
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
-
-
-Node-path: dummyproj/branches/more_crazy/alpha
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/alpha
-Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
-
-
-Node-path: dummyproj/branches/more_crazy/beta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/beta
-Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
-
-
-Node-path: dummyproj/branches/more_crazy/delta
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 2
-Node-copyfrom-path: dummyproj/trunk/delta
-Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
-
-
-Node-path: dummyproj/branches/more_crazy/gamma
-Node-kind: file
-Node-action: add
-Node-copyfrom-rev: 5
-Node-copyfrom-path: dummyproj/trunk/gamma
-Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
-
-
deleted file mode 100644
--- a/hgsubversion/svnwrap/tests/test_svnwrap.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import os
-import subprocess
-import shutil
-import tempfile
-import unittest
-
-from nose import tools
-
-import svnwrap
-
-class TestBasicRepoLayout(unittest.TestCase):
-    def setUp(self):
-        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
-        self.repo_path = '%s/testrepo' % self.tmpdir
-        os.spawnvp(os.P_WAIT, 'svnadmin', ['svnadmin', 'create',
-                                           self.repo_path,])
-        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
-                                'project_root_at_repo_root.svndump'))
-        proc = subprocess.call(['svnadmin', 'load', self.repo_path,],
-                                stdin=inp, close_fds=True,
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT)
-        assert proc == 0
-        self.repo = svnwrap.SubversionRepo('file://%s' % self.repo_path)
-
-    def tearDown(self):
-        shutil.rmtree(self.tmpdir)
-
-
-    def test_num_revs(self):
-        revs = list(self.repo.revisions())
-        tools.eq_(len(revs), 7)
-        r = revs[1]
-        tools.eq_(r.revnum, 2)
-        tools.eq_(sorted(r.paths.keys()),
-                  ['trunk/alpha', 'trunk/beta', 'trunk/delta'])
-        for r in revs:
-            for p in r.paths:
-                # make sure these paths are always non-absolute for sanity
-                if p:
-                    assert p[0] != '/'
-        revs = list(self.repo.revisions(start=3))
-        tools.eq_(len(revs), 4)
-
-
-    def test_branches(self):
-        tools.eq_(self.repo.branches.keys(), ['crazy', 'more_crazy'])
-        tools.eq_(self.repo.branches['crazy'], ('trunk', 2, 4))
-        tools.eq_(self.repo.branches['more_crazy'], ('trunk', 5, 7))
-
-
-    def test_tags(self):
-        tags = self.repo.tags
-        tools.eq_(tags.keys(), ['rev1'])
-        tools.eq_(tags['rev1'], ('trunk', 2))
-
-class TestRootAsSubdirOfRepo(TestBasicRepoLayout):
-    def setUp(self):
-        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
-        self.repo_path = '%s/testrepo' % self.tmpdir
-        os.spawnvp(os.P_WAIT, 'svnadmin', ['svnadmin', 'create',
-                                           self.repo_path,])
-        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
-                                'project_root_not_repo_root.svndump'))
-        ret = subprocess.call(['svnadmin', 'load', self.repo_path,],
-                              stdin=inp, close_fds=True,
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT)
-        assert ret == 0
-        self.repo = svnwrap.SubversionRepo('file://%s/dummyproj' %
-                                           self.repo_path)
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -35,26 +35,18 @@ def parent(orig, ui, repo, *args, **opts
     return 0
 
 
-def outgoing(orig, ui, repo, dest=None, *args, **opts):
+def outgoing(repo, dest=None, heads=None, force=False):
     """show changesets not found in the Subversion repository
     """
-    svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default')
-    if not hg.repository(ui, svnurl).capable('subversion'):
-        return orig(ui, repo, dest, *args, **opts)
+    assert dest.capable('subversion')
 
     # split off #rev; TODO implement --revision/#rev support
-    svnurl, revs, checkout = hg.parseurl(svnurl, opts.get('rev'))
+    svnurl, revs, checkout = hg.parseurl(dest.svnurl, heads)
     hge = hg_delta_editor.HgChangeReceiver(repo=repo)
     svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
                                  hge.revmap.iterkeys()))
-    o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes,
-                                  repo.parents()[0].node())
-    if not (o_r and len(o_r)):
-        ui.status('no changes found\n')
-        return 0
-    displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False)
-    for node in reversed(o_r):
-        displayer.show(repo[node])
+    return util.outgoing_revisions(repo.ui, repo, hge, svn_commit_hashes,
+                                   repo.parents()[0].node())
 
 
 def diff(orig, ui, repo, *args, **opts):
@@ -87,12 +79,12 @@ def diff(orig, ui, repo, *args, **opts):
                                                   }))
     ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev))
 
-def push(repo, dest="default", force=False, revs=None):
+def push(repo, dest, force, revs):
     """push revisions starting at a specified head back to Subversion.
     """
     assert not revs, 'designated revisions for push remains unimplemented.'
     ui = repo.ui
-    svnurl = util.normalize_url(repo.ui.expandpath(dest))
+    svnurl = util.normalize_url(repo.ui.expandpath(dest.svnurl))
     old_encoding = util.swap_out_encoding()
     # split of #rev; TODO: implement --rev/#rev support
     svnurl, revs, checkout = hg.parseurl(svnurl, revs)
@@ -157,7 +149,7 @@ def push(repo, dest="default", force=Fal
             return 1
         # 3. Fetch revisions from svn
         # TODO: this probably should pass in the source explicitly - rev too?
-        r = pull(repo, source=dest, force=force)
+        r = repo.pull(dest, force=force)
         assert not r or r == 0
         # 4. Find the new head of the target branch
         oldtipctx = repo[oldtip]
@@ -194,13 +186,10 @@ def push(repo, dest="default", force=Fal
     return 0
 
 
-def pull(repo, source="default", heads=None, force=False):
-    """pull new revisions from Subversion
-
-    Also takes svn, svn_stupid, and create_new_dest kwargs.
-    """
-    url = repo.ui.expandpath(source)
-    svn_url = util.normalize_url(url)
+def pull(repo, source, heads=[], force=False):
+    """pull new revisions from Subversion"""
+    assert source.capable('subversion')
+    svn_url = source.svnurl
 
     # Split off #rev
     svn_url, heads, checkout = hg.parseurl(svn_url, heads)
@@ -217,12 +206,14 @@ def pull(repo, source="default", heads=N
     have_replay = not repo.ui.configbool('hgsubversion', 'stupid')
     if have_replay and not callable(
         delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover
-        ui.status('You are using old Subversion SWIG bindings. Replay will not'
-                  ' work until you upgrade to 1.5.0 or newer. Falling back to'
-                  ' a slower method that may be buggier. Please upgrade, or'
-                  ' contribute a patch to use the ctypes bindings instead'
-                  ' of SWIG.\n')
+        repo.ui.status('You are using old Subversion SWIG bindings. Replay '
+                       'will not work until you upgrade to 1.5.0 or newer. '
+                       'Falling back to a slower method that may be buggier. '
+                       'Please upgrade, or contribute a patch to use the '
+                       'ctypes bindings instead of SWIG.\n')
         have_replay = False
+    elif not have_replay:
+        repo.ui.note('fetching stupidly...\n')
 
     # TODO: do credentials specified in the URL still work?
     user = repo.ui.config('hgsubversion', 'username')
@@ -242,15 +233,12 @@ def pull(repo, source="default", heads=N
     revisions = 0
 
     try:
-      # start converting revisions
-      for r in svn.revisions(start=start, stop=stopat_rev):
-        valid = True
-        hg_editor.update_branch_tag_map_for_rev(r)
-        for p in r.paths:
-            if hg_editor._is_path_valid(p):
-                valid = True
-                break
-        if valid:
+        # start converting revisions
+        for r in svn.revisions(start=start, stop=stopat_rev):
+            if (r.author is None and 
+                r.message == 'This is an empty revision for padding.'):
+                continue
+            hg_editor.update_branch_tag_map_for_rev(r)
             # got a 502? Try more than once!
             tries = 0
             converted = False
@@ -279,12 +267,12 @@ def pull(repo, source="default", heads=N
             revisions += 1
     except KeyboardInterrupt:
         pass
-
-    util.swap_out_encoding(old_encoding)
+    finally:
+        util.swap_out_encoding(old_encoding)
 
     if revisions == 0:
         ui.status(i18n._("no changes found\n"))
-        return
+        return 0
     else:
         ui.status("pulled %d revisions\n" % revisions)
 
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ setup(
     long_description = open(os.path.join(os.path.dirname(__file__),
                                          'README')).read(),
     keywords = 'mercurial',
-    packages = ['hgsubversion', 'hgsubversion.svnwrap'],
+    packages = ('hgsubversion', 'hgsubversion.svnwrap'),
     platforms = 'any',
     classifiers = [
         'License :: OSI Approved :: GNU General Public License (GPL)',
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,3 +1,3 @@
-import sys
-import os
-sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+import imp, os, sys
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
--- a/tests/comprehensive/test_stupid_pull.py
+++ b/tests/comprehensive/test_stupid_pull.py
@@ -18,8 +18,8 @@ def _do_case(self, name):
     checkout_path = self.repo_path
     if subdir:
         checkout_path += '/' + subdir
-    wrappers.clone(None, ui.ui(), source=test_util.fileurl(checkout_path),
-                     dest=wc2_path, stupid=True, noupdate=True)
+    u.setconfig('hgsubversion', 'stupid', '1')
+    hg.clone(u, test_util.fileurl(checkout_path), wc2_path, update=False)
     self.repo2 = hg.repository(ui.ui(), wc2_path)
     self.assertEqual(self.repo.branchtags(), self.repo2.branchtags())
     self.assertEqual(pickle.load(open(os.path.join(self.wc_path, '.hg', 'svn', 'tag_info'))),
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/project_root_at_repo_root.svndump
@@ -0,0 +1,352 @@
+SVN-fs-dump-format-version: 2
+
+UUID: e72213dc-9746-48d2-846a-a19c2c569b0d
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-09T14:46:06.470091Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:07.147162Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Initial Files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:08.147874Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
+Content-length: 25
+
+PROPS-END
+This is alpha.
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 14
+Text-content-md5: 952a13b33256f343982d0a8f0e0af277
+Content-length: 24
+
+PROPS-END
+This is beta.
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
+Content-length: 25
+
+PROPS-END
+This is delta.
+
+
+Revision-number: 3
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Tag rev 1.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:10.131807Z
+PROPS-END
+
+Node-path: tags/rev1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: tags/rev1/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: tags/rev1/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: tags/rev1/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Branch to crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:12.131174Z
+PROPS-END
+
+Node-path: branches/crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: branches/crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: branches/crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: branches/crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 5
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add gamma
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:13.143196Z
+PROPS-END
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 255b705986e84fbb13db9c5a832739ae
+Content-length: 25
+
+PROPS-END
+This is gamma.
+
+
+Revision-number: 6
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add omega
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:14.144244Z
+PROPS-END
+
+Node-path: branches/crazy/omega
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
+Content-length: 25
+
+PROPS-END
+This is omega.
+
+
+Revision-number: 7
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 21
+Branch to more_crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:16.135900Z
+PROPS-END
+
+Node-path: branches/more_crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: branches/more_crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: branches/more_crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: branches/more_crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Node-path: branches/more_crazy/gamma
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk/gamma
+Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
+
+
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/project_root_not_repo_root.svndump
@@ -0,0 +1,361 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1916bd89-caf8-4f3d-96a7-7d5a7968f6ea
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-09T15:32:50.842663Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:51.151627Z
+PROPS-END
+
+Node-path: dummyproj
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Initial Files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:52.149125Z
+PROPS-END
+
+Node-path: dummyproj/trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
+Content-length: 25
+
+PROPS-END
+This is alpha.
+
+
+Node-path: dummyproj/trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 14
+Text-content-md5: 952a13b33256f343982d0a8f0e0af277
+Content-length: 24
+
+PROPS-END
+This is beta.
+
+
+Node-path: dummyproj/trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
+Content-length: 25
+
+PROPS-END
+This is delta.
+
+
+Revision-number: 3
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Tag rev 1.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:54.131909Z
+PROPS-END
+
+Node-path: dummyproj/tags/rev1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/tags/rev1/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/tags/rev1/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/tags/rev1/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Branch to crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:56.132149Z
+PROPS-END
+
+Node-path: dummyproj/branches/crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/branches/crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/branches/crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/branches/crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 5
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add gamma
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:57.142842Z
+PROPS-END
+
+Node-path: dummyproj/trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 255b705986e84fbb13db9c5a832739ae
+Content-length: 25
+
+PROPS-END
+This is gamma.
+
+
+Revision-number: 6
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add omega
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:58.146324Z
+PROPS-END
+
+Node-path: dummyproj/branches/crazy/omega
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
+Content-length: 25
+
+PROPS-END
+This is omega.
+
+
+Revision-number: 7
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 21
+Branch to more_crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:33:00.138992Z
+PROPS-END
+
+Node-path: dummyproj/branches/more_crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/branches/more_crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/branches/more_crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/branches/more_crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Node-path: dummyproj/branches/more_crazy/gamma
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: dummyproj/trunk/gamma
+Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
+
+
--- a/tests/run.py
+++ b/tests/run.py
@@ -2,8 +2,7 @@ import os
 import sys
 import unittest
 
-sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-
+import test_util
 import test_binaryfiles
 import test_diff
 import test_externals
@@ -21,6 +20,7 @@ import test_push_renames
 import test_push_dirs
 import test_push_eol
 import test_rebuildmeta
+import test_svnwrap
 import test_tags
 import test_utility_commands
 import test_urls
@@ -43,6 +43,7 @@ def suite():
                                test_push_dirs.suite(),
                                test_push_eol.suite(),
                                test_rebuildmeta.suite(),
+                               test_svnwrap.suite(),
                                test_tags.suite(),
                                test_utility_commands.suite(),
                                test_urls.suite(),
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -1,4 +1,5 @@
 import unittest
+import tests
 
 from mercurial import ui
 
--- a/tests/test_rebuildmeta.py
+++ b/tests/test_rebuildmeta.py
@@ -2,14 +2,14 @@ import os
 import pickle
 import unittest
 
+import test_util
+
 from mercurial import hg
 from mercurial import ui
 
 from hgsubversion import svncommands
 from hgsubversion import hg_delta_editor
 
-import test_util
-
 def _do_case(self, name, stupid):
     subdir = test_util.subdir.get(name, '')
     self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid)
@@ -69,6 +69,9 @@ def buildmethod(case, name, stupid):
 attrs = {'_do_case': _do_case,
          }
 for case in [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')]:
+    # this fixture results in an empty repository, don't use it
+    if case == 'project_root_not_repo_root.svndump':
+        continue
     name = 'test_' + case[:-len('.svndump')]
     attrs[name] = buildmethod(case, name, False)
     name += '_stupid'
new file mode 100644
--- /dev/null
+++ b/tests/test_svnwrap.py
@@ -0,0 +1,76 @@
+import imp
+import os
+import subprocess
+import shutil
+import tempfile
+import unittest
+
+import test_util
+
+from hgsubversion import svnwrap
+
+class TestBasicRepoLayout(unittest.TestCase):
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
+        self.repo_path = '%s/testrepo' % self.tmpdir
+        os.spawnvp(os.P_WAIT, 'svnadmin', ['svnadmin', 'create',
+                                           self.repo_path,])
+        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
+                                'project_root_at_repo_root.svndump'))
+        proc = subprocess.call(['svnadmin', 'load', self.repo_path,],
+                                stdin=inp, close_fds=True,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        assert proc == 0
+        self.repo = svnwrap.SubversionRepo('file://%s' % self.repo_path)
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+
+    def test_num_revs(self):
+        revs = list(self.repo.revisions())
+        self.assertEqual(len(revs), 7)
+        r = revs[1]
+        self.assertEqual(r.revnum, 2)
+        self.assertEqual(sorted(r.paths.keys()),
+                  ['trunk/alpha', 'trunk/beta', 'trunk/delta'])
+        for r in revs:
+            for p in r.paths:
+                # make sure these paths are always non-absolute for sanity
+                if p:
+                    assert p[0] != '/'
+        revs = list(self.repo.revisions(start=3))
+        self.assertEqual(len(revs), 4)
+
+
+    def test_branches(self):
+        self.assertEqual(self.repo.branches.keys(), ['crazy', 'more_crazy'])
+        self.assertEqual(self.repo.branches['crazy'], ('trunk', 2, 4))
+        self.assertEqual(self.repo.branches['more_crazy'], ('trunk', 5, 7))
+
+
+    def test_tags(self):
+        tags = self.repo.tags
+        self.assertEqual(tags.keys(), ['rev1'])
+        self.assertEqual(tags['rev1'], ('trunk', 2))
+
+class TestRootAsSubdirOfRepo(TestBasicRepoLayout):
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
+        self.repo_path = '%s/testrepo' % self.tmpdir
+        os.spawnvp(os.P_WAIT, 'svnadmin', ['svnadmin', 'create',
+                                           self.repo_path,])
+        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
+                                'project_root_not_repo_root.svndump'))
+        ret = subprocess.call(['svnadmin', 'load', self.repo_path,],
+                              stdin=inp, close_fds=True,
+                              stdout=subprocess.PIPE,
+                              stderr=subprocess.STDOUT)
+        assert ret == 0
+        self.repo = svnwrap.SubversionRepo('file://%s/dummyproj' %
+                                           self.repo_path)
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
+           unittest.TestLoader().loadTestsFromTestCase(TestRootAsSubdirOfRepo)]
+    return unittest.TestSuite(all)
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,4 +1,5 @@
 import errno
+import imp
 import os
 import subprocess
 import shutil
@@ -8,6 +9,8 @@ import tempfile
 import unittest
 import urllib
 
+import __init__
+
 from mercurial import context
 from mercurial import commands
 from mercurial import hg
--- a/tests/test_utility_commands.py
+++ b/tests/test_utility_commands.py
@@ -7,6 +7,7 @@ from mercurial import hg
 from mercurial import revlog
 from mercurial import context
 from mercurial import node
+from mercurial import commands
 
 from hgsubversion import util
 from hgsubversion import utility_commands
@@ -111,7 +112,6 @@ class UtilityTests(test_util.TestBase):
     def test_outgoing_output(self):
         self._load_fixture_and_fetch('two_heads.svndump')
         u = ui.ui()
-        u.pushbuffer()
         parents = (self.repo['the_branch'].node(), revlog.nullid, )
         def filectxfn(repo, memctx, path):
             return context.memfilectx(path=path,
@@ -129,22 +129,26 @@ class UtilityTests(test_util.TestBase):
                              {'branch': 'localbranch', })
         new = self.repo.commitctx(ctx)
         hg.update(self.repo, new)
-        wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
+        u.pushbuffer()
+        commands.outgoing(u, self.repo, self.repourl)
         actual = u.popbuffer()
-        self.assert_(node.hex(self.repo['localbranch'].node())[:8] in actual)
-        self.assertEqual(actual, ('changeset:   5:6de15430fa20\n'
-                                               'branch:      localbranch\n'
-                                               'tag:         tip\n'
-                                               'parent:      3:4e256962fc5d\n'
-                                               'user:        testy\n'
-                                               'date:        Sun Dec 21 16:32:00 2008 -0500\n'
-                                               'summary:     automated test\n'
-                                               '\n'))
+        u.write(actual)
+        self.assertTrue(node.hex(self.repo['localbranch'].node())[:8] in actual)
+        actual = actual.splitlines()
+        self.assertEqual(actual[0], 'comparing with ' + self.repourl)
+        self.assertEqual(actual[1], 'changeset:   5:6de15430fa20')
+        self.assertEqual(actual[2], 'branch:      localbranch')
+        self.assertEqual(actual[3], 'tag:         tip')
+        self.assertEqual(actual[4], 'parent:      3:4e256962fc5d')
+        self.assertEqual(actual[5], 'user:        testy')
+        self.assertEqual(actual[6], 'date:        Sun Dec 21 16:32:00 2008 -0500')
+        self.assertEqual(actual[7], 'summary:     automated test')
         hg.update(self.repo, 'default')
         u.pushbuffer()
-        wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
+        commands.outgoing(u, self.repo, self.repourl)
         actual = u.popbuffer()
-        self.assertEqual(actual, 'no changes found\n')
+        u.write(actual)
+        self.assertEqual(actual.splitlines()[1], 'no changes found')
 
     def test_rebase(self):
         self._load_fixture_and_fetch('two_revs.svndump')