changeset 1255:139a44a63090 stable 1.7

Merge default into stable for a release.
author Augie Fackler <raf@durin42.com>
date Wed, 05 Nov 2014 12:48:59 -0500
parents 807c443928d4 (current diff) d07ccad28b1a (diff)
children ee4d0f91eaf9
files
diffstat 36 files changed, 2920 insertions(+), 692 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags
+++ b/.hgtags
@@ -9,6 +9,7 @@ 0cbf9fd89672e73165e1bb4db1ec8f7f65b95c94
 07234759a3f750029ccaa001837d42fa12dd33ee 1.4
 77b22e5b4ea6c248e079afd0f1e544cb5690ce20 1.5
 d0f3a5c2cb56ce65d9ef1c611c8bfbebdc3bef34 1.5.1
+7d47a0f731354505ed9ae8d60d2a6996e8c3294f 1.6
 8caf1226adecb322e90ddb3817c604fa2fe8a66d 1.6.1
 36f6d51b4edc31f1f9ce2d0d02965a85dd26a455 1.6.2
 46523cdfd3b0cee0bf1366ab587686bb65211747 1.6.3
--- a/hgsubversion/__init__.py
+++ b/hgsubversion/__init__.py
@@ -20,9 +20,16 @@ import sys
 import traceback
 
 from mercurial import commands
+try:
+    from mercurial import exchange
+    exchange.push  # existed in first iteration of this file
+except ImportError:
+    # We only *use* the exchange module in hg 3.2+, so this is safe
+    pass
 from mercurial import extensions
 from mercurial import help
 from mercurial import hg
+from mercurial import localrepo
 from mercurial import util as hgutil
 from mercurial import demandimport
 demandimport.ignore.extend([
@@ -33,27 +40,9 @@ demandimport.ignore.extend([
     'svn.ra',
     ])
 
-try:
-    from mercurial import templatekw
-    # force demandimport to load templatekw
-    templatekw.keywords
-except ImportError:
-    templatekw = None
-
-try:
-    from mercurial import revset
-    # force demandimport to load revset
-    revset.methods
-except ImportError:
-    revset = None
-
-try:
-    from mercurial import subrepo
-    # require svnsubrepo and hg >= 1.7.1
-    subrepo.svnsubrepo
-    hgutil.checknlink
-except (ImportError, AttributeError), e:
-    subrepo = None
+from mercurial import templatekw
+from mercurial import revset
+from mercurial import subrepo
 
 import svncommands
 import util
@@ -124,9 +113,8 @@ except AttributeError:
 except ImportError:
     pass
 
-def extsetup():
+def extsetup(ui):
     """insert command wrappers for a bunch of commands"""
-    # add the ui argument to this function once we drop support for 1.3
 
     docvals = {'extension': 'hgsubversion'}
     for cmd, (generic, target, fixdoc, ppopts, opts) in wrapcmds.iteritems():
@@ -155,6 +143,13 @@ def extsetup():
     except:
         pass
 
+    if not hgutil.safehasattr(localrepo.localrepository, 'push'):
+        # Mercurial >= 3.2
+        extensions.wrapfunction(exchange, 'push', wrappers.exchangepush)
+    if not hgutil.safehasattr(localrepo.localrepository, 'pull'):
+        # Mercurial >= 3.2
+        extensions.wrapfunction(exchange, 'pull', wrappers.exchangepull)
+
     helpdir = os.path.join(os.path.dirname(__file__), 'help')
 
     entries = (
@@ -163,20 +158,13 @@ def extsetup():
          lambda: open(os.path.join(helpdir, 'subversion.rst')).read()),
     )
 
-    # in 1.6 and earler the help table is a tuple
-    if getattr(help.helptable, 'extend', None):
-        help.helptable.extend(entries)
-    else:
-        help.helptable = help.helptable + entries
+    help.helptable.extend(entries)
 
-    if templatekw:
-        templatekw.keywords.update(util.templatekeywords)
+    templatekw.keywords.update(util.templatekeywords)
 
-    if revset:
-        revset.symbols.update(util.revsets)
+    revset.symbols.update(util.revsets)
 
-    if subrepo:
-        subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
+    subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
 
 def reposetup(ui, repo):
     if repo.local():
@@ -184,7 +172,7 @@ def reposetup(ui, repo):
         for tunnel in ui.configlist('hgsubversion', 'tunnels'):
             hg.schemes['svn+' + tunnel] = svnrepo
 
-    if revset and ui.configbool('hgsubversion', 'nativerevs'):
+    if ui.configbool('hgsubversion', 'nativerevs'):
         extensions.wrapfunction(revset, 'stringset', util.revset_stringset)
 
 _old_local = hg.schemes['file']
--- a/hgsubversion/compathacks.py
+++ b/hgsubversion/compathacks.py
@@ -1,4 +1,7 @@
-"""Functions to work around API changes inside Mercurial."""
+"""Functions to work around API changes."""
+
+import errno
+import sys
 
 def branchset(repo):
     """Return the set of branches present in a repo.
@@ -25,3 +28,42 @@ def makememfilectx(repo, path, data, isl
         return context.memfilectx(repo, path, data, islink, isexec, copied)
     except TypeError:
         return context.memfilectx(path, data, islink, isexec, copied)
+
+def filectxfn_deleted(memctx, path):
+    """
+    Return None or raise an IOError as necessary if path is deleted.
+
+    Call as:
+
+    if path_missing:
+        return compathacks.filectxfn_deleted(memctx, path)
+
+    Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards,
+    for deleted files, filectxfn should return None rather than returning
+    IOError.
+    """
+    if getattr(memctx, '_returnnoneformissingfiles', False):
+        return None
+    raise IOError(errno.ENOENT, '%s is deleted' % path)
+
+def filectxfn_deleted_reraise(memctx):
+    """
+    Return None or reraise exc as necessary.
+
+    Call as:
+
+    try:
+        # code that raises IOError if the path is missing
+    except IOError:
+        return compathacks.filectxfn_deleted_reraise(memctx)
+
+    Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards,
+    for deleted files, filectxfn should return None rather than returning
+    IOError.
+    """
+    exc_info = sys.exc_info()
+    if (exc_info[1].errno == errno.ENOENT and
+        getattr(memctx, '_returnnoneformissingfiles', False)):
+        return None
+    # preserve traceback info
+    raise exc_info[0], exc_info[1], exc_info[2]
--- a/hgsubversion/editor.py
+++ b/hgsubversion/editor.py
@@ -31,6 +31,13 @@ class FileStore(object):
         if fname in self._popped:
             raise EditingError('trying to set a popped file %s' % fname)
 
+        if fname in self._data:
+            self._size -= len(self._data[fname])
+            del self._data[fname]
+
+        if fname in self._files:
+            del self._files[fname]
+
         if self._maxsize < 0 or (len(data) + self._size) <= self._maxsize:
             self._data[fname] = data
             self._size += len(data)
@@ -239,6 +246,13 @@ class HgEditor(svnwrap.Editor):
         else:
             # Resolve missing directories content immediately so the
             # missing files maybe processed by delete actions.
+            # we remove the missing directory entries to deal with the case
+            # where a directory is replaced from e.g. a closed branch
+            # this will show up as a delete and then a copy
+            # we process deletes after missing, so we can handle a directory
+            # copy plus delete of file in that directory.  This means that we
+            # need to be sure that only things whose final disposition is
+            # deletion remain in self._deleted at the end of the editing process.
             rev = self.current.rev.revnum
             path = path + '/'
             parentdir = path[len(root):]
@@ -248,6 +262,7 @@ class HgEditor(svnwrap.Editor):
                 f = parentdir + f
                 if not self.meta.is_path_valid(f, False):
                     continue
+                self._deleted.discard(f)
                 self._missing.add(f)
 
     @svnwrap.ieditor
@@ -365,7 +380,10 @@ class HgEditor(svnwrap.Editor):
 
         fctx = ctx.filectx(from_file)
         flags = fctx.flags()
-        self.current.set(path, fctx.data(), 'x' in flags, 'l' in flags)
+        base = fctx.data()
+        if 'l' in flags:
+            base = 'link ' + base
+        self.current.set(path, base, 'x' in flags, 'l' in flags)
         copypath = None
         if from_branch == branch:
             parentid = self.meta.get_parent_revision(
@@ -374,7 +392,7 @@ class HgEditor(svnwrap.Editor):
                 parentctx = self._getctx(parentid)
                 if util.issamefile(parentctx, ctx, from_file):
                     copypath = from_file
-        return self._openfile(path, fctx.data(), 'x' in flags, 'l' in flags,
+        return self._openfile(path, base, 'x' in flags, 'l' in flags,
                 copypath, create=True)
 
     @svnwrap.ieditor
@@ -431,7 +449,7 @@ class HgEditor(svnwrap.Editor):
             source_rev = copyfrom_revision
             frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
         new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
-        if new_hash == node.nullid:
+        if frompath is None or new_hash == node.nullid:
             self.addmissing(path, isdir=True)
             return baton
         fromctx = self._getctx(new_hash)
@@ -636,7 +654,7 @@ class HgEditor(svnwrap.Editor):
                     svn.init_ra_and_client()
                 i += 1
                 data, mode = svn.get_file(f, rev)
-                self.current.set(f, data, 'x' in mode, 'l' in mode)
+                self.current.set(root + f, data, 'x' in mode, 'l' in mode)
             if not self.ui.debugflag:
                 self.ui.note('\n')
 
--- a/hgsubversion/help/subversion.rst
+++ b/hgsubversion/help/subversion.rst
@@ -45,8 +45,8 @@ issue ``hg clone http://python-nose.goog
 works with any directory with a Subversion repository, and is known as a single
 directory clone. Normally, converted changesets will be marked as belonging to
 the ``default`` branch, but this can be changed by using the ``-b/--branch``
-option when using Mercurial 1.5 or later. To force single directory clone, use
-hgsubversion.layout option (see below for detailed help) ::
+option. To force single directory clone, use hgsubversion.layout option (see
+below for detailed help) ::
 
  $ hg clone --layout single svn+http://python-nose.googlecode.com/svn nose-hg
 
@@ -85,8 +85,6 @@ An example::
 
   $ hg log --template='{rev}:{node|short} {author|user}\nsvn: {svnrev}\n'
 
-The template keywords are available when using Mercurial 1.5 or later.
-
 For finding changesets from Subversion, hgsubversion extends revsets
 to provide two new selectors:
 
@@ -100,9 +98,7 @@ For example::
   $ hg log -r 'fromsvn()'
   $ hg log -r 'svnrev(500)'
 
-Revsets are available when using Mercurial 1.6 or later and are
-accepted by several Mercurial commands for specifying revisions. See
-``hg help revsets`` for details.
+See ``hg help revsets`` for details.
 
 Support for externals
 ---------------------
@@ -146,7 +142,7 @@ related Subversion repository.
 Alternatively, one can use the ``hgsubversion.externals`` in hgrc to
 specify ``subrepos`` as the externals mode. In this mode, ``.hgsub``
 and ``.hgsubstate`` files will be used instead of
-``.hgsvnexternals``. This feature requires Mercurial 1.7.1 or later.
+``.hgsvnexternals``.
 
 
 Using Subrepositories
@@ -183,8 +179,6 @@ with the revision identifier replaced wi
 
 This mode has the following limitations:
 
-* Require Mercurial >= 1.7.1 to work correctly on all platforms.
-
 * "hgsubversion" subrepositories require hgsubversion extension to be
   available. To operate transparently on ``svn:externals`` we have to
   stay as close as possible to their original property
@@ -365,10 +359,10 @@ settings:
     when necessary.
 
   ``hgsubversion.externals``
-    Set to ``subrepos`` to switch to subrepos-based externals support
-    (requires Mercurial 1.7.1 or later.) Default is ``svnexternals``,
-    which uses a custom hgsubversion-specific format and works on
-    older versions of Mercurial. Use ``ignore`` to avoid converting externals.
+    Set to ``subrepos`` to switch to subrepos-based externals support. Default
+    is ``svnexternals``, which uses a custom hgsubversion-specific format and
+    works on older versions of Mercurial. Use ``ignore`` to avoid converting
+    externals.
 
 The following options only have an effect on the initial clone of a repository:
 
--- a/hgsubversion/layouts/base.py
+++ b/hgsubversion/layouts/base.py
@@ -48,7 +48,7 @@ class BaseLayout(object):
         """
         self.__unimplemented('remotepath')
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         """Return a list of locations within svn to search for tags
 
         Should be returned in reverse-sorted order.
--- a/hgsubversion/layouts/custom.py
+++ b/hgsubversion/layouts/custom.py
@@ -64,7 +64,7 @@ class CustomLayout(base.BaseLayout):
             subdir += '/'
         return subdir + self.remotename(branch)
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/detect.py
+++ b/hgsubversion/layouts/detect.py
@@ -58,7 +58,7 @@ def layout_from_config(ui, allow_auto=Fa
         raise hgutil.Abort("unknown layout '%s'" % layout)
     return layout
 
-def layout_from_file(meta_data_dir, ui=None):
+def layout_from_file(metapath, ui=None):
     """ Load the layout in use from the metadata file.
 
     If you pass the ui arg, we will also write the layout to the
@@ -67,7 +67,7 @@ def layout_from_file(meta_data_dir, ui=N
     """
 
     layout = None
-    layoutfile = os.path.join(meta_data_dir, 'layout')
+    layoutfile = os.path.join(metapath, 'layout')
     if os.path.exists(layoutfile):
         f = open(layoutfile)
         layout = f.read().strip()
--- a/hgsubversion/layouts/persist.py
+++ b/hgsubversion/layouts/persist.py
@@ -7,10 +7,10 @@ to do it.
 
 import os.path
 
-def layout_to_file(meta_data_dir, layout):
-    """Save the given layout to a file under the given meta_data_dir"""
+def layout_to_file(metapath, layout):
+    """Save the given layout to a file under the given metapath"""
 
-    layoutfile = os.path.join(meta_data_dir, 'layout')
+    layoutfile = os.path.join(metapath, 'layout')
     f = open(layoutfile, 'w')
     f.write(layout)
     f.close()
--- a/hgsubversion/layouts/single.py
+++ b/hgsubversion/layouts/single.py
@@ -14,7 +14,7 @@ class SingleLayout(base.BaseLayout):
     def remotepath(self, branch, subdir='/'):
         return subdir or '/'
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         return []
 
     def get_path_tag(self, path, taglocations):
--- a/hgsubversion/layouts/standard.py
+++ b/hgsubversion/layouts/standard.py
@@ -1,5 +1,4 @@
 import os.path
-import pickle
 
 import base
 
@@ -55,7 +54,7 @@ class StandardLayout(base.BaseLayout):
 
         return '%s/%s' % (subdir or '', branchpath)
 
-    def taglocations(self, meta_data_dir):
+    def taglocations(self, metapath):
         # import late to avoid trouble when running the test suite
         try:
             # newer versions of mercurial >= 2.8 will import this because the
@@ -66,17 +65,14 @@ class StandardLayout(base.BaseLayout):
 
         if self._tag_locations is None:
 
-            tag_locations_file = os.path.join(meta_data_dir, 'tag_locations')
+            tag_locations_file = os.path.join(metapath, 'tag_locations')
+            self._tag_locations = util.load(tag_locations_file)
 
-            if os.path.exists(tag_locations_file):
-                f = open(tag_locations_file)
-                self._tag_locations = pickle.load(f)
-                f.close()
-            else:
+            if not self._tag_locations:
                 self._tag_locations = self.ui.configlist('hgsubversion',
                                                         'tagpaths',
                                                         ['tags'])
-            util.pickle_atomic(self._tag_locations, tag_locations_file)
+            util.dump(self._tag_locations, tag_locations_file)
 
             # ensure nested paths are handled properly
             self._tag_locations.sort()
--- a/hgsubversion/maps.py
+++ b/hgsubversion/maps.py
@@ -3,7 +3,7 @@
 import errno
 import os
 from mercurial import util as hgutil
-from mercurial import node
+from mercurial.node import bin, hex, nullid
 
 import svncommands
 import util
@@ -14,12 +14,12 @@ class AuthorMap(dict):
 
     If the 'hgsubversion.defaultauthors' configuration option is set to false,
     attempting to obtain an unknown author will fail with an Abort.
-    
+
     If the 'hgsubversion.caseignoreauthors' configuration option is set to true,
     the userid from Subversion is always compared lowercase.
     '''
 
-    def __init__(self, ui, path, defaulthost=None):
+    def __init__(self, meta):
         '''Initialise a new AuthorMap.
 
         The ui argument is used to print diagnostic messages.
@@ -27,17 +27,19 @@ class AuthorMap(dict):
         The path argument is the location of the backing store,
         typically .hg/svn/authors.
         '''
-        self.ui = ui
-        self.path = path
-        self.use_defaultauthors = self.ui.configbool('hgsubversion', 'defaultauthors', True)
-        self.caseignoreauthors = self.ui.configbool('hgsubversion', 'caseignoreauthors', False)
-        if defaulthost:
-            self.defaulthost = '@%s' % defaulthost.lstrip('@')
-        else:
-            self.defaulthost = ''
+        self.meta = meta
+        self.defaulthost = ''
+        if meta.defaulthost:
+            self.defaulthost = '@%s' % meta.defaulthost.lstrip('@')
+
         self.super = super(AuthorMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.authors_file)
+
+        # append authors specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'authormap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         ''' Load mappings from a file at the specified path. '''
@@ -47,10 +49,10 @@ class AuthorMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.authors_file:
+            writing = open(self.meta.authors_file, 'a')
 
-        self.ui.debug('reading authormap from %s\n' % path)
+        self.meta.ui.debug('reading authormap from %s\n' % path)
         f = open(path, 'r')
         for number, line_org in enumerate(f):
 
@@ -62,21 +64,21 @@ class AuthorMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in author map %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
 
-            if self.caseignoreauthors:
+            if self.meta.caseignoreauthors:
                 src = src.lower()
 
             if writing:
                 if not src in self:
-                    self.ui.debug('adding author %s to author map\n' % src)
+                    self.meta.ui.debug('adding author %s to author map\n' % src)
                 elif dst != self[src]:
                     msg = 'overriding author: "%s" to "%s" (%s)\n'
-                    self.ui.status(msg % (self[src], dst, src))
+                    self.meta.ui.status(msg % (self[src], dst, src))
                 writing.write(line_org)
 
             self[src] = dst
@@ -92,21 +94,20 @@ class AuthorMap(dict):
         if author is None:
             author = '(no author)'
 
-        if self.caseignoreauthors:
+        search_author = author
+        if self.meta.caseignoreauthors:
             search_author = author.lower()
-        else:
-            search_author = author
 
         if search_author in self:
             result = self.super.__getitem__(search_author)
-        elif self.use_defaultauthors:
+        elif self.meta.defaultauthors:
             self[author] = result = '%s%s' % (author, self.defaulthost)
             msg = 'substituting author "%s" for default "%s"\n'
-            self.ui.debug(msg % (author, result))
+            self.meta.ui.debug(msg % (author, result))
         else:
             msg = 'author %s has no entry in the author map!'
             raise hgutil.Abort(msg % author)
-        self.ui.debug('mapping author "%s" to "%s"\n' % (author, result))
+        self.meta.ui.debug('mapping author "%s" to "%s"\n' % (author, result))
         return result
 
     def reverselookup(self, author):
@@ -126,27 +127,23 @@ class Tags(dict):
     """
     VERSION = 2
 
-    @classmethod
-    def filepath(cls, repo):
-        return os.path.join(repo.path, 'svn', 'tagmap')
-
-    def __init__(self, repo, endrev=None):
+    def __init__(self, meta, endrev=None):
         dict.__init__(self)
-        self.path = self.filepath(repo)
+        self.meta = meta
         self.endrev = endrev
-        if os.path.isfile(self.path):
-            self._load(repo)
+        if os.path.isfile(self.meta.tagfile):
+            self._load()
         else:
             self._write()
 
-    def _load(self, repo):
-        f = open(self.path)
+    def _load(self):
+        f = open(self.meta.tagfile)
         ver = int(f.readline())
         if ver < self.VERSION:
-            repo.ui.status('tag map outdated, running rebuildmeta...\n')
+            self.meta.ui.status('tag map outdated, running rebuildmeta...\n')
             f.close()
-            os.unlink(self.path)
-            svncommands.rebuildmeta(repo.ui, repo, ())
+            os.unlink(self.meta.tagfile)
+            svncommands.rebuildmeta(self.meta.ui, self.meta.repo, ())
             return
         elif ver != self.VERSION:
             raise hgutil.Abort('tagmap too new -- please upgrade')
@@ -158,12 +155,12 @@ class Tags(dict):
                 break
             if not tag:
                 continue
-            dict.__setitem__(self, tag, node.bin(ha))
+            dict.__setitem__(self, tag, bin(ha))
         f.close()
 
     def _write(self):
         assert self.endrev is None
-        f = open(self.path, 'w')
+        f = open(self.meta.tagfile, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
@@ -173,7 +170,7 @@ class Tags(dict):
 
     def __contains__(self, tag):
         return (tag and dict.__contains__(self, tag)
-                and dict.__getitem__(self, tag) != node.nullid)
+                and dict.__getitem__(self, tag) != nullid)
 
     def __getitem__(self, tag):
         if tag and tag in self:
@@ -184,8 +181,8 @@ class Tags(dict):
         if not tag:
             raise hgutil.Abort('tag cannot be empty')
         ha, revision = info
-        f = open(self.path, 'a')
-        f.write('%s %s %s\n' % (node.hex(ha), revision, tag))
+        f = open(self.meta.tagfile, 'a')
+        f.write('%s %s %s\n' % (hex(ha), revision, tag))
         f.close()
         dict.__setitem__(self, tag, ha)
 
@@ -194,32 +191,15 @@ class RevMap(dict):
 
     VERSION = 1
 
-    def __init__(self, repo):
+    def __init__(self, meta):
         dict.__init__(self)
-        self.path = self.mappath(repo)
-        self.repo = repo
-        self.ypath = os.path.join(repo.path, 'svn', 'lastpulled')
-        # TODO(durin42): Consider moving management of the youngest
-        # file to svnmeta itself rather than leaving it here.
-        # must load youngest file first, or else self._load() can
-        # clobber the info
-        _yonngest_str = util.load_string(self.ypath, '0')
-        self._youngest = int(_yonngest_str.strip())
-        self.oldest = 0
-        if os.path.isfile(self.path):
+        self.meta = meta
+
+        if os.path.isfile(self.meta.revmap_file):
             self._load()
         else:
             self._write()
 
-    def _set_youngest(self, rev):
-        self._youngest = max(self._youngest, rev)
-        util.save_string(self.ypath, str(self._youngest) + '\n')
-
-    def _get_youngest(self):
-        return self._youngest
-
-    youngest = property(_get_youngest, _set_youngest)
-
     def hashes(self):
         return dict((v, k) for (k, v) in self.iteritems())
 
@@ -227,14 +207,10 @@ class RevMap(dict):
         check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
         return sorted(filter(check, self.iteritems()), reverse=True)
 
-    @staticmethod
-    def mappath(repo):
-        return os.path.join(repo.path, 'svn', 'rev_map')
-
     @classmethod
-    def readmapfile(cls, repo, missingok=True):
+    def readmapfile(cls, path, missingok=True):
         try:
-            f = open(cls.mappath(repo))
+            f = open(path)
         except IOError, err:
             if not missingok or err.errno != errno.ENOENT:
                 raise
@@ -244,35 +220,41 @@ class RevMap(dict):
             raise hgutil.Abort('revmap too new -- please upgrade')
         return f
 
+    @util.gcdisable
     def _load(self):
-        for l in self.readmapfile(self.repo):
+        lastpulled = self.meta.lastpulled
+        firstpulled = self.meta.firstpulled
+        setitem = dict.__setitem__
+        for l in self.readmapfile(self.meta.revmap_file):
             revnum, ha, branch = l.split(' ', 2)
             if branch == '\n':
                 branch = None
             else:
                 branch = branch[:-1]
             revnum = int(revnum)
-            if revnum > self.youngest or not self.youngest:
-                self.youngest = revnum
-            if revnum < self.oldest or not self.oldest:
-                self.oldest = revnum
-            dict.__setitem__(self, (revnum, branch), node.bin(ha))
+            if revnum > lastpulled or not lastpulled:
+                lastpulled = revnum
+            if revnum < firstpulled or not firstpulled:
+                firstpulled = revnum
+            setitem(self, (revnum, branch), bin(ha))
+        self.meta.lastpulled = lastpulled
+        self.meta.firstpulled = firstpulled
 
     def _write(self):
-        f = open(self.path, 'w')
+        f = open(self.meta.revmap_file, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
     def __setitem__(self, key, ha):
         revnum, branch = key
-        f = open(self.path, 'a')
+        f = open(self.meta.revmap_file, 'a')
         b = branch or ''
-        f.write(str(revnum) + ' ' + node.hex(ha) + ' ' + b + '\n')
+        f.write(str(revnum) + ' ' + hex(ha) + ' ' + b + '\n')
         f.close()
-        if revnum > self.youngest or not self.youngest:
-            self.youngest = revnum
-        if revnum < self.oldest or not self.oldest:
-            self.oldest = revnum
+        if revnum > self.meta.lastpulled or not self.meta.lastpulled:
+            self.meta.lastpulled = revnum
+        if revnum < self.meta.firstpulled or not self.meta.firstpulled:
+            self.meta.firstpulled = revnum
         dict.__setitem__(self, (revnum, branch), ha)
 
 
@@ -280,7 +262,7 @@ class FileMap(object):
 
     VERSION = 1
 
-    def __init__(self, ui, path):
+    def __init__(self, meta):
         '''Initialise a new FileMap.
 
         The ui argument is used to print diagnostic messages.
@@ -288,15 +270,19 @@ class FileMap(object):
         The path argument is the location of the backing store,
         typically .hg/svn/filemap.
         '''
-        self.ui = ui
-        self.path = path
+        self.meta = meta
         self.include = {}
         self.exclude = {}
-        if os.path.isfile(self.path):
+        if os.path.isfile(self.meta.filemap_file):
             self._load()
         else:
             self._write()
 
+        # append file mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'filemap')
+        if clmap:
+            self.load(clmap)
+
     def _rpairs(self, name):
         e = len(name)
         while e != -1:
@@ -335,19 +321,19 @@ class FileMap(object):
         mapping = getattr(self, m)
         if path in mapping:
             msg = 'duplicate %s entry in %s: "%s"\n'
-            self.ui.status(msg % (m, fn, path))
+            self.meta.ui.status(msg % (m, fn, path))
             return
         bits = m.rstrip('e'), path
-        self.ui.debug('%sing %s\n' % bits)
+        self.meta.ui.debug('%sing %s\n' % bits)
         # respect rule order
         mapping[path] = len(self)
-        if fn != self.path:
-            f = open(self.path, 'a')
+        if fn != self.meta.filemap_file:
+            f = open(self.meta.filemap_file, 'a')
             f.write(m + ' ' + path + '\n')
             f.close()
 
     def load(self, fn):
-        self.ui.debug('reading file map from %s\n' % fn)
+        self.meta.ui.debug('reading file map from %s\n' % fn)
         f = open(fn, 'r')
         self.load_fd(f, fn)
         f.close()
@@ -363,22 +349,22 @@ class FileMap(object):
                 if cmd in ('include', 'exclude'):
                     self.add(fn, cmd, path)
                     continue
-                self.ui.warn('unknown filemap command %s\n' % cmd)
+                self.meta.ui.warn('unknown filemap command %s\n' % cmd)
             except IndexError:
                 msg = 'ignoring bad line in filemap %s: %s\n'
-                self.ui.warn(msg % (fn, line.rstrip()))
+                self.meta.ui.warn(msg % (fn, line.rstrip()))
 
     def _load(self):
-        self.ui.debug('reading in-repo file map from %s\n' % self.path)
-        f = open(self.path)
+        self.meta.ui.debug('reading in-repo file map from %s\n' % self.meta.filemap_file)
+        f = open(self.meta.filemap_file)
         ver = int(f.readline())
         if ver != self.VERSION:
             raise hgutil.Abort('filemap too new -- please upgrade')
-        self.load_fd(f, self.path)
+        self.load_fd(f, self.meta.filemap_file)
         f.close()
 
     def _write(self):
-        f = open(self.path, 'w')
+        f = open(self.meta.filemap_file, 'w')
         f.write('%s\n' % self.VERSION)
         f.close()
 
@@ -392,12 +378,16 @@ class BranchMap(dict):
     changes on other will now be on default (have no branch name set).
     '''
 
-    def __init__(self, ui, path):
-        self.ui = ui
-        self.path = path
+    def __init__(self, meta):
+        self.meta = meta
         self.super = super(BranchMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.branchmap_file)
+
+        # append branch mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'branchmap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         '''Load mappings from a file at the specified path.'''
@@ -405,10 +395,10 @@ class BranchMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.branchmap_file:
+            writing = open(self.meta.branchmap_file, 'a')
 
-        self.ui.debug('reading branchmap from %s\n' % path)
+        self.meta.ui.debug('reading branchmap from %s\n' % path)
         f = open(path, 'r')
         for number, line in enumerate(f):
 
@@ -423,12 +413,12 @@ class BranchMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in branch map %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
-            self.ui.debug('adding branch %s to branch map\n' % src)
+            self.meta.ui.debug('adding branch %s to branch map\n' % src)
 
             if not dst:
                 # prevent people from assuming such lines are valid
@@ -437,7 +427,7 @@ class BranchMap(dict):
                                    % (number, path))
             elif src in self and dst != self[src]:
                 msg = 'overriding branch: "%s" to "%s" (%s)\n'
-                self.ui.status(msg % (self[src], dst, src))
+                self.meta.ui.status(msg % (self[src], dst, src))
             self[src] = dst
 
         f.close()
@@ -454,12 +444,16 @@ class TagMap(dict):
         the other tag will not be reflected in the hg repository.
     '''
 
-    def __init__(self, ui, path):
-        self.ui = ui
-        self.path = path
+    def __init__(self, meta):
+        self.meta = meta
         self.super = super(TagMap, self)
         self.super.__init__()
-        self.load(path)
+        self.load(self.meta.tagmap_file)
+
+        # append tag mapping specified from the commandline
+        clmap = util.configpath(self.meta.ui, 'tagmap')
+        if clmap:
+            self.load(clmap)
 
     def load(self, path):
         '''Load mappings from a file at the specified path.'''
@@ -467,10 +461,10 @@ class TagMap(dict):
             return
 
         writing = False
-        if path != self.path:
-            writing = open(self.path, 'a')
+        if path != self.meta.tagmap_file:
+            writing = open(self.meta.tagmap_file, 'a')
 
-        self.ui.debug('reading tag renames from %s\n' % path)
+        self.meta.ui.debug('reading tag renames from %s\n' % path)
         f = open(path, 'r')
         for number, line in enumerate(f):
 
@@ -485,16 +479,16 @@ class TagMap(dict):
                 src, dst = line.split('=', 1)
             except (IndexError, ValueError):
                 msg = 'ignoring line %i in tag renames %s: %s\n'
-                self.ui.status(msg % (number, path, line.rstrip()))
+                self.meta.ui.status(msg % (number, path, line.rstrip()))
                 continue
 
             src = src.strip()
             dst = dst.strip()
-            self.ui.debug('adding tag %s to tag renames\n' % src)
+            self.meta.ui.debug('adding tag %s to tag renames\n' % src)
 
             if src in self and dst != self[src]:
                 msg = 'overriding tag rename: "%s" to "%s" (%s)\n'
-                self.ui.status(msg % (self[src], dst, src))
+                self.meta.ui.status(msg % (self[src], dst, src))
             self[src] = dst
 
         f.close()
--- a/hgsubversion/replay.py
+++ b/hgsubversion/replay.py
@@ -65,13 +65,13 @@ def _convert_rev(ui, meta, svn, r, tbdel
     editor.current.rev = r
     editor.setsvn(svn)
 
-    if firstrun and meta.revmap.oldest <= 0:
+    if firstrun and meta.firstpulled <= 0:
         # We know nothing about this project, so fetch everything before
         # trying to apply deltas.
         ui.debug('replay: fetching full revision\n')
         svn.get_revision(r.revnum, editor)
     else:
-        svn.get_replay(r.revnum, editor, meta.revmap.oldest)
+        svn.get_replay(r.revnum, editor, meta.firstpulled)
     editor.close()
 
     current = editor.current
@@ -147,7 +147,10 @@ def _convert_rev(ui, meta, svn, r, tbdel
 
         def filectxfn(repo, memctx, path):
             current_file = files[path]
-            data, isexec, islink, copied = current.pop(current_file)
+            try:
+                data, isexec, islink, copied = current.pop(current_file)
+            except IOError:
+                return compathacks.filectxfn_deleted_reraise(memctx)
             if isexec is None or islink is None:
                 flags = parentctx.flags(path)
                 if isexec is None:
--- a/hgsubversion/stupid.py
+++ b/hgsubversion/stupid.py
@@ -158,27 +158,8 @@ def filteriterhunks(meta):
         applycurrent = False
         # Passing False instead of textmode because we should never
         # be ignoring EOL type.
-        if iterhunks.func_code.co_argcount == 1:
-            # Since 1.9 (28762bb767dc)
-            fp = args[0]
-            gen = iterhunks(fp)
-        else:
-            ui, fp = args[:2]
-            if len(args) > 2:
-                sourcefile = args[2]
-            else:
-                sourcefile = kwargs.get('sourcefile', None)
-            if len(args) > 3:
-                textmode = args[3]
-            else:
-                textmode = kwargs.get('textmode', False)
-            if not iterhunks.func_defaults:
-                # Since 1.7 (cfedc529e4a1)
-                gen = iterhunks(ui, fp)
-            elif len(iterhunks.func_defaults) == 1:
-                gen = iterhunks(ui, fp, sourcefile)
-            else:
-                gen = iterhunks(ui, fp, sourcefile, textmode)
+        fp = args[0]
+        gen = iterhunks(fp)
         for data in gen:
             if data[0] == 'file':
                 if data[1][1] in meta.filemap:
@@ -226,7 +207,16 @@ def patchrepoold(ui, meta, parentctx, pa
 try:
     class svnbackend(patch.repobackend):
         def getfile(self, fname):
-            data, (islink, isexec) = super(svnbackend, self).getfile(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)
@@ -361,13 +351,16 @@ def diff_branchrev(ui, svn, meta, branch
                       if f.symlink is not None)
     def filectxfn(repo, memctx, path):
         if path in files_data and files_data[path] is None:
-            raise IOError(errno.ENOENT, '%s is deleted' % path)
+            return compathacks.filectxfn_deleted(memctx, path)
 
         if path in binary_files or path in unknown_files:
             pa = path
             if branchpath:
                 pa = branchpath + '/' + path
-            data, mode = svn.get_file(pa, r.revnum)
+            try:
+                data, mode = svn.get_file(pa, r.revnum)
+            except IOError:
+                return compathacks.filectxfn_deleted_reraise(memctx)
             isexe = 'x' in mode
             islink = 'l' in mode
         else:
@@ -587,7 +580,10 @@ def fetch_branchrev(svn, meta, branch, b
         svnpath = path
         if branchpath:
             svnpath = branchpath + '/' + path
-        data, mode = svn.get_file(svnpath, r.revnum)
+        try:
+            data, mode = svn.get_file(svnpath, r.revnum)
+        except IOError:
+            return compathacks.filectxfn_deleted_reraise(memctx)
         isexec = 'x' in mode
         islink = 'l' in mode
         copied = copies.get(path)
@@ -736,7 +732,7 @@ def convert_rev(ui, meta, svn, r, tbdelt
         # path does not support this case with svn >= 1.7. We can fix
         # it, or we can force the existing fetch_branchrev() path. Do
         # the latter for now.
-        incremental = (meta.revmap.oldest > 0 and
+        incremental = (meta.firstpulled > 0 and
                        parentctx.rev() != node.nullrev and
                        not firstrun)
 
--- a/hgsubversion/svncommands.py
+++ b/hgsubversion/svncommands.py
@@ -1,6 +1,5 @@
 import os
 import posixpath
-import cPickle as pickle
 import sys
 import traceback
 import urlparse
@@ -19,6 +18,7 @@ import svnrepo
 import util
 import svnexternals
 import verify
+import svnmeta
 
 
 def updatemeta(ui, repo, args, **opts):
@@ -39,22 +39,6 @@ def rebuildmeta(ui, repo, args, unsafe_s
     return _buildmeta(ui, repo, args, partial=False,
                       skipuuid=unsafe_skip_uuid_check)
 
-def read_if_exists(path):
-     try:
-        fp = open(path, 'rb')
-        d = fp.read()
-        fp.close()
-        return d
-     except IOError, err:
-         if err.errno != errno.ENOENT:
-             raise
-
-def write_if_needed(path, content):
-    if read_if_exists(path) != content:
-        fp = open(path, 'wb')
-        fp.write(content)
-        fp.close()
-
 def _buildmeta(ui, repo, args, partial=False, skipuuid=False):
 
     if repo is None:
@@ -70,19 +54,13 @@ def _buildmeta(ui, repo, args, partial=F
         raise hgutil.Abort('rebuildmeta takes 1 or no arguments')
     url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or
                              repo.ui.config('paths', 'default') or '')
-    svnmetadir = os.path.join(repo.path, 'svn')
-    if not os.path.exists(svnmetadir):
-        os.makedirs(svnmetadir)
-    uuidpath = os.path.join(svnmetadir, 'uuid')
-    uuid = read_if_exists(uuidpath)
-
-    subdirpath = os.path.join(svnmetadir, 'subdir')
-    subdir = read_if_exists(subdirpath)
+
+    meta = svnmeta.SVNMeta(repo, skiperrorcheck=True)
+
     svn = None
-    if subdir is None:
+    if meta.subdir is None:
         svn = svnrepo.svnremoterepo(ui, url).svn
-        subdir = svn.subdir
-        open(subdirpath, 'wb').write(subdir.strip('/'))
+        meta.subdir = svn.subdir
 
     youngest = 0
     startrev = 0
@@ -90,16 +68,18 @@ def _buildmeta(ui, repo, args, partial=F
     branchinfo = {}
     if partial:
         try:
-            youngestpath = os.path.join(svnmetadir, 'lastpulled')
+            # we can't use meta.lastpulled here because we are bootstraping the
+            # lastpulled and want to keep the cached value on disk during a
+            # partial rebuild
             foundpartialinfo = False
+            youngestpath = os.path.join(meta.metapath, 'lastpulled')
             if os.path.exists(youngestpath):
-                youngest = int(util.load_string(youngestpath).strip())
-                sofar = list(maps.RevMap.readmapfile(repo))
+                youngest = util.load(youngestpath)
+                sofar = list(maps.RevMap.readmapfile(meta.revmap_file))
                 if sofar and len(sofar[-1].split(' ', 2)) > 1:
                     lasthash = sofar[-1].split(' ', 2)[1]
                     startrev = repo[lasthash].rev() + 1
-                    branchinfo = pickle.load(open(os.path.join(svnmetadir,
-                                                           'branch_info')))
+                    branchinfo = util.load(meta.branch_info_file)
                     foundpartialinfo = True
             if not foundpartialinfo:
                 ui.status('missing some metadata -- doing a full rebuild\n')
@@ -111,16 +91,12 @@ def _buildmeta(ui, repo, args, partial=F
         except AttributeError:
             ui.status('no metadata available -- doing a full rebuild\n')
 
-
-    lastpulled = open(os.path.join(svnmetadir, 'lastpulled'), 'wb')
-    revmap = open(os.path.join(svnmetadir, 'rev_map'), 'w')
-    revmap.write('1\n')
+    revmap = open(meta.revmap_file, 'w')
+    revmap.write('%d\n' % maps.RevMap.VERSION)
     revmap.writelines(sofar)
     last_rev = -1
-    tagfile = os.path.join(svnmetadir, 'tagmap')
-    if not partial and os.path.exists(maps.Tags.filepath(repo)) :
-        os.unlink(maps.Tags.filepath(repo))
-    tags = maps.Tags(repo)
+    if not partial and os.path.exists(meta.tagfile):
+        os.unlink(meta.tagfile)
 
     layout = None
     layoutobj = None
@@ -164,7 +140,7 @@ def _buildmeta(ui, repo, args, partial=F
             else:
                 closed.add(parentctx.rev())
 
-    lastpulled.write(str(youngest) + '\n')
+    meta.lastpulled = youngest
     ui.progress('prepare', None, total=numrevs)
 
     for rev in xrange(startrev, len(repo)):
@@ -197,11 +173,13 @@ def _buildmeta(ui, repo, args, partial=F
                 # number.
                 tagging = int(convinfo[40:].split('@')[1])
                 tagrev = max(tagged, tagging)
-                tags[tag] = node.bin(ha), tagrev
+                meta.tags[tag] = node.bin(ha), tagrev
 
         # check that the conversion metadata matches expectations
         assert convinfo.startswith('svn:')
         revpath, revision = convinfo[40:].split('@')
+        # use tmp variable for testing
+        subdir = meta.subdir
         if subdir and subdir[0] != '/':
             subdir = '/' + subdir
         if subdir and subdir[-1] == '/':
@@ -212,16 +190,16 @@ def _buildmeta(ui, repo, args, partial=F
         if layout is None:
             layout = layouts.detect.layout_from_commit(subdir, revpath,
                                                        ctx.branch(), ui)
-            existing_layout = layouts.detect.layout_from_file(svnmetadir)
+            existing_layout = layouts.detect.layout_from_file(meta.metapath)
             if layout != existing_layout:
-                layouts.persist.layout_to_file(svnmetadir, layout)
+                layouts.persist.layout_to_file(meta.metapath, layout)
             layoutobj = layouts.layout_from_name(layout, ui)
         elif layout == 'single':
             assert (subdir or '/') == revpath, ('Possible layout detection'
                                                 ' defect in replay')
 
         # write repository uuid if required
-        if uuid is None or validateuuid:
+        if meta.uuid is None or validateuuid:
             validateuuid = False
             uuid = convinfo[4:40]
             if not skipuuid:
@@ -230,7 +208,7 @@ def _buildmeta(ui, repo, args, partial=F
                 if uuid != svn.uuid:
                     raise hgutil.Abort('remote svn repository identifier '
                                        'does not match')
-            write_if_needed(uuidpath, uuid)
+            meta.uuid = uuid
 
         # don't reflect closed branches
         if (ctx.extra().get('close') and not ctx.files() or
@@ -241,7 +219,7 @@ def _buildmeta(ui, repo, args, partial=F
         # find commitpath, write to revmap
         commitpath = revpath[len(subdir)+1:]
 
-        tag_locations = layoutobj.taglocations(svnmetadir)
+        tag_locations = layoutobj.taglocations(meta.metapath)
         found_tag = False
         for location in tag_locations:
             if commitpath.startswith(location + '/'):
@@ -304,9 +282,7 @@ def _buildmeta(ui, repo, args, partial=F
     ui.progress('rebuild', None, total=numrevs)
 
     # save off branch info
-    branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w')
-    pickle.dump(branchinfo, branchinfofile)
-    branchinfofile.close()
+    util.dump(branchinfo, meta.branch_info_file)
 
 
 def help_(ui, args=None, **opts):
--- a/hgsubversion/svnexternals.py
+++ b/hgsubversion/svnexternals.py
@@ -3,26 +3,14 @@ import cStringIO
 import os, re, shutil, stat, subprocess
 from mercurial import util as hgutil
 from mercurial.i18n import _
+from mercurial import subrepo
 
 try:
-    from mercurial import subrepo
-    # require svnsubrepo and hg >= 1.7.1
-    subrepo.svnsubrepo
-    hgutil.checknlink
-except (ImportError, AttributeError), e:
-    subrepo = None
-
-passpegrev = True # see svnsubrepo below
-try:
-    canonpath = hgutil.canonpath
+    from mercurial import scmutil
+    canonpath = scmutil.canonpath
 except (ImportError, AttributeError):
-    passpegrev = False
-    try:
-        from mercurial import scmutil
-        canonpath = scmutil.canonpath
-    except (ImportError, AttributeError):
-        from mercurial import pathutil
-        canonpath = pathutil.canonpath
+    from mercurial import pathutil
+    canonpath = pathutil.canonpath
 
 import util
 
@@ -398,58 +386,53 @@ def parse(ui, ctx):
         raise hgutil.Abort(_('unknown externals modes: %s') % mode)
     return external
 
-if subrepo:
-    class svnsubrepo(subrepo.svnsubrepo):
-        def __init__(self, ctx, path, state):
-            state = (state[0].split(':', 1)[1], state[1])
-            super(svnsubrepo, self).__init__(ctx, path, state)
-
-        def get(self, state, *args, **kwargs):
-            # Resolve source first
-            line = state[0].split(':', 1)[1]
-            source, pegrev = parsedefinition(line)[2:4]
-            try:
-                # Getting the root SVN repository URL is expensive.
-                # Assume the externals is absolute.
-                source = resolvesource(self._ui, None, source)
-            except RelativeSourceError:
-                svnurl = self._ctx._repo.ui.expandpath('default')
-                svnroot = getsvninfo(util.normalize_url(svnurl))[1]
-                source = resolvesource(self._ui, svnroot, source)
-            # hg < 1.9 svnsubrepo calls "svn checkout" with --rev
-            # only, so peg revisions are correctly used. 1.9 and
-            # higher, append the rev as a peg revision to the source
-            # URL, so we cannot add our own. We assume that "-r10
-            # url@2" will be similar to "url@10" most of the time.
-            if pegrev is not None and passpegrev:
-                source = source + '@' + pegrev
-            state = (source, state[1])
-            # hg-1.7.4-c19b9282d3a7 introduced the overwrite argument
-            return super(svnsubrepo, self).get(state, *args, **kwargs)
-
-        def dirty(self, ignoreupdate=False):
-            # You cannot compare anything with HEAD. Just accept it
-            # can be anything.
-            if hasattr(self, '_wcrevs'):
-                wcrevs = self._wcrevs()
-            else:
-                wcrev = self._wcrev()
-                wcrevs = (wcrev, wcrev)
-            if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or
-                self._state[1] in wcrevs or ignoreupdate)
-                and not self._wcchanged()[0]):
-                return False
-            return True
-
-        def commit(self, text, user, date):
-            rev = super(svnsubrepo, self).commit(text, user, date)
-            # Keep unversioned externals unversioned
-            if self._state[1] == 'HEAD':
-                rev = 'HEAD'
-            return rev
-
-        def basestate(self):
-            # basestate() was introduced by bcb973abcc0b in 2.2
-            if self._state[1] == 'HEAD':
-                return 'HEAD'
-            return super(svnsubrepo, self).basestate()
+class svnsubrepo(subrepo.svnsubrepo):
+    def __init__(self, ctx, path, state):
+        state = (state[0].split(':', 1)[1], state[1])
+        super(svnsubrepo, self).__init__(ctx, path, state)
+
+    def get(self, state, *args, **kwargs):
+        # Resolve source first
+        line = state[0].split(':', 1)[1]
+        source, pegrev = parsedefinition(line)[2:4]
+        try:
+            # Getting the root SVN repository URL is expensive.
+            # Assume the externals is absolute.
+            source = resolvesource(self._ui, None, source)
+        except RelativeSourceError:
+            svnurl = self._ctx._repo.ui.expandpath('default')
+            svnroot = getsvninfo(util.normalize_url(svnurl))[1]
+            source = resolvesource(self._ui, svnroot, source)
+        # hg 1.9 and higher, append the rev as a peg revision to
+        # the source URL, so we cannot add our own. We assume
+        # that "-r10 url@2" will be similar to "url@10" most of
+        # the time.
+        state = (source, state[1])
+        return super(svnsubrepo, self).get(state, *args, **kwargs)
+
+    def dirty(self, ignoreupdate=False):
+        # You cannot compare anything with HEAD. Just accept it
+        # can be anything.
+        if hasattr(self, '_wcrevs'):
+            wcrevs = self._wcrevs()
+        else:
+            wcrev = self._wcrev()
+            wcrevs = (wcrev, wcrev)
+        if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or
+            self._state[1] in wcrevs or ignoreupdate)
+            and not self._wcchanged()[0]):
+            return False
+        return True
+
+    def commit(self, text, user, date):
+        rev = super(svnsubrepo, self).commit(text, user, date)
+        # Keep unversioned externals unversioned
+        if self._state[1] == 'HEAD':
+            rev = 'HEAD'
+        return rev
+
+    def basestate(self):
+        # basestate() was introduced by bcb973abcc0b in 2.2
+        if self._state[1] == 'HEAD':
+            return 'HEAD'
+        return super(svnsubrepo, self).basestate()
--- a/hgsubversion/svnmeta.py
+++ b/hgsubversion/svnmeta.py
@@ -1,4 +1,3 @@
-import cPickle as pickle
 import posixpath
 import os
 import tempfile
@@ -17,7 +16,7 @@ import editor
 
 class SVNMeta(object):
 
-    def __init__(self, repo, uuid=None, subdir=None):
+    def __init__(self, repo, uuid=None, subdir=None, skiperrorcheck=False):
         """path is the path to the target hg repo.
 
         subdir is the subdirectory of the edits *on the svn server*.
@@ -26,52 +25,115 @@ class SVNMeta(object):
         self.ui = repo.ui
         self.repo = repo
         self.path = os.path.normpath(repo.join('..'))
+        self._skiperror = skiperrorcheck
 
-        if not os.path.isdir(self.meta_data_dir):
-            os.makedirs(self.meta_data_dir)
+        if not os.path.isdir(self.metapath):
+            os.makedirs(self.metapath)
         self.uuid = uuid
         self.subdir = subdir
-        self.revmap = maps.RevMap(repo)
-
-        author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
-        authors = util.configpath(self.ui, 'authormap')
-        self.usebranchnames = self.ui.configbool('hgsubversion',
-                                                 'usebranchnames', True)
-        branchmap = util.configpath(self.ui, 'branchmap')
-        tagmap = util.configpath(self.ui, 'tagmap')
-        filemap = util.configpath(self.ui, 'filemap')
-
-        self.branches = {}
-        if os.path.exists(self.branch_info_file):
-            f = open(self.branch_info_file)
-            self.branches = pickle.load(f)
-            f.close()
+        self._revmap = None
+        self.firstpulled = 0
+
+        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.branches = util.load(self.branch_info_file) or {}
         self.prevbranches = dict(self.branches)
-        self.tags = maps.Tags(repo)
-        self._layout = layouts.detect.layout_from_file(self.meta_data_dir,
+        self._tags = None
+        self._layout = layouts.detect.layout_from_file(self.metapath,
                                                        ui=self.repo.ui)
         self._layoutobj = None
 
-        self.authors = maps.AuthorMap(self.ui, self.authors_file,
-                                 defaulthost=author_host)
-        if authors: self.authors.load(authors)
+        self._authors = None
 
-        self.branchmap = maps.BranchMap(self.ui, self.branchmapfile)
-        if branchmap:
-            self.branchmap.load(branchmap)
+        self._branchmap = None
 
-        self.tagmap = maps.TagMap(self.ui, self.tagmapfile)
-        if tagmap:
-            self.tagmap.load(tagmap)
+        self._tagmap = None
 
-        self.filemap = maps.FileMap(self.ui, self.filemap_file)
-        if filemap:
-            self.filemap.load(filemap)
+        self._filemap = None
 
         self.lastdate = '1970-01-01 00:00:00 -0000'
         self.addedtags = {}
         self.deletedtags = {}
 
+    def _get_cachedconfig(self, name, filename, configname, default):
+        """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.
+            name: property name, e.g. 'lastpulled'
+            filename: name of file in .hg/svn
+            configname: commandline option name
+            default: default value
+        """
+        varname = '_' + name
+        if getattr(self, varname) is None:
+            # construct the file path from metapath (e.g. .hg/svn) plus the
+            # filename
+            f = os.path.join(self.metapath, filename)
+
+            # load the config property (i.e. command-line or .hgrc)
+            c = None
+            if configname:
+                # a little awkward but we need to convert the option from a
+                # string to whatever type the default value is, so we use the
+                # type of `default` to determine with ui.config method to call
+                c = None
+                if isinstance(default, bool):
+                    c = self.ui.configbool('hgsubversion', configname, default)
+                elif isinstance(default, int):
+                    c = self.ui.configint('hgsubversion', configname, default)
+                elif isinstance(default, list):
+                    c = self.ui.configlist('hgsubversion', configname, default)
+                else:
+                    c = self.ui.config('hgsubversion', configname, default)
+
+            # load the value from disk
+            val = util.load(f, default=default)
+
+            # prefer the non-default, and the one sent from command-line
+            if c is not None and c != val and c != default:
+                val = c
+
+            # set the value as the one from disk (or default if not found)
+            setattr(self, varname, val)
+
+            # save the value to disk by using the setter property
+            setattr(self, name, val)
+
+        return getattr(self, varname)
+
+    def _set_cachedconfig(self, value, name, filename):
+        varname = '_' + name
+        f = os.path.join(self.metapath, filename)
+        setattr(self, varname, value)
+        util.dump(value, f)
+
+    def _gen_cachedconfig(self, name, default=None, filename=None,
+                          configname=None):
+        """Generate an attribute for reading (and caching) config data.
+
+        This method constructs a new attribute on self with the given name.
+        The actual value from the config file will be read lazily, and then
+        cached once that read has occurred. No cache invalidation will happen,
+        so within a session these values shouldn't be required to mutate.
+        """
+        setattr(SVNMeta, '_' + name, None)
+        if filename is None:
+            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))
+        setattr(SVNMeta, name, prop)
+
     @property
     def layout(self):
         # this method can't determine the layout, but it needs to be
@@ -79,7 +141,7 @@ class SVNMeta(object):
         # gets called
         if not self._layout or self._layout == 'auto':
             self._layout = layouts.detect.layout_from_config(self.repo.ui)
-            layouts.persist.layout_to_file(self.meta_data_dir, self._layout)
+            layouts.persist.layout_to_file(self.metapath, self._layout)
         return self._layout
 
     @property
@@ -101,24 +163,23 @@ class SVNMeta(object):
         if subdir:
             subdir = '/'.join(p for p in subdir.split('/') if p)
 
-        subdirfile = os.path.join(self.meta_data_dir, 'subdir')
+        self.__subdir = None
+        subdirfile = os.path.join(self.metapath, 'subdir')
 
         if os.path.isfile(subdirfile):
-            stored_subdir = open(subdirfile).read()
+            stored_subdir = util.load(subdirfile)
             assert stored_subdir is not None
             if subdir is None:
                 self.__subdir = stored_subdir
-            elif subdir != stored_subdir:
+            elif subdir and subdir != stored_subdir:
                 raise hgutil.Abort('unable to work on a different path in the '
                                    'repository')
             else:
                 self.__subdir = subdir
         elif subdir is not None:
-            f = open(subdirfile, 'w')
-            f.write(subdir)
-            f.close()
+            util.dump(subdir, subdirfile)
             self.__subdir = subdir
-        else:
+        elif not self._skiperror:
             raise hgutil.Abort("hgsubversion metadata unavailable; "
                                "please run 'hg svn rebuildmeta'")
 
@@ -130,19 +191,18 @@ class SVNMeta(object):
         return self.__uuid
 
     def _set_uuid(self, uuid):
-        uuidfile = os.path.join(self.meta_data_dir, 'uuid')
+        self.__uuid = None
+        uuidfile = os.path.join(self.metapath, 'uuid')
         if os.path.isfile(uuidfile):
-            stored_uuid = open(uuidfile).read()
+            stored_uuid = util.load(uuidfile)
             assert stored_uuid
             if uuid and uuid != stored_uuid:
                 raise hgutil.Abort('unable to operate on unrelated repository')
             self.__uuid = uuid or stored_uuid
         elif uuid:
-            f = open(uuidfile, 'w')
-            f.write(uuid)
-            f.close()
+            util.dump(uuid, uuidfile)
             self.__uuid = uuid
-        else:
+        elif not self._skiperror:
             raise hgutil.Abort("hgsubversion metadata unavailable; "
                                "please run 'hg svn rebuildmeta'")
 
@@ -150,29 +210,74 @@ class SVNMeta(object):
                     'Error-checked UUID of source Subversion repository.')
 
     @property
-    def meta_data_dir(self):
+    def metapath(self):
         return os.path.join(self.path, '.hg', 'svn')
 
     @property
     def branch_info_file(self):
-        return os.path.join(self.meta_data_dir, 'branch_info')
+        return os.path.join(self.metapath, 'branch_info')
 
     @property
     def authors_file(self):
-        return os.path.join(self.meta_data_dir, 'authors')
+        return os.path.join(self.metapath, 'authors')
+
+    @property
+    def authors(self):
+        if self._authors is None:
+            self._authors = maps.AuthorMap(self)
+        return self._authors
 
     @property
     def filemap_file(self):
-        return os.path.join(self.meta_data_dir, 'filemap')
+        return os.path.join(self.metapath, 'filemap')
+
+    @property
+    def filemap(self):
+        if self._filemap is None:
+            self._filemap = maps.FileMap(self)
+        return self._filemap
+
+    @property
+    def branchmap_file(self):
+        return os.path.join(self.metapath, 'branchmap')
+
+    @property
+    def branchmap(self):
+        if self._branchmap is None:
+            self._branchmap = maps.BranchMap(self)
+        return self._branchmap
 
     @property
-    def branchmapfile(self):
-        return os.path.join(self.meta_data_dir, 'branchmap')
+    def tagfile(self):
+        # called tagmap for backwards compatibility
+        return os.path.join(self.metapath, 'tagmap')
 
     @property
-    def tagmapfile(self):
+    def tags(self):
+        if self._tags is None:
+            self._tags = maps.Tags(self)
+        return self._tags
+
+    @property
+    def tagmap_file(self):
         # called tag-renames for backwards compatibility
-        return os.path.join(self.meta_data_dir, 'tag-renames')
+        return os.path.join(self.metapath, 'tag-renames')
+
+    @property
+    def tagmap(self):
+        if self._tagmap is None:
+            self._tagmap = maps.TagMap(self)
+        return self._tagmap
+
+    @property
+    def revmap_file(self):
+        return os.path.join(self.metapath, 'rev_map')
+
+    @property
+    def revmap(self):
+        if self._revmap is None:
+            self._revmap = maps.RevMap(self)
+        return self._revmap
 
     def fixdate(self, date):
         if date is not None:
@@ -187,7 +292,7 @@ class SVNMeta(object):
         '''Save the Subversion metadata. This should really be called after
         every revision is created.
         '''
-        util.pickle_atomic(self.branches, self.branch_info_file)
+        util.dump(self.branches, self.branch_info_file)
 
     def localname(self, path):
         """Compute the local name for a branch located at path.
@@ -247,7 +352,7 @@ class SVNMeta(object):
 
     @property
     def taglocations(self):
-        return self.layoutobj.taglocations(self.meta_data_dir)
+        return self.layoutobj.taglocations(self.metapath)
 
     def get_path_tag(self, path):
         """If path could represent the path to a tag, returns the
@@ -395,7 +500,7 @@ class SVNMeta(object):
                     return node.hex(self.revmap[tagged])
                 tag = fromtag
             # Reference an existing tag
-            limitedtags = maps.Tags(self.repo, endrev=number - 1)
+            limitedtags = maps.Tags(self, endrev=number - 1)
             if tag in limitedtags:
                 return limitedtags[tag]
         r, br = self.get_parent_svn_branch_and_rev(number - 1, branch, exact)
--- a/hgsubversion/svnrepo.py
+++ b/hgsubversion/svnrepo.py
@@ -17,6 +17,7 @@ subclass: pull() is called on the instan
 import errno
 
 from mercurial import error
+from mercurial import localrepo
 from mercurial import util as hgutil
 
 try:
@@ -71,10 +72,6 @@ def generate_repo_class(ui, repo):
         """
         original = getattr(repo, fn.__name__, None)
 
-        # remove when dropping support for hg < 1.6.
-        if original is None and fn.__name__ == 'findoutgoing':
-            return
-
         def wrapper(self, *args, **opts):
             capable = getattr(args[0], 'capable', lambda x: False)
             if capable('subversion'):
@@ -99,21 +96,25 @@ def generate_repo_class(ui, repo):
                 self.pushkey('phases', self[hash].hex(), str(phases.draft), str(phases.public))
             return hash
 
-        # TODO use newbranch to allow branch creation in Subversion?
-        @remotesvn
-        def push(self, remote, force=False, revs=None, newbranch=None):
-            return wrappers.push(self, remote, force, revs)
+        if hgutil.safehasattr(localrepo.localrepository, 'push'):
+            # Mercurial < 3.2
+            # TODO use newbranch to allow branch creation in Subversion?
+            @remotesvn
+            def push(self, remote, force=False, revs=None, newbranch=None):
+                return wrappers.push(self, remote, force, revs)
 
-        @remotesvn
-        def pull(self, remote, heads=[], force=False):
-            return wrappers.pull(self, remote, heads, force)
+        if hgutil.safehasattr(localrepo.localrepository, 'pull'):
+            # Mercurial < 3.2
+            @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.findoutgoing(repo, remote, heads, force)
 
-        def svnmeta(self, uuid=None, subdir=None):
-            return svnmeta.SVNMeta(self, uuid, subdir)
+        def svnmeta(self, uuid=None, subdir=None, skiperrorcheck=False):
+            return svnmeta.SVNMeta(self, uuid, subdir, skiperrorcheck)
 
     repo.__class__ = svnlocalrepo
 
--- a/hgsubversion/util.py
+++ b/hgsubversion/util.py
@@ -1,8 +1,10 @@
-import cPickle as pickle
+import compathacks
 import errno
 import re
 import os
 import urllib
+import json
+import gc
 
 from mercurial import cmdutil
 from mercurial import error
@@ -54,6 +56,18 @@ def filterdiff(diff, oldrev, newrev):
     diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff)
     return diff
 
+def gcdisable(orig):
+    """decorator to disable GC for a function or method"""
+    def wrapper(*args, **kwargs):
+        enabled = gc.isenabled()
+        if enabled:
+            gc.disable()
+        try:
+            orig(*args, **kwargs)
+        finally:
+            if enabled:
+                gc.enable()
+    return wrapper
 
 def parentrev(ui, repo, meta, hashes):
     """Find the svn parent revision of the repo's dirstate.
@@ -120,57 +134,74 @@ def normalize_url(url):
         url = '%s#%s' % (url, checkout)
     return url
 
+def _scrub(data):
+    if not data and not isinstance(data, list):
+        return ''
+    return data
+
+def _descrub(data):
+    if isinstance(data, list):
+        return tuple(data)
+    if data == '':
+        return None
+    return data
+
+def _convert(input, visitor):
+    if isinstance(input, dict):
+        scrubbed = {}
+        d = dict([(_convert(key, visitor), _convert(value, visitor))
+                  for key, value in input.iteritems()])
+        for key, val in d.iteritems():
+            scrubbed[visitor(key)] = visitor(val)
+        return scrubbed
+    elif isinstance(input, list):
+        return [_convert(element, visitor) for element in input]
+    elif isinstance(input, unicode):
+        return input.encode('utf-8')
+    return input
+
+def dump(data, file_path):
+    """Serialize some data to a path atomically.
+
+    This is present because I kept corrupting my revmap by managing to hit ^C
+    during the serialization of that file.
+    """
+    f = hgutil.atomictempfile(file_path, 'w+b', 0644)
+    json.dump(_convert(data, _scrub), f)
+    f.close()
 
-def load_string(file_path, default=None, limit=1024):
+def load(file_path, default=None, resave=True):
+    """Deserialize some data from a path.
+    """
+    data = default
     if not os.path.exists(file_path):
-        return default
+        return data
+
+    f = open(file_path)
     try:
-        f = open(file_path, 'r')
-        ret = f.read(limit)
+        data = _convert(json.load(f), _descrub)
         f.close()
-    except:
-        return default
-    if ret == '':
-        return default
-    return ret
-
-
-def save_string(file_path, string):
-    if string is None:
-        string = ""
-    f = open(file_path, 'wb')
-    f.write(str(string))
-    f.close()
-
-def pickle_atomic(data, file_path):
-    """pickle some data to a path atomically.
+    except ValueError:
+        try:
+            # Ok, JSON couldn't be loaded, so we'll try the old way of using pickle
+            data = compathacks.pickle_load(f)
+        except:
+            # well, pickle didn't work either, so we reset the file pointer and
+            # read the string
+            f.seek(0)
+            data = f.read()
 
-    This is present because I kept corrupting my revmap by managing to hit ^C
-    during the pickle of that file.
-    """
-    f = hgutil.atomictempfile(file_path, 'w+b', 0644)
-    pickle.dump(data, f)
-    # Older versions of hg have .rename() instead of .close on
-    # atomictempfile.
-    if getattr(hgutil.atomictempfile, 'rename', False):
-        f.rename()
-    else:
+        # convert the file to json immediately
         f.close()
+        if resave:
+            dump(data, file_path)
+    return data
 
 def parseurl(url, heads=[]):
-    parsed = hg.parseurl(url, heads)
-    if len(parsed) == 3:
-        # old hg, remove when we can be 1.5-only
-        svn_url, heads, checkout = parsed
-    else:
-        svn_url, heads = parsed
-        if isinstance(heads, tuple) and len(heads) == 2:
-            # hg 1.6 or later
-            _junk, heads = heads
-        if heads:
-            checkout = heads[0]
-        else:
-            checkout = None
+    checkout = None
+    svn_url, (_junk, heads) = hg.parseurl(url, heads)
+    if heads:
+        checkout = heads[0]
     return svn_url, heads, checkout
 
 
@@ -325,9 +356,11 @@ def revset_fromsvn(repo, subset, x):
 
     rev = repo.changelog.rev
     bin = node.bin
+    meta = repo.svnmeta(skiperrorcheck=True)
     try:
         svnrevs = set(rev(bin(l.split(' ', 2)[1]))
-                      for l in maps.RevMap.readmapfile(repo, missingok=False))
+                      for l in maps.RevMap.readmapfile(meta.revmap_file,
+                                                       missingok=False))
         return filter(svnrevs.__contains__, subset)
     except IOError, err:
         if err.errno != errno.ENOENT:
@@ -350,8 +383,9 @@ def revset_svnrev(repo, subset, x):
 
     rev = rev + ' '
     revs = []
+    meta = repo.svnmeta(skiperrorcheck=True)
     try:
-        for l in maps.RevMap.readmapfile(repo, missingok=False):
+        for l in maps.RevMap.readmapfile(meta.revmap_file, missingok=False):
             if l.startswith(rev):
                 n = l.split(' ', 2)[1]
                 r = repo[node.bin(n)].rev()
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -2,6 +2,12 @@ from hgext import rebase as hgrebase
 
 from mercurial import cmdutil
 from mercurial import discovery
+try:
+    from mercurial import exchange
+    exchange.push  # existed in first iteration of this file
+except ImportError:
+    # We only *use* the exchange module in hg 3.2+, so this is safe
+    pass
 from mercurial import patch
 from mercurial import hg
 from mercurial import util as hgutil
@@ -97,7 +103,7 @@ def incoming(orig, ui, repo, origsource=
     meta = repo.svnmeta(svn.uuid, svn.subdir)
 
     ui.status('incoming changes from %s\n' % other.svnurl)
-    svnrevisions = list(svn.revisions(start=meta.revmap.youngest))
+    svnrevisions = list(svn.revisions(start=meta.lastpulled))
     if opts.get('newest_first'):
         svnrevisions.reverse()
     # Returns 0 if there are incoming changes, 1 otherwise.
@@ -181,7 +187,8 @@ def push(repo, dest, force, revs):
     checkpush = getattr(repo, 'checkpush', None)
     if checkpush:
         try:
-            # The checkpush function changed as of e10000369b47 in mercurial
+            # The checkpush function changed as of e10000369b47 (first
+            # in 3.0) in mercurial
             from mercurial.exchange import pushoperation
             pushop = pushoperation(repo, dest, force, revs, False)
             checkpush(pushop)
@@ -209,6 +216,7 @@ def push(repo, dest, force, revs):
             ui.status('Cowardly refusing to push branch merge\n')
             return 0 # results in nonzero exit status, see hg's commands.py
         workingrev = repo.parents()[0]
+        workingbranch = workingrev.branch()
         ui.status('searching for changes\n')
         hashes = meta.revmap.hashes()
         outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
@@ -272,9 +280,16 @@ def push(repo, dest, force, revs):
                         "in svn.\n" % current_ctx)
                 return
 
+            # This hook is here purely for testing.  It allows us to
+            # onsistently trigger hit the race condition between
+            # pushing and pulling here.  In particular, we use it to
+            # trigger another revision landing between the time we
+            # push a revision and pull it back.
+            repo.hook('debug-hgsubversion-between-push-and-pull-for-tests')
+
             # 5. Pull the latest changesets from subversion, which will
             # include the one we just committed (and possibly others).
-            r = repo.pull(dest, force=force)
+            r = pull(repo, dest, force=force)
             assert not r or r == 0
             meta = repo.svnmeta(svn.uuid, svn.subdir)
             hashes = meta.revmap.hashes()
@@ -324,7 +339,7 @@ def push(repo, dest, force, revs):
 
         util.swap_out_encoding(old_encoding)
         try:
-            hg.update(repo, repo['tip'].node())
+            hg.update(repo, repo.branchtip(workingbranch))
         finally:
             util.swap_out_encoding()
 
@@ -353,6 +368,16 @@ def push(repo, dest, force, revs):
             util.swap_out_encoding(old_encoding)
     return 1 # so we get a sane exit status, see hg's commands.push
 
+def exchangepush(orig, repo, remote, force=False, revs=None, newbranch=False,
+                 bookmarks=()):
+    capable = getattr(remote, 'capable', lambda x: False)
+    if capable('subversion'):
+        pushop = exchange.pushoperation(repo, remote, force, revs, newbranch,
+                                        bookmarks=bookmarks)
+        pushop.cgresult = push(repo, remote, force, revs)
+        return pushop
+    else:
+        return orig(repo, remote, force, revs, newbranch, bookmarks=bookmarks)
 
 def pull(repo, source, heads=[], force=False):
     """pull new revisions from Subversion"""
@@ -390,7 +415,7 @@ def pull(repo, source, heads=[], force=F
             meta.branchmap['default'] = branch
 
         ui = repo.ui
-        start = meta.revmap.youngest
+        start = meta.lastpulled
         origrevcount = len(meta.revmap)
 
         if start <= 0:
@@ -484,7 +509,7 @@ def pull(repo, source, heads=[], force=F
         util.swap_out_encoding(old_encoding)
 
     if lastpulled is not None:
-        meta.revmap.youngest = lastpulled
+        meta.lastpulled = lastpulled
     revisions = len(meta.revmap) - oldrevisions
 
     if revisions == 0:
@@ -493,6 +518,19 @@ def pull(repo, source, heads=[], force=F
     else:
         ui.status("pulled %d revisions\n" % revisions)
 
+def exchangepull(orig, repo, remote, heads=None, force=False, bookmarks=()):
+    capable = getattr(remote, 'capable', lambda x: False)
+    if capable('subversion'):
+        pullop = exchange.pulloperation(repo, remote, heads, force,
+                                        bookmarks=bookmarks)
+        try:
+            pullop.cgresult = pull(repo, remote, heads, force)
+            return pullop
+        finally:
+            pullop.releasetransaction()
+    else:
+        return orig(repo, remote, heads, force, bookmarks=bookmarks)
+
 def rebase(orig, ui, repo, **opts):
     """rebase current unpushed revisions onto the Subversion head
 
@@ -573,11 +611,7 @@ def clone(orig, ui, source, dest=None, *
 
     data = {}
     def hgclonewrapper(orig, ui, *args, **opts):
-        if getattr(hg, 'peer', None):
-            # Since 1.9 (d976542986d2)
-            origsource = args[1]
-        else:
-            origsource = args[0]
+        origsource = args[1]
 
         if isinstance(origsource, str):
             source, branch, checkout = util.parseurl(ui.expandpath(origsource),
@@ -612,14 +646,11 @@ def clone(orig, ui, source, dest=None, *
 
     dstrepo = data.get('dstrepo')
     srcrepo = data.get('srcrepo')
+    dst = dstrepo.local()
 
     if dstrepo.local() and srcrepo.capable('subversion'):
         dst = dstrepo.local()
-        if isinstance(dst, bool):
-            # Apparently <= hg@1.9
-            fd = dstrepo.opener("hgrc", "a", text=True)
-        else:
-            fd = dst.opener("hgrc", "a", text=True)
+        fd = dst.opener("hgrc", "a", text=True)
         preservesections = set(s for s, v in optionmap.itervalues())
         preservesections |= extrasections
         for section in preservesections:
--- a/tests/comprehensive/test_custom_layout.py
+++ b/tests/comprehensive/test_custom_layout.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
--- a/tests/comprehensive/test_rebuildmeta.py
+++ b/tests/comprehensive/test_rebuildmeta.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import unittest
 import sys
 
@@ -14,11 +13,14 @@ except ImportError:
 from mercurial import context
 from mercurial import extensions
 from mercurial import hg
+from mercurial import localrepo
 from mercurial import ui
+from mercurial import util as hgutil
 
 from hgsubversion import compathacks
 from hgsubversion import svncommands
 from hgsubversion import svnmeta
+from hgsubversion import util
 
 # These test repositories have harmless skew in rebuildmeta for the
 # last-pulled-rev because the last rev in svn causes absolutely no
@@ -85,7 +87,12 @@ def _do_case(self, name, layout):
         # remove the wrapper
         context.changectx.children = origchildren
 
-    dest.pull(src)
+    if hgutil.safehasattr(localrepo.localrepository, 'pull'):
+        dest.pull(src)
+    else:
+        # Mercurial >= 3.2
+        from mercurial import exchange
+        exchange.pull(dest, src)
 
     # insert a wrapper that prevents calling changectx.children()
     extensions.wrapfunction(context.changectx, 'children', failfn)
@@ -110,23 +117,29 @@ def _run_assertions(self, name, single, 
     for tf in ('lastpulled', 'rev_map', 'uuid', 'tagmap', 'layout', 'subdir',):
 
         stf = os.path.join(src.path, 'svn', tf)
-        self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf)
+        # the generation of tagmap is lazy so it doesn't strictly need to exist
+        # if it's not being used
+        if not stf.endswith('tagmap'):
+            self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf)
         dtf = os.path.join(dest.path, 'svn', tf)
-        self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf)
-        old, new = open(stf).read(), open(dtf).read()
+        old, new = None, None
+        if not dtf.endswith('tagmap'):
+            self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf)
+        if os.path.isfile(stf) and os.path.isfile(dtf):
+            old, new = util.load(stf, resave=False), util.load(dtf, resave=False)
         if tf == 'lastpulled' and (name,
                                    self.stupid, single) in expect_youngest_skew:
             self.assertNotEqual(old, new,
                                 'rebuildmeta unexpected match on youngest rev!')
             continue
-        self.assertMultiLineEqual(old, new, tf + ' differs')
+        self.assertEqual(old, new, tf + ' differs')
         try:
           self.assertEqual(src.branchmap(), dest.branchmap())
         except AttributeError:
           # hg 2.8 and earlier
           self.assertEqual(src.branchtags(), dest.branchtags())
-    srcbi = pickle.load(open(os.path.join(src.path, 'svn', 'branch_info')))
-    destbi = pickle.load(open(os.path.join(dest.path, 'svn', 'branch_info')))
+    srcbi = util.load(os.path.join(src.path, 'svn', 'branch_info'))
+    destbi = util.load(os.path.join(dest.path, 'svn', 'branch_info'))
     self.assertEqual(sorted(srcbi.keys()), sorted(destbi.keys()))
     revkeys = svnmeta.SVNMeta(dest).revmap.keys()
     for branch in destbi:
--- a/tests/comprehensive/test_stupid_pull.py
+++ b/tests/comprehensive/test_stupid_pull.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
--- a/tests/comprehensive/test_verify_and_startrev.py
+++ b/tests/comprehensive/test_verify_and_startrev.py
@@ -1,5 +1,4 @@
 import os
-import pickle
 import sys
 import unittest
 
new file mode 100755
--- /dev/null
+++ b/tests/fixtures/copyafterclose.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+rm -rf temp
+mkdir temp
+cd temp
+svnadmin create repo
+repo=file://`pwd`/repo
+svn co $repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+
+cd trunk
+echo trunk1 > file
+mkdir dir
+echo trunk1 > dir/file
+svn add file dir
+svn ci -m 'Add file and dir.'
+cd ..
+svn up
+
+svn cp trunk branches/test
+svn ci -m 'Branch.'
+svn up
+
+cd branches/test/
+echo branch1 > file
+echo branch1 > dir/file
+svn ci -m 'edit on branch.'
+cd ../../
+svn up
+
+cd trunk
+echo trunk2 > file
+echo trunk2 > dir/file
+svn ci -m 'edit on trunk'
+cd ..
+svn up
+
+svn rm trunk
+svn ci -m 'Close trunk.'
+svn up
+
+cd branches/test
+svn rm file
+svn cp $repo/trunk/file@5 file
+svn rm dir
+svn cp $repo/trunk/dir@5 dir
+svn ci -m 'copy from trunk before close'
+cd ../..
+svn up
+
+cd ../..
+svnadmin dump temp/repo > copyafterclose.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in copyafterclose.svndump'
+exit 0
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/copyafterclose.svndump
@@ -0,0 +1,285 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 288797d9-b527-4683-aa49-2eb9e084ffad
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.334418Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 108
+Content-length: 108
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.393547Z
+K 7
+svn:log
+V 3
+btt
+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: 123
+Content-length: 123
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.442353Z
+K 7
+svn:log
+V 17
+Add file and dir.
+PROPS-END
+
+Node-path: trunk/dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/dir/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: 5f2a436c7d4aa15dfbdca7b303fcae35
+Text-content-sha1: 391157987cb6fefff86fd89353356611ea621906
+Content-length: 17
+
+PROPS-END
+trunk1
+
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: 5f2a436c7d4aa15dfbdca7b303fcae35
+Text-content-sha1: 391157987cb6fefff86fd89353356611ea621906
+Content-length: 17
+
+PROPS-END
+trunk1
+
+
+Revision-number: 3
+Prop-content-length: 112
+Content-length: 112
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.504478Z
+K 7
+svn:log
+V 7
+Branch.
+PROPS-END
+
+Node-path: branches/test
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 121
+Content-length: 121
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.549176Z
+K 7
+svn:log
+V 15
+edit on branch.
+PROPS-END
+
+Node-path: branches/test/dir/file
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: ed787ace107676c1dfcced2ae527df92
+Text-content-sha1: b8486c4feca589a4237a1ee428322d7109ede12e
+Content-length: 8
+
+branch1
+
+
+Node-path: branches/test/file
+Node-kind: file
+Node-action: change
+Text-content-length: 8
+Text-content-md5: ed787ace107676c1dfcced2ae527df92
+Text-content-sha1: b8486c4feca589a4237a1ee428322d7109ede12e
+Content-length: 8
+
+branch1
+
+
+Revision-number: 5
+Prop-content-length: 119
+Content-length: 119
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.600193Z
+K 7
+svn:log
+V 13
+edit on trunk
+PROPS-END
+
+Node-path: trunk/dir/file
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 28d0a7e7ef2864416b7a9398623e4d09
+Text-content-sha1: 91454e2d3487f712490f17481157e389c11a6fe0
+Content-length: 7
+
+trunk2
+
+
+Node-path: trunk/file
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 28d0a7e7ef2864416b7a9398623e4d09
+Text-content-sha1: 91454e2d3487f712490f17481157e389c11a6fe0
+Content-length: 7
+
+trunk2
+
+
+Revision-number: 6
+Prop-content-length: 118
+Content-length: 118
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.650888Z
+K 7
+svn:log
+V 12
+Close trunk.
+PROPS-END
+
+Node-path: trunk
+Node-action: delete
+
+
+Revision-number: 7
+Prop-content-length: 134
+Content-length: 134
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-03T22:42:41.757761Z
+K 7
+svn:log
+V 28
+copy from trunk before close
+PROPS-END
+
+Node-path: branches/test/dir
+Node-kind: dir
+Node-action: delete
+
+Node-path: branches/test/dir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk/dir
+
+
+
+
+Node-path: branches/test/file
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/test/file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk/file
+Text-copy-source-md5: 28d0a7e7ef2864416b7a9398623e4d09
+Text-copy-source-sha1: 91454e2d3487f712490f17481157e389c11a6fe0
+
+
+
+
--- a/tests/fixtures/renames.sh
+++ b/tests/fixtures/renames.sh
@@ -3,6 +3,10 @@
 # Generate renames.svndump
 #
 
+set -e
+
+rm -rf temp
+
 mkdir temp
 cd temp
 
@@ -21,82 +25,112 @@ cd project/trunk
 # Entries for regular tests
 echo a > a
 echo b > b
+ln -s a linka
+ln -s b linkb
 mkdir -p da/db
 echo c > da/daf
+ln -s daf da/dalink
 echo d > da/db/dbf
+ln -s ../daf da/db/dblink
 # Entries to test delete + copy
 echo deleted > deletedfile
+ln -s b deletedlink
 mkdir deleteddir
 echo deleteddir > deleteddir/f
+ln -s f deleteddir/link
 # Entries to test copy before change
 echo changed > changed
+ln -s changed changedlink
 mkdir changeddir
 echo changed2 > changeddir/f
+ln -s f changeddir/link
 # Entries unchanged in the rest of history
 echo unchanged > unchanged
+ln -s unchanged unchangedlink
 mkdir unchangeddir
 echo unchanged2 > unchangeddir/f
+ln -s f unchangeddir/link
 # One of the files will be changed afterwards, to test
 # group copies detection
 mkdir groupdir
 echo a > groupdir/a
 echo b > groupdir/b
-svn add a b da deletedfile deleteddir changed changeddir unchanged unchangeddir groupdir
-svn ci -m "add a and b"
+ln -s a groupdir/linka
+ln -s b groupdir/linkb
+svn add a b linka linkb da deleted* changed* unchanged* groupdir
+svn ci -m "add everything"
 # Remove files to be copied later
 svn rm deletedfile
 svn rm deleteddir
+svn rm deletedlink
 # Update files to be copied before this change
 echo changed >> changed
 echo changed2 >> changeddir/f
+ln -sfn changeddir/f changedlink
+ln -sfn ../changed changeddir/link
 # Update one of the groupdir files
 echo a >> groupdir/a
+ln -sfn ../a groupdir/linka
 svn ci -m "delete files and dirs"
 cd ../branches
 svn cp ../trunk branch1
 svn ci -m "create branch1"
 cd branch1
 echo c > c
-svn add c
-svn ci -m "add c"
+ln -s c linkc
+svn add c linkc
+svn ci -m "add c and linkc"
 cd ../../trunk
 # Regular copy and rename
 svn cp a a1
+svn cp linka linka1
 svn mv a a2
+svn mv linka linka2
 # Copy and update of source and dest
 svn cp b b1
+svn cp linkb linkb1
 echo b >> b
 echo c >> b1
+ln -sfn bb linkb
+ln -sfn bc linkb1
 # Directory copy and renaming
 svn cp da da1
 svn mv da da2
 # Test one copy operation in branch
 cd ../branches/branch1
 svn cp c c1
+svn cp linkc linkc1
 echo c >> c1
+ln -sfn cc linkc1
 cd ../..
-svn ci -m "rename and copy a, b and da"
+svn ci -m "rename and copy a, b, c and da, plus their links"
 cd trunk
 # Copy across branch
 svn cp ../branches/branch1/c c
-svn ci -m "copy b from branch1"
+svn cp ../branches/branch1/linkc linkc
+svn ci -m "copy c from branch1"
 # Copy deleted stuff from the past
 svn cp $svnurl/trunk/deletedfile@2 deletedfile
 svn cp $svnurl/trunk/deleteddir@2 deleteddir
+svn cp $svnurl/trunk/deletedlink@2 deletedlink
 svn ci -m "copy stuff from the past"
 # Copy data from the past before it was changed
 svn cp $svnurl/trunk/changed@2 changed2
 svn cp $svnurl/trunk/changeddir@2 changeddir2
+svn cp $svnurl/trunk/changedlink@2 changedlink2
 # Harder, copy from the past before change and change it again
 # This confused the stupid diff path
 svn cp $svnurl/trunk/changed@2 changed3
+svn cp $svnurl/trunk/changedlink@2 changedlink3
 echo changed3 >> changed3
+ln -sfn changed3 changedlink3
 svn ci -m "copy stuff from the past before change"
 # Copy unchanged stuff from the past. Since no changed occured in these files
 # between the source and parent revision, we record them as copy from parent
 # instead of source rev.
 svn cp $svnurl/trunk/unchanged@2 unchanged2
 svn cp $svnurl/trunk/unchangeddir@2 unchangeddir2
+svn cp $svnurl/trunk/unchangedlink@2 unchangedlink2
 svn ci -m "copy unchanged stuff from the past"
 # Copy groupdir, unfortunately one file was changed after r2 so the
 # copy should not be recorded at all
--- a/tests/fixtures/renames.svndump
+++ b/tests/fixtures/renames.svndump
@@ -1,6 +1,6 @@
 SVN-fs-dump-format-version: 2
 
-UUID: 113560bd-ec36-42a6-acef-e4688a33b129
+UUID: e86bec6c-370f-405d-b639-abb48bba3962
 
 Revision-number: 0
 Prop-content-length: 56
@@ -9,25 +9,25 @@ Content-length: 56
 K 8
 svn:date
 V 27
-2008-12-05T22:48:38.139917Z
+2014-04-07T22:34:17.074854Z
 PROPS-END
 
 Revision-number: 1
-Prop-content-length: 114
-Content-length: 114
+Prop-content-length: 118
+Content-length: 118
 
-K 7
-svn:log
-V 12
-init project
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:38.525864Z
+2014-04-07T22:34:17.214759Z
+K 7
+svn:log
+V 12
+init project
 PROPS-END
 
 Node-path: branches
@@ -49,21 +49,21 @@ PROPS-END
 
 
 Revision-number: 2
-Prop-content-length: 113
-Content-length: 113
+Prop-content-length: 120
+Content-length: 120
 
-K 7
-svn:log
-V 11
-add a and b
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:39.313010Z
+2014-04-07T22:34:17.391940Z
+K 7
+svn:log
+V 14
+add everything
 PROPS-END
 
 Node-path: trunk/a
@@ -72,6 +72,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 Content-length: 12
 
 PROPS-END
@@ -84,6 +85,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
 Content-length: 12
 
 PROPS-END
@@ -96,6 +98,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 8
 Text-content-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-content-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
 Content-length: 18
 
 PROPS-END
@@ -117,12 +120,45 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 9
 Text-content-md5: 2dfdfd8492a2c558ec838d69f73f5f6b
+Text-content-sha1: fc7acf217b976525893922a9ed1bb3c3ab24f3a9
 Content-length: 19
 
 PROPS-END
 changed2
 
 
+Node-path: trunk/changeddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
+Node-path: trunk/changedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 12
+Text-content-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-content-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+Content-length: 45
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link changed
+
 Node-path: trunk/da
 Node-kind: dir
 Node-action: add
@@ -138,12 +174,29 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
 Content-length: 12
 
 PROPS-END
 c
 
 
+Node-path: trunk/da/dalink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 8
+Text-content-md5: 21af4beda4f4d197c0b1cecbf11543dc
+Text-content-sha1: 52f2276428bcb4cf45fefaf293521b5b3a26aa5f
+Content-length: 41
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link daf
+
 Node-path: trunk/da/db
 Node-kind: dir
 Node-action: add
@@ -159,12 +212,29 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Text-content-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff
 Content-length: 12
 
 PROPS-END
 d
 
 
+Node-path: trunk/da/db/dblink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 11
+Text-content-md5: 301198daf87f24796a8be0746389da42
+Text-content-sha1: af5485e6ea78867c36f7993542cbaadb570b79c8
+Content-length: 44
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link ../daf
+
 Node-path: trunk/deleteddir
 Node-kind: dir
 Node-action: add
@@ -180,24 +250,58 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 11
 Text-content-md5: 49b72b575e26ecddb296dd59b24c3e67
+Text-content-sha1: 02801293a2cd7e4c105239d34a3cfa4a4eb9c921
 Content-length: 21
 
 PROPS-END
 deleteddir
 
 
+Node-path: trunk/deleteddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
 Node-path: trunk/deletedfile
 Node-kind: file
 Node-action: add
 Prop-content-length: 10
 Text-content-length: 8
 Text-content-md5: 4d742b2f247bec99b41a60acbebc149a
+Text-content-sha1: 1ef5922542033869106719d682a87ed706af4ddd
 Content-length: 18
 
 PROPS-END
 deleted
 
 
+Node-path: trunk/deletedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
 Node-path: trunk/groupdir
 Node-kind: dir
 Node-action: add
@@ -213,6 +317,7 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 Content-length: 12
 
 PROPS-END
@@ -225,18 +330,84 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
 Content-length: 12
 
 PROPS-END
 b
 
 
+Node-path: trunk/groupdir/linka
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: c118dba188202a1efc975bef6064180b
+Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link a
+
+Node-path: trunk/groupdir/linkb
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
+Node-path: trunk/linka
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: c118dba188202a1efc975bef6064180b
+Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link a
+
+Node-path: trunk/linkb
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
 Node-path: trunk/unchanged
 Node-kind: file
 Node-action: add
 Prop-content-length: 10
 Text-content-length: 10
 Text-content-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-content-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
 Content-length: 20
 
 PROPS-END
@@ -258,28 +429,61 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 11
 Text-content-md5: a11092875079a002afb9ecef07f510e7
+Text-content-sha1: c18ebadf1cffd6a79e4b74c50474b3cf8d5cb32b
 Content-length: 21
 
 PROPS-END
 unchanged2
 
 
+Node-path: trunk/unchangeddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
+Node-path: trunk/unchangedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 14
+Text-content-md5: 1aa9c01278c74a273e3117dc42426153
+Text-content-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+Content-length: 47
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link unchanged
+
 Revision-number: 3
-Prop-content-length: 123
-Content-length: 123
+Prop-content-length: 127
+Content-length: 127
 
-K 7
-svn:log
-V 21
-delete files and dirs
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:40.224632Z
+2014-04-07T22:34:17.496578Z
+K 7
+svn:log
+V 21
+delete files and dirs
 PROPS-END
 
 Node-path: trunk/changed
@@ -287,6 +491,7 @@ Node-kind: file
 Node-action: change
 Text-content-length: 16
 Text-content-md5: 1725f40a29aad369a39b0f96c82d50f9
+Text-content-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf
 Content-length: 16
 
 changed
@@ -298,23 +503,55 @@ Node-kind: file
 Node-action: change
 Text-content-length: 18
 Text-content-md5: 984b8c4ab9193b7659b9f914897a949c
+Text-content-sha1: df588cfa0799c1d4447646645ff2799e23e59f57
 Content-length: 18
 
 changed2
 changed2
 
 
+Node-path: trunk/changeddir/link
+Node-kind: file
+Node-action: change
+Text-content-length: 15
+Text-content-md5: 19b47078b08789b4af5bc8d78b09f051
+Text-content-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f
+Content-length: 15
+
+link ../changed
+
+Node-path: trunk/changedlink
+Node-kind: file
+Node-action: change
+Text-content-length: 17
+Text-content-md5: 78d7d7c917f0f0355f01f23508cc0a0a
+Text-content-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd
+Content-length: 17
+
+link changeddir/f
+
 Node-path: trunk/groupdir/a
 Node-kind: file
 Node-action: change
 Text-content-length: 4
 Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
 Content-length: 4
 
 a
 a
 
 
+Node-path: trunk/groupdir/linka
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 37f1cfbed04f4354d8e706a1f1f626b6
+Text-content-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759
+Content-length: 9
+
+link ../a
+
 Node-path: trunk/deleteddir
 Node-action: delete
 
@@ -323,22 +560,26 @@ Node-path: trunk/deletedfile
 Node-action: delete
 
 
+Node-path: trunk/deletedlink
+Node-action: delete
+
+
 Revision-number: 4
-Prop-content-length: 116
-Content-length: 116
+Prop-content-length: 120
+Content-length: 120
 
-K 7
-svn:log
-V 14
-create branch1
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:42.184704Z
+2014-04-07T22:34:17.595148Z
+K 7
+svn:log
+V 14
+create branch1
 PROPS-END
 
 Node-path: branches/branch1
@@ -346,14 +587,6 @@ 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/branch1/a
@@ -362,6 +595,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/a
 Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 
 
 Node-path: branches/branch1/b
@@ -370,6 +604,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/b
 Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
 
 
 Node-path: branches/branch1/changed
@@ -378,6 +613,7 @@ Node-action: add
 Node-copyfrom-rev: 3
 Node-copyfrom-path: trunk/changed
 Text-copy-source-md5: 1725f40a29aad369a39b0f96c82d50f9
+Text-copy-source-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf
 
 
 Node-path: branches/branch1/changeddir
@@ -397,8 +633,33 @@ Node-action: add
 Node-copyfrom-rev: 3
 Node-copyfrom-path: trunk/changeddir/f
 Text-copy-source-md5: 984b8c4ab9193b7659b9f914897a949c
+Text-copy-source-sha1: df588cfa0799c1d4447646645ff2799e23e59f57
+
+
 
 
+Node-path: branches/branch1/changeddir/link
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/changeddir/link
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/changeddir/link
+Text-copy-source-md5: 19b47078b08789b4af5bc8d78b09f051
+Text-copy-source-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f
+
+
+
+
+Node-path: branches/branch1/changedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/changedlink
+Text-copy-source-md5: 78d7d7c917f0f0355f01f23508cc0a0a
+Text-copy-source-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd
 
 
 Node-path: branches/branch1/da
@@ -425,8 +686,42 @@ Node-action: add
 Node-copyfrom-rev: 3
 Node-copyfrom-path: trunk/groupdir/a
 Text-copy-source-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-copy-source-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+
+
+
+
+Node-path: branches/branch1/groupdir/linka
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/groupdir/linka
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/groupdir/linka
+Text-copy-source-md5: 37f1cfbed04f4354d8e706a1f1f626b6
+Text-copy-source-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759
+
+
+
+
+Node-path: branches/branch1/linka
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
 
 
+Node-path: branches/branch1/linkb
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/linkb
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
 
 
 Node-path: branches/branch1/unchanged
@@ -435,6 +730,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/unchanged
 Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
 
 
 Node-path: branches/branch1/unchangeddir
@@ -444,22 +740,31 @@ Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/unchangeddir
 
 
+Node-path: branches/branch1/unchangedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchangedlink
+Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153
+Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+
+
 Revision-number: 5
-Prop-content-length: 106
-Content-length: 106
+Prop-content-length: 121
+Content-length: 121
 
-K 7
-svn:log
-V 5
-add c
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:43.175723Z
+2014-04-07T22:34:17.655921Z
+K 7
+svn:log
+V 15
+add c and linkc
 PROPS-END
 
 Node-path: branches/branch1/c
@@ -468,28 +773,45 @@ Node-action: add
 Prop-content-length: 10
 Text-content-length: 2
 Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
 Content-length: 12
 
 PROPS-END
 c
 
 
+Node-path: branches/branch1/linkc
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: aaa83258c434079cc82a5b4868340dc0
+Text-content-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link c
+
 Revision-number: 6
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 154
+Content-length: 154
 
-K 7
-svn:log
-V 27
-rename and copy a, b and da
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:50.200094Z
+2014-04-07T22:34:17.901507Z
+K 7
+svn:log
+V 48
+rename and copy a, b, c and da, plus their links
 PROPS-END
 
 Node-path: branches/branch1/c1
@@ -498,34 +820,37 @@ Node-action: add
 Node-copyfrom-rev: 5
 Node-copyfrom-path: branches/branch1/c
 Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
-Prop-content-length: 34
+Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
 Text-content-length: 4
 Text-content-md5: 63fad9092ad37713ebe26b3193f89c41
-Content-length: 38
-
-K 13
-svn:mergeinfo
-V 0
+Text-content-sha1: ccfb93b7bac6f1520f0adc0eebc2cafe9da80f42
+Content-length: 4
 
-PROPS-END
 c
 c
 
 
+Node-path: branches/branch1/linkc1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/linkc
+Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0
+Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
+Text-content-length: 7
+Text-content-md5: ea7a177c3c3af680cf62010efe71275f
+Text-content-sha1: d780ef86a4c5016931861dc32373a1155755e404
+Content-length: 7
+
+link cc
+
 Node-path: trunk/a1
 Node-kind: file
 Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/a
 Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 
 
 Node-path: trunk/a2
@@ -534,14 +859,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/a
 Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
 
 
 Node-path: trunk/b
@@ -549,6 +867,7 @@ Node-kind: file
 Node-action: change
 Text-content-length: 4
 Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
+Text-content-sha1: f6980469e74f7125178e88ec571e06fe6ce86e95
 Content-length: 4
 
 b
@@ -561,16 +880,12 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/b
 Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
-Prop-content-length: 34
+Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
 Text-content-length: 4
 Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7
-Content-length: 38
-
-K 13
-svn:mergeinfo
-V 0
+Text-content-sha1: 7a6478264aa11a0f4befef356c03e83f2b1f6eba
+Content-length: 4
 
-PROPS-END
 b
 c
 
@@ -580,14 +895,6 @@ Node-kind: dir
 Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/da
-Prop-content-length: 34
-Content-length: 34
-
-K 13
-svn:mergeinfo
-V 0
-
-PROPS-END
 
 
 Node-path: trunk/da2
@@ -595,16 +902,50 @@ Node-kind: dir
 Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/da
-Prop-content-length: 34
-Content-length: 34
 
-K 13
-svn:mergeinfo
-V 0
 
-PROPS-END
+Node-path: trunk/linka1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
 
 
+Node-path: trunk/linka2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+
+
+Node-path: trunk/linkb
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 00b18251bf95a42453612a62619254c0
+Text-content-sha1: 8d00f006e36676f00d40c3935b6992cbb8349c2b
+Content-length: 7
+
+link bb
+
+Node-path: trunk/linkb1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/linkb
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
+Text-content-length: 7
+Text-content-md5: 1c9f17365658cf9ea7904568eff11f27
+Text-content-sha1: 163de098ef857862584154ca26e3d028fb34ba45
+Content-length: 7
+
+link bc
+
 Node-path: trunk/a
 Node-action: delete
 
@@ -613,22 +954,26 @@ Node-path: trunk/da
 Node-action: delete
 
 
+Node-path: trunk/linka
+Node-action: delete
+
+
 Revision-number: 7
-Prop-content-length: 121
-Content-length: 121
+Prop-content-length: 125
+Content-length: 125
 
-K 7
-svn:log
-V 19
-copy b from branch1
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:52.154125Z
+2014-04-07T22:34:17.969842Z
+K 7
+svn:log
+V 19
+copy c from branch1
 PROPS-END
 
 Node-path: trunk/c
@@ -637,32 +982,34 @@ Node-action: add
 Node-copyfrom-rev: 5
 Node-copyfrom-path: branches/branch1/c
 Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
-Prop-content-length: 34
-Content-length: 34
+Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
 
-K 13
-svn:mergeinfo
-V 0
 
-PROPS-END
+Node-path: trunk/linkc
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/linkc
+Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0
+Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
 
 
 Revision-number: 8
-Prop-content-length: 126
-Content-length: 126
+Prop-content-length: 130
+Content-length: 130
 
-K 7
-svn:log
-V 24
-copy stuff from the past
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:55.160439Z
+2014-04-07T22:34:18.068356Z
+K 7
+svn:log
+V 24
+copy stuff from the past
 PROPS-END
 
 Node-path: trunk/deleteddir
@@ -678,24 +1025,34 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/deletedfile
 Text-copy-source-md5: 4d742b2f247bec99b41a60acbebc149a
+Text-copy-source-sha1: 1ef5922542033869106719d682a87ed706af4ddd
+
+
+Node-path: trunk/deletedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/deletedlink
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
 
 
 Revision-number: 9
-Prop-content-length: 140
-Content-length: 140
+Prop-content-length: 144
+Content-length: 144
 
-K 7
-svn:log
-V 38
-copy stuff from the past before change
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:48:59.163460Z
+2014-04-07T22:34:18.201489Z
+K 7
+svn:log
+V 38
+copy stuff from the past before change
 PROPS-END
 
 Node-path: trunk/changed2
@@ -704,6 +1061,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/changed
 Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
 
 
 Node-path: trunk/changed3
@@ -712,8 +1070,10 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/changed
 Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
 Text-content-length: 17
 Text-content-md5: 7d93e8c4d61c2a7b05c20b7d8bf11f83
+Text-content-sha1: d78c3e7f04c44b599787ec534a3193357df1fa37
 Content-length: 17
 
 changed
@@ -727,22 +1087,45 @@ Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/changeddir
 
 
+Node-path: trunk/changedlink2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changedlink
+Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+
+
+Node-path: trunk/changedlink3
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changedlink
+Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+Text-content-length: 13
+Text-content-md5: 0ad81e7d7fc35e744e0ad3dea0f158fe
+Text-content-sha1: bae4618ecddda5e6ab6e5fa3b116f5d14dc3464b
+Content-length: 13
+
+link changed3
+
 Revision-number: 10
-Prop-content-length: 136
-Content-length: 136
+Prop-content-length: 140
+Content-length: 140
 
-K 7
-svn:log
-V 34
-copy unchanged stuff from the past
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:49:02.160163Z
+2014-04-07T22:34:18.298821Z
+K 7
+svn:log
+V 34
+copy unchanged stuff from the past
 PROPS-END
 
 Node-path: trunk/unchanged2
@@ -751,6 +1134,7 @@ Node-action: add
 Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/unchanged
 Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
 
 
 Node-path: trunk/unchangeddir2
@@ -760,22 +1144,31 @@ Node-copyfrom-rev: 2
 Node-copyfrom-path: trunk/unchangeddir
 
 
+Node-path: trunk/unchangedlink2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchangedlink
+Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153
+Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+
+
 Revision-number: 11
-Prop-content-length: 129
-Content-length: 129
+Prop-content-length: 133
+Content-length: 133
 
-K 7
-svn:log
-V 27
-copy groupdir from the past
 K 10
 svn:author
-V 7
-pmezard
+V 10
+dschleimer
 K 8
 svn:date
 V 27
-2008-12-05T22:49:04.157303Z
+2014-04-07T22:34:18.370254Z
+K 7
+svn:log
+V 27
+copy groupdir from the past
 PROPS-END
 
 Node-path: trunk/groupdir2
copy from tests/fixtures/renames.sh
copy to tests/fixtures/renames_with_prefix.sh
--- a/tests/fixtures/renames.sh
+++ b/tests/fixtures/renames_with_prefix.sh
@@ -1,102 +1,137 @@
 #!/bin/sh
 #
-# Generate renames.svndump
+# Generate renames_with_prefix.svndump
 #
 
+set -e
+
+rm -rf temp
+
 mkdir temp
 cd temp
 
-mkdir project-orig
-cd project-orig
+mkdir -p project-orig/prefix
+cd project-orig/prefix
 mkdir trunk
 mkdir branches
-cd ..
+cd ../..
 
 svnadmin create testrepo
 svnurl=file://`pwd`/testrepo
 svn import project-orig $svnurl -m "init project"
+svnurl=$svnurl/prefix
 
 svn co $svnurl project
 cd project/trunk
 # Entries for regular tests
 echo a > a
 echo b > b
+ln -s a linka
+ln -s b linkb
 mkdir -p da/db
 echo c > da/daf
+ln -s daf da/dalink
 echo d > da/db/dbf
+ln -s ../daf da/db/dblink
 # Entries to test delete + copy
 echo deleted > deletedfile
+ln -s b deletedlink
 mkdir deleteddir
 echo deleteddir > deleteddir/f
+ln -s f deleteddir/link
 # Entries to test copy before change
 echo changed > changed
+ln -s changed changedlink
 mkdir changeddir
 echo changed2 > changeddir/f
+ln -s f changeddir/link
 # Entries unchanged in the rest of history
 echo unchanged > unchanged
+ln -s unchanged unchangedlink
 mkdir unchangeddir
 echo unchanged2 > unchangeddir/f
+ln -s f unchangeddir/link
 # One of the files will be changed afterwards, to test
 # group copies detection
 mkdir groupdir
 echo a > groupdir/a
 echo b > groupdir/b
-svn add a b da deletedfile deleteddir changed changeddir unchanged unchangeddir groupdir
-svn ci -m "add a and b"
+ln -s a groupdir/linka
+ln -s b groupdir/linkb
+svn add a b linka linkb da deleted* changed* unchanged* groupdir
+svn ci -m "add everything"
 # Remove files to be copied later
 svn rm deletedfile
 svn rm deleteddir
+svn rm deletedlink
 # Update files to be copied before this change
 echo changed >> changed
 echo changed2 >> changeddir/f
+ln -sfn changeddir/f changedlink
+ln -sfn ../changed changeddir/link
 # Update one of the groupdir files
 echo a >> groupdir/a
+ln -sfn ../a groupdir/linka
 svn ci -m "delete files and dirs"
 cd ../branches
 svn cp ../trunk branch1
 svn ci -m "create branch1"
 cd branch1
 echo c > c
-svn add c
-svn ci -m "add c"
+ln -s c linkc
+svn add c linkc
+svn ci -m "add c and linkc"
 cd ../../trunk
 # Regular copy and rename
 svn cp a a1
+svn cp linka linka1
 svn mv a a2
+svn mv linka linka2
 # Copy and update of source and dest
 svn cp b b1
+svn cp linkb linkb1
 echo b >> b
 echo c >> b1
+ln -sfn bb linkb
+ln -sfn bc linkb1
 # Directory copy and renaming
 svn cp da da1
 svn mv da da2
 # Test one copy operation in branch
 cd ../branches/branch1
 svn cp c c1
+svn cp linkc linkc1
 echo c >> c1
+ln -sfn cc linkc1
 cd ../..
-svn ci -m "rename and copy a, b and da"
+svn ci -m "rename and copy a, b, c and da, plus their links"
 cd trunk
 # Copy across branch
 svn cp ../branches/branch1/c c
-svn ci -m "copy b from branch1"
+svn cp ../branches/branch1/linkc linkc
+svn ci -m "copy c from branch1"
 # Copy deleted stuff from the past
 svn cp $svnurl/trunk/deletedfile@2 deletedfile
 svn cp $svnurl/trunk/deleteddir@2 deleteddir
+svn cp $svnurl/trunk/deletedlink@2 deletedlink
 svn ci -m "copy stuff from the past"
 # Copy data from the past before it was changed
 svn cp $svnurl/trunk/changed@2 changed2
 svn cp $svnurl/trunk/changeddir@2 changeddir2
+svn cp $svnurl/trunk/changedlink@2 changedlink2
 # Harder, copy from the past before change and change it again
 # This confused the stupid diff path
 svn cp $svnurl/trunk/changed@2 changed3
+svn cp $svnurl/trunk/changedlink@2 changedlink3
 echo changed3 >> changed3
+ln -sfn changed3 changedlink3
 svn ci -m "copy stuff from the past before change"
 # Copy unchanged stuff from the past. Since no changed occured in these files
 # between the source and parent revision, we record them as copy from parent
 # instead of source rev.
 svn cp $svnurl/trunk/unchanged@2 unchanged2
 svn cp $svnurl/trunk/unchangeddir@2 unchangeddir2
+svn cp $svnurl/trunk/unchangedlink@2 unchangedlink2
 svn ci -m "copy unchanged stuff from the past"
 # Copy groupdir, unfortunately one file was changed after r2 so the
 # copy should not be recorded at all
@@ -104,4 +139,4 @@ svn cp $svnurl/trunk/groupdir@2 groupdir
 svn ci -m "copy groupdir from the past"
 cd ../..
 
-svnadmin dump testrepo > ../renames.svndump
+svnadmin dump testrepo > ../renames_with_prefix.svndump
new file mode 100644
--- /dev/null
+++ b/tests/fixtures/renames_with_prefix.svndump
@@ -0,0 +1,1189 @@
+SVN-fs-dump-format-version: 2
+
+UUID: ae30a990-0fd3-493e-b5d7-883bdd606745
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.118401Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 118
+Content-length: 118
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.138394Z
+K 7
+svn:log
+V 12
+init project
+PROPS-END
+
+Node-path: prefix
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 120
+Content-length: 120
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.304127Z
+K 7
+svn:log
+V 14
+add everything
+PROPS-END
+
+Node-path: prefix/trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: prefix/trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: prefix/trunk/changed
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-content-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
+Content-length: 18
+
+PROPS-END
+changed
+
+
+Node-path: prefix/trunk/changeddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/changeddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 2dfdfd8492a2c558ec838d69f73f5f6b
+Text-content-sha1: fc7acf217b976525893922a9ed1bb3c3ab24f3a9
+Content-length: 19
+
+PROPS-END
+changed2
+
+
+Node-path: prefix/trunk/changeddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
+Node-path: prefix/trunk/changedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 12
+Text-content-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-content-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+Content-length: 45
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link changed
+
+Node-path: prefix/trunk/da
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/da/daf
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: prefix/trunk/da/dalink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 8
+Text-content-md5: 21af4beda4f4d197c0b1cecbf11543dc
+Text-content-sha1: 52f2276428bcb4cf45fefaf293521b5b3a26aa5f
+Content-length: 41
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link daf
+
+Node-path: prefix/trunk/da/db
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/da/db/dbf
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Text-content-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff
+Content-length: 12
+
+PROPS-END
+d
+
+
+Node-path: prefix/trunk/da/db/dblink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 11
+Text-content-md5: 301198daf87f24796a8be0746389da42
+Text-content-sha1: af5485e6ea78867c36f7993542cbaadb570b79c8
+Content-length: 44
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link ../daf
+
+Node-path: prefix/trunk/deleteddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/deleteddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 11
+Text-content-md5: 49b72b575e26ecddb296dd59b24c3e67
+Text-content-sha1: 02801293a2cd7e4c105239d34a3cfa4a4eb9c921
+Content-length: 21
+
+PROPS-END
+deleteddir
+
+
+Node-path: prefix/trunk/deleteddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
+Node-path: prefix/trunk/deletedfile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 4d742b2f247bec99b41a60acbebc149a
+Text-content-sha1: 1ef5922542033869106719d682a87ed706af4ddd
+Content-length: 18
+
+PROPS-END
+deleted
+
+
+Node-path: prefix/trunk/deletedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
+Node-path: prefix/trunk/groupdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/groupdir/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: prefix/trunk/groupdir/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: prefix/trunk/groupdir/linka
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: c118dba188202a1efc975bef6064180b
+Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link a
+
+Node-path: prefix/trunk/groupdir/linkb
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
+Node-path: prefix/trunk/linka
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: c118dba188202a1efc975bef6064180b
+Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link a
+
+Node-path: prefix/trunk/linkb
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link b
+
+Node-path: prefix/trunk/unchanged
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 10
+Text-content-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-content-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
+Content-length: 20
+
+PROPS-END
+unchanged
+
+
+Node-path: prefix/trunk/unchangeddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: prefix/trunk/unchangeddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 11
+Text-content-md5: a11092875079a002afb9ecef07f510e7
+Text-content-sha1: c18ebadf1cffd6a79e4b74c50474b3cf8d5cb32b
+Content-length: 21
+
+PROPS-END
+unchanged2
+
+
+Node-path: prefix/trunk/unchangeddir/link
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: af3f1e8f8fa51f08e4985bda241ee7b8
+Text-content-sha1: f11a0ea0293755a1ec59d29628130cf3fcd3ec1c
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link f
+
+Node-path: prefix/trunk/unchangedlink
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 14
+Text-content-md5: 1aa9c01278c74a273e3117dc42426153
+Text-content-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+Content-length: 47
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link unchanged
+
+Revision-number: 3
+Prop-content-length: 127
+Content-length: 127
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.400275Z
+K 7
+svn:log
+V 21
+delete files and dirs
+PROPS-END
+
+Node-path: prefix/trunk/changed
+Node-kind: file
+Node-action: change
+Text-content-length: 16
+Text-content-md5: 1725f40a29aad369a39b0f96c82d50f9
+Text-content-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf
+Content-length: 16
+
+changed
+changed
+
+
+Node-path: prefix/trunk/changeddir/f
+Node-kind: file
+Node-action: change
+Text-content-length: 18
+Text-content-md5: 984b8c4ab9193b7659b9f914897a949c
+Text-content-sha1: df588cfa0799c1d4447646645ff2799e23e59f57
+Content-length: 18
+
+changed2
+changed2
+
+
+Node-path: prefix/trunk/changeddir/link
+Node-kind: file
+Node-action: change
+Text-content-length: 15
+Text-content-md5: 19b47078b08789b4af5bc8d78b09f051
+Text-content-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f
+Content-length: 15
+
+link ../changed
+
+Node-path: prefix/trunk/changedlink
+Node-kind: file
+Node-action: change
+Text-content-length: 17
+Text-content-md5: 78d7d7c917f0f0355f01f23508cc0a0a
+Text-content-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd
+Content-length: 17
+
+link changeddir/f
+
+Node-path: prefix/trunk/groupdir/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
+Node-path: prefix/trunk/groupdir/linka
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 37f1cfbed04f4354d8e706a1f1f626b6
+Text-content-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759
+Content-length: 9
+
+link ../a
+
+Node-path: prefix/trunk/deleteddir
+Node-action: delete
+
+
+Node-path: prefix/trunk/deletedfile
+Node-action: delete
+
+
+Node-path: prefix/trunk/deletedlink
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 120
+Content-length: 120
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.494631Z
+K 7
+svn:log
+V 14
+create branch1
+PROPS-END
+
+Node-path: prefix/branches/branch1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: prefix/trunk
+
+
+Node-path: prefix/branches/branch1/a
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+
+
+Node-path: prefix/branches/branch1/b
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/b
+Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+
+
+Node-path: prefix/branches/branch1/changed
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/changed
+Text-copy-source-md5: 1725f40a29aad369a39b0f96c82d50f9
+Text-copy-source-sha1: bd7078ed7302005a46b0f32b08cb81406df5d5cf
+
+
+Node-path: prefix/branches/branch1/changeddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changeddir
+
+
+Node-path: prefix/branches/branch1/changeddir/f
+Node-kind: file
+Node-action: delete
+
+Node-path: prefix/branches/branch1/changeddir/f
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/changeddir/f
+Text-copy-source-md5: 984b8c4ab9193b7659b9f914897a949c
+Text-copy-source-sha1: df588cfa0799c1d4447646645ff2799e23e59f57
+
+
+
+
+Node-path: prefix/branches/branch1/changeddir/link
+Node-kind: file
+Node-action: delete
+
+Node-path: prefix/branches/branch1/changeddir/link
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/changeddir/link
+Text-copy-source-md5: 19b47078b08789b4af5bc8d78b09f051
+Text-copy-source-sha1: 697d35a9a0857889666f1cae1baa9e294b4cf36f
+
+
+
+
+Node-path: prefix/branches/branch1/changedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/changedlink
+Text-copy-source-md5: 78d7d7c917f0f0355f01f23508cc0a0a
+Text-copy-source-sha1: 6d7057bfb5ba8dffc0184f491e4fa43ec1904cdd
+
+
+Node-path: prefix/branches/branch1/da
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/da
+
+
+Node-path: prefix/branches/branch1/groupdir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/groupdir
+
+
+Node-path: prefix/branches/branch1/groupdir/a
+Node-kind: file
+Node-action: delete
+
+Node-path: prefix/branches/branch1/groupdir/a
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/groupdir/a
+Text-copy-source-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-copy-source-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+
+
+
+
+Node-path: prefix/branches/branch1/groupdir/linka
+Node-kind: file
+Node-action: delete
+
+Node-path: prefix/branches/branch1/groupdir/linka
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: prefix/trunk/groupdir/linka
+Text-copy-source-md5: 37f1cfbed04f4354d8e706a1f1f626b6
+Text-copy-source-sha1: 7e368116b09c906ec1b989cefe328fd6dec4f759
+
+
+
+
+Node-path: prefix/branches/branch1/linka
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+
+
+Node-path: prefix/branches/branch1/linkb
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/linkb
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
+
+
+Node-path: prefix/branches/branch1/unchanged
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchanged
+Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
+
+
+Node-path: prefix/branches/branch1/unchangeddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchangeddir
+
+
+Node-path: prefix/branches/branch1/unchangedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchangedlink
+Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153
+Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+
+
+Revision-number: 5
+Prop-content-length: 121
+Content-length: 121
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.558149Z
+K 7
+svn:log
+V 15
+add c and linkc
+PROPS-END
+
+Node-path: prefix/branches/branch1/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: prefix/branches/branch1/linkc
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 6
+Text-content-md5: aaa83258c434079cc82a5b4868340dc0
+Text-content-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
+Content-length: 39
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link c
+
+Revision-number: 6
+Prop-content-length: 154
+Content-length: 154
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.742494Z
+K 7
+svn:log
+V 48
+rename and copy a, b, c and da, plus their links
+PROPS-END
+
+Node-path: prefix/branches/branch1/c1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: prefix/branches/branch1/c
+Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+Text-content-length: 4
+Text-content-md5: 63fad9092ad37713ebe26b3193f89c41
+Text-content-sha1: ccfb93b7bac6f1520f0adc0eebc2cafe9da80f42
+Content-length: 4
+
+c
+c
+
+
+Node-path: prefix/branches/branch1/linkc1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: prefix/branches/branch1/linkc
+Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0
+Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
+Text-content-length: 7
+Text-content-md5: ea7a177c3c3af680cf62010efe71275f
+Text-content-sha1: d780ef86a4c5016931861dc32373a1155755e404
+Content-length: 7
+
+link cc
+
+Node-path: prefix/trunk/a1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+
+
+Node-path: prefix/trunk/a2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+Text-copy-source-sha1: 3f786850e387550fdab836ed7e6dc881de23001b
+
+
+Node-path: prefix/trunk/b
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
+Text-content-sha1: f6980469e74f7125178e88ec571e06fe6ce86e95
+Content-length: 4
+
+b
+b
+
+
+Node-path: prefix/trunk/b1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/b
+Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+Text-copy-source-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b
+Text-content-length: 4
+Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7
+Text-content-sha1: 7a6478264aa11a0f4befef356c03e83f2b1f6eba
+Content-length: 4
+
+b
+c
+
+
+Node-path: prefix/trunk/da1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/da
+
+
+Node-path: prefix/trunk/da2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/da
+
+
+Node-path: prefix/trunk/linka1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+
+
+Node-path: prefix/trunk/linka2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/linka
+Text-copy-source-md5: c118dba188202a1efc975bef6064180b
+Text-copy-source-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+
+
+Node-path: prefix/trunk/linkb
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 00b18251bf95a42453612a62619254c0
+Text-content-sha1: 8d00f006e36676f00d40c3935b6992cbb8349c2b
+Content-length: 7
+
+link bb
+
+Node-path: prefix/trunk/linkb1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/linkb
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
+Text-content-length: 7
+Text-content-md5: 1c9f17365658cf9ea7904568eff11f27
+Text-content-sha1: 163de098ef857862584154ca26e3d028fb34ba45
+Content-length: 7
+
+link bc
+
+Node-path: prefix/trunk/a
+Node-action: delete
+
+
+Node-path: prefix/trunk/da
+Node-action: delete
+
+
+Node-path: prefix/trunk/linka
+Node-action: delete
+
+
+Revision-number: 7
+Prop-content-length: 125
+Content-length: 125
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.807910Z
+K 7
+svn:log
+V 19
+copy c from branch1
+PROPS-END
+
+Node-path: prefix/trunk/c
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: prefix/branches/branch1/c
+Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Text-copy-source-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69
+
+
+Node-path: prefix/trunk/linkc
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: prefix/branches/branch1/linkc
+Text-copy-source-md5: aaa83258c434079cc82a5b4868340dc0
+Text-copy-source-sha1: d347f561790aa523b963fc1714c64e1d158cc5d7
+
+
+Revision-number: 8
+Prop-content-length: 130
+Content-length: 130
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:22.895608Z
+K 7
+svn:log
+V 24
+copy stuff from the past
+PROPS-END
+
+Node-path: prefix/trunk/deleteddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/deleteddir
+
+
+Node-path: prefix/trunk/deletedfile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/deletedfile
+Text-copy-source-md5: 4d742b2f247bec99b41a60acbebc149a
+Text-copy-source-sha1: 1ef5922542033869106719d682a87ed706af4ddd
+
+
+Node-path: prefix/trunk/deletedlink
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/deletedlink
+Text-copy-source-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-copy-source-sha1: 7325442a5f7383205e66db563025d51535883784
+
+
+Revision-number: 9
+Prop-content-length: 144
+Content-length: 144
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:23.017668Z
+K 7
+svn:log
+V 38
+copy stuff from the past before change
+PROPS-END
+
+Node-path: prefix/trunk/changed2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changed
+Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
+
+
+Node-path: prefix/trunk/changed3
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changed
+Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-copy-source-sha1: 2f6933b5ee0f5fdd823d9717d8729f3c2523811b
+Text-content-length: 17
+Text-content-md5: 7d93e8c4d61c2a7b05c20b7d8bf11f83
+Text-content-sha1: d78c3e7f04c44b599787ec534a3193357df1fa37
+Content-length: 17
+
+changed
+changed3
+
+
+Node-path: prefix/trunk/changeddir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changeddir
+
+
+Node-path: prefix/trunk/changedlink2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changedlink
+Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+
+
+Node-path: prefix/trunk/changedlink3
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/changedlink
+Text-copy-source-md5: d91fb1e1062e62a17f97b44932d454c4
+Text-copy-source-sha1: 8c147187742f58ed0cd8707ddd0c0942fe8b2616
+Text-content-length: 13
+Text-content-md5: 0ad81e7d7fc35e744e0ad3dea0f158fe
+Text-content-sha1: bae4618ecddda5e6ab6e5fa3b116f5d14dc3464b
+Content-length: 13
+
+link changed3
+
+Revision-number: 10
+Prop-content-length: 140
+Content-length: 140
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:23.111440Z
+K 7
+svn:log
+V 34
+copy unchanged stuff from the past
+PROPS-END
+
+Node-path: prefix/trunk/unchanged2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchanged
+Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+Text-copy-source-sha1: c654a5435f170cfad37e136fee9672ecc40afd4a
+
+
+Node-path: prefix/trunk/unchangeddir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchangeddir
+
+
+Node-path: prefix/trunk/unchangedlink2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/unchangedlink
+Text-copy-source-md5: 1aa9c01278c74a273e3117dc42426153
+Text-copy-source-sha1: 3f1a6ab03b820f0baab56c083f77bd9279db2486
+
+
+Revision-number: 11
+Prop-content-length: 133
+Content-length: 133
+
+K 10
+svn:author
+V 10
+dschleimer
+K 8
+svn:date
+V 27
+2014-04-08T01:02:23.179096Z
+K 7
+svn:log
+V 27
+copy groupdir from the past
+PROPS-END
+
+Node-path: prefix/trunk/groupdir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: prefix/trunk/groupdir
+
+
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -50,9 +50,7 @@ class TestFetchBranches(test_util.TestBa
         self.assertEqual(['a', 'c', 'z'], sorted(r.manifest()))
 
     def test_renamed_branch_to_trunk(self):
-        config = {'hgsubversion.failonmissing': 'true'}
-        repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump',
-                                            config=config)
+        repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump')
         self.assertEqual(repo['default'].parents()[0].branch(), 'dev_branch')
         self.assert_('iota' in repo['default'])
         self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default')
@@ -73,6 +71,15 @@ class TestFetchBranches(test_util.TestBa
         self.assertEqual(repo['test'].extra().get('close'), '1')
         self.assertEqual(repo['test']['b'].data(), 'a\n')
 
+    def test_copyafterclose(self):
+        repo = self._load_fixture_and_fetch('copyafterclose.svndump')
+        self.assertEqual(repo['tip'].branch(), 'test')
+        self.assert_('file' in repo['test'])
+        self.assertEqual(repo['test']['file'].data(), 'trunk2\n')
+        self.assert_('dir/file' in repo['test'])
+        self.assertEqual(repo['test']['dir/file'].data(), 'trunk2\n')
+
+
     def test_branch_create_with_dir_delete_works(self):
         repo = self._load_fixture_and_fetch('branch_create_with_dir_delete.svndump')
         self.assertEqual(repo['tip'].manifest().keys(),
--- a/tests/test_fetch_mappings.py
+++ b/tests/test_fetch_mappings.py
@@ -84,10 +84,15 @@ class MapTests(test_util.TestBase):
     def test_author_map_no_overwrite(self):
         cwd = os.path.dirname(__file__)
         orig = os.path.join(cwd, 'fixtures', 'author-map-test.txt')
-        new = open(self.authors, 'w')
+        # create a fake hgsubversion repo
+        repopath = os.path.join(self.wc_path, '.hg')
+        repopath = os.path.join(repopath, 'svn')
+        if not os.path.isdir(repopath):
+            os.makedirs(repopath)
+        new = open(os.path.join(repopath, 'authors'), 'w')
         new.write(open(orig).read())
         new.close()
-        test = maps.AuthorMap(self.ui(), self.authors)
+        test = maps.AuthorMap(self.repo.svnmeta(skiperrorcheck=True))
         fromself = set(test)
         test.load(orig)
         all_tests = set(test)
@@ -285,21 +290,6 @@ class MapTests(test_util.TestBase):
         for r in repo:
             self.assertEquals(verify.verify(ui, repo, rev=r), 0)
 
-    def test_branchmap_no_replacement(self):
-        '''
-        test that empty mappings are rejected
-
-        Empty mappings are lines like 'this ='. The most sensible thing to do
-        is to not convert the 'this' branches. Until we can do that, we settle
-        with aborting.
-        '''
-        repo_path = self.load_svndump('propset-branch.svndump')
-        branchmap = open(self.branchmap, 'w')
-        branchmap.write("closeme =\n")
-        branchmap.close()
-        self.assertRaises(hgutil.Abort,
-                          maps.BranchMap, self.ui(), self.branchmap)
-
     def test_tagmap(self):
         repo_path = self.load_svndump('basic_tag_tests.svndump')
         tagmap = open(self.tagmap, 'w')
--- a/tests/test_fetch_renames.py
+++ b/tests/test_fetch_renames.py
@@ -18,29 +18,56 @@ class TestFetchRenames(test_util.TestBas
     def test_rename(self):
         config = {
             'hgsubversion.filestoresize': '0',
+            # we set this because we expect all of the copies to be
+            # handled via replay, and we want to notice if that
+            # changes.
+            'hgsubversion.failonmissing': 'yes',
             }
         repo = self._load_fixture_and_fetch('renames.svndump', config=config)
+        self._run_assertions(repo)
 
+    def test_rename_with_prefix(self):
+        config = {
+            'hgsubversion.filestoresize': '0',
+            'hgsubversion.failonmissing': 'yes',
+            }
+        repo = self._load_fixture_and_fetch('renames_with_prefix.svndump',
+                                            subdir='prefix',
+                                            config=config)
+        self._run_assertions(repo)
+
+    def _run_assertions(self, repo):
         # Map revnum to mappings of dest name to (source name, dest content)
         copies = {
             4: {
                 'a1': ('a', 'a\n'),
+                'linka1': ('linka', 'a'),
                 'a2': ('a', 'a\n'),
+                'linka2': ('linka', 'a'),
                 'b1': ('b', 'b\nc\n'),
+                'linkb1': ('linkb', 'bc'),
                 'da1/daf': ('da/daf', 'c\n'),
+                'da1/dalink': ('da/dalink', 'daf'),
                 'da1/db/dbf': ('da/db/dbf', 'd\n'),
+                'da1/db/dblink': ('da/db/dblink', '../daf'),
                 'da2/daf': ('da/daf', 'c\n'),
+                'da2/dalink': ('da/dalink', 'daf'),
                 'da2/db/dbf': ('da/db/dbf', 'd\n'),
+                'da2/db/dblink': ('da/db/dblink', '../daf'),
                 },
             5: {
                 'c1': ('c', 'c\nc\n'),
+                'linkc1': ('linkc', 'cc'),
                 },
             9: {
                 'unchanged2': ('unchanged', 'unchanged\n'),
+                'unchangedlink2': ('unchangedlink', 'unchanged'),
                 'unchangeddir2/f': ('unchangeddir/f', 'unchanged2\n'),
+                'unchangeddir2/link': ('unchangeddir/link', 'f'),
                 },
             10: {
-                 'groupdir2/b': ('groupdir/b', 'b\n')
+                'groupdir2/b': ('groupdir/b', 'b\n'),
+                'groupdir2/linkb': ('groupdir/linkb', 'b'),
                  },
             }
         for rev in repo:
--- a/tests/test_push_command.py
+++ b/tests/test_push_command.py
@@ -373,7 +373,7 @@ class PushTests(test_util.TestBase):
     def test_delete_file(self):
         repo = self.repo
         def file_callback(repo, memctx, path):
-            raise IOError(errno.ENOENT, '%s is deleted' % path)
+            return compathacks.filectxfn_deleted(memctx, path)
         old_files = set(repo['default'].manifest().keys())
         ctx = context.memctx(repo,
                              (repo['default'].node(), node.nullid),
@@ -552,7 +552,7 @@ class PushTests(test_util.TestBase):
                                               copied=False)
         ctx = context.memctx(repo,
                              (repo['default'].node(), node.nullid),
-                             'message',
+                             'mutate already-special file alpha',
                              ['alpha', ],
                              file_callback2,
                              'author',
@@ -577,7 +577,7 @@ class PushTests(test_util.TestBase):
                                               copied=False)
         ctx = context.memctx(repo,
                              (repo['default'].node(), node.nullid),
-                             'message',
+                             'convert alpha back to regular file',
                              ['alpha', ],
                              file_callback3,
                              'author',
@@ -754,3 +754,20 @@ class PushTests(test_util.TestBase):
         self.assertEqual(tip['adding_file'].data(), 'fooFirstFile')
         self.assertEqual(tip['newdir/new_file'].data(), 'fooNewFile')
         self.assertEqual(tip.branch(), 'default')
+
+    def test_update_after_push(self):
+        repo = self.repo
+        ui = repo.ui
+
+        ui.setconfig('hooks',
+                     'debug-hgsubversion-between-push-and-pull-for-tests',
+                     lambda ui, repo, hooktype: self.add_svn_rev(
+                         self.repo_path,
+                         {'trunk/racey_file': 'race conditions suck'}))
+
+        self.test_push_to_branch(push=False)
+        commands.push(ui, repo)
+        newctx = self.repo['.']
+        self.assertNotEqual(newctx.node(), self.repo['tip'].node())
+        self.assertEqual(newctx['adding_file'].data(), 'foo')
+        self.assertEqual(newctx.branch(), 'the_branch')
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -175,6 +175,8 @@ rename a tag
         repo = self._load_fixture_and_fetch('tag_name_same_as_branch.svndump')
         tm = os.path.join(repo.path, 'svn', 'tagmap')
         open(tm, 'w').write('1\n')
+        # force tags to load since it is lazily loaded when needed
+        repo.svnmeta().tags
         commands.pull(repo.ui, repo)
         self.assertEqual(open(tm).read().splitlines()[0], '2')
 
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -24,7 +24,7 @@ from mercurial import i18n
 from mercurial import node
 from mercurial import scmutil
 from mercurial import ui
-from mercurial import util
+from mercurial import util as hgutil
 from mercurial import extensions
 
 from hgsubversion import compathacks
@@ -108,6 +108,7 @@ subdir = {'truncatedhistory.svndump': '/
           'non_ascii_path_1.svndump': '/b\xC3\xB8b',
           'non_ascii_path_2.svndump': '/b%C3%B8b',
           'subdir_is_file_prefix.svndump': '/flaf',
+          'renames_with_prefix.svndump': '/prefix',
           }
 # map defining the layouts of the fixtures we can use with custom layout
 # these are really popular layouts, so I gave them names
@@ -138,6 +139,10 @@ custom = {
         'old_trunk': 'branches/old_trunk',
         },
     'copies.svndump': trunk_only,
+    'copyafterclose.svndump': {
+        'default': 'trunk',
+        'test': 'branches/test'
+        },
     'copybeforeclose.svndump': {
         'default': 'trunk',
         'test': 'branches/test'
@@ -159,6 +164,10 @@ custom = {
         'default': 'trunk',
         'branch1': 'branches/branch1',
         },
+    'renames_with_prefix.svndump': {
+        'default': 'trunk',
+        'branch1': 'branches/branch1',
+        },
     'replace_branch_with_branch.svndump': {
         'default': 'trunk',
         'branch1': 'branches/branch1',
@@ -445,9 +454,15 @@ class TestBase(unittest.TestCase):
 
         self.oldenv = dict([(k, os.environ.get(k, None),) for k in
                            ('LANG', 'LC_ALL', 'HGRCPATH',)])
-        self.oldt = i18n.t
-        os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
-        i18n.t = gettext.translation('hg', i18n.localedir, fallback=True)
+        try:
+            self.oldugettext = i18n._ugettext  # Mercurial >= 3.2
+        except AttributeError:
+            self.oldt = i18n.t
+            os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
+            i18n.t = gettext.translation('hg', i18n.localedir, fallback=True)
+        else:
+            os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
+            i18n.setdatapath(hgutil.datapath)
 
         self.oldwd = os.getcwd()
         self.tmpdir = tempfile.mkdtemp(
@@ -494,7 +509,10 @@ class TestBase(unittest.TestCase):
                 del os.environ[var]
             else:
                 os.environ[var] = val
-        i18n.t = self.oldt
+        try:
+            i18n._ugettext = self.oldugettext  # Mercurial >= 3.2
+        except AttributeError:
+            i18n.t = self.oldt
         rmtree(self.tmpdir)
         os.chdir(self.oldwd)
         setattr(ui.ui, self.patch[0].func_name, self.patch[0])
@@ -658,8 +676,7 @@ class TestBase(unittest.TestCase):
 
         def filectxfn(repo, memctx, path):
             if path in removed:
-                raise IOError(errno.ENOENT,
-                              "File \"%s\" no longer exists" % path)
+                return compathacks.filectxfn_deleted(memctx, path)
             entry = [e for e in changes if path == e[1]][0]
             source, dest, newdata = entry
             if newdata is None: