# HG changeset patch
# Parent 7853483b8503fd60acd6c9238ce46979663c51a9
http://mercurial.selenic.com/wiki/HgSubversion

diff --git a/opt/hgsubversion/.hgignore b/opt/hgsubversion/.hgignore
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/.hgignore
@@ -0,0 +1,14 @@
+syntax:glob
+build
+*.pyc
+*.pyo
+.DS_Store
+*.swp
+*~
+.coverage
+cover
+*.py,cover
+MANIFEST
+dist
+*.egg-info
+hgsubversion/__version__.py
diff --git a/opt/hgsubversion/.hgtags b/opt/hgsubversion/.hgtags
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/.hgtags
@@ -0,0 +1,7 @@
+97f2079e3778511944ffb6a662520580650a3993 1.0
+09c016174e332eceda015d3f43d96d7e0228acf3 1.0.1
+4359ddd73b009fbb356c52ea5d99cba25222ee7a 1.1
+8e621dbb82d4363a85317638ad237e2817c56347 1.1.1
+093ae2915b452539b44390ee4ea14987484e1eee 1.1.2
+708234ad6c97fb52417e0b46a86c8373e25123a5 1.2
+4bbc6bf947f56a92e95a04a27b94a9f72d5482d7 1.2.1
diff --git a/opt/hgsubversion/COPYING b/opt/hgsubversion/COPYING
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/opt/hgsubversion/MANIFEST.in b/opt/hgsubversion/MANIFEST.in
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/MANIFEST.in
@@ -0,0 +1,2 @@
+include Makefile *.rst
+recursive-include tests *.py *.sh *.svndump *.txt
diff --git a/opt/hgsubversion/Makefile b/opt/hgsubversion/Makefile
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/Makefile
@@ -0,0 +1,33 @@
+# Makefile for testing hgsubversion
+
+PYTHON=python
+
+.PHONY: all check check-demandimport check-subvertpy check-swig
+
+all:
+	@echo "Use the following commands to build and install hgsubversion:"
+	@echo
+	@echo "$$ cd $(PWD)"
+	@echo "$$ $(PYTHON) ./setup.py install"
+	@echo
+	@exit 1
+
+check: check-demandimport check-subvertpy check-swig
+
+check-demandimport:
+	# verify that hgsubversion loads properly without bindings, but fails
+	# when actually used
+	! LC_ALL=C HGSUBVERSION_BINDINGS=none \
+	  hg --config extensions.hgsubversion=./hgsubversion \
+	  version 2>&1 \
+	  | egrep '(^abort:|failed to import extension)'
+	LC_ALL=C HGSUBVERSION_BINDINGS=none \
+	  hg --config extensions.hgsubversion=./hgsubversion \
+	  version --svn 2>&1 \
+	  | egrep '(^abort:|failed to import extension)'
+
+check-subvertpy:
+	$(PYTHON) tests/run.py --all --bindings=subvertpy
+
+check-swig:
+	$(PYTHON) tests/run.py --all --bindings=swig
diff --git a/opt/hgsubversion/README b/opt/hgsubversion/README
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/README
@@ -0,0 +1,69 @@
+.. -*-restructuredtext-*-
+
+============
+hgsubversion
+============
+
+hgsubversion is an extension for Mercurial that allows using Mercurial
+as a Subversion client.
+
+At this point, hgsubversion is usable by users reasonably familiar with
+Mercurial as a VCS. It's not recommended to dive into hgsubversion as an
+introduction to Mercurial, since hgsubversion "bends the rules" a little
+and violates some of the typical assumptions of early Mercurial users.
+
+Installation
+------------
+You need to have either have Subversion 1.5 (or later) installed along with
+either Subvertpy 0.7.4 (or later) or the Subversion SWIG Python bindings. You
+need Mercurial 1.3 or later.
+
+.. _mercurial: http://selenic.com/repo/hg
+.. _mercurial-stable: http://selenic.com/repo/hg-stable
+.. _crew: http://hg.intevation.org/mercurial/crew
+.. _crew-stable: http://hg.intevation.org/mercurial/crew-stable
+
+If you are unfamiliar with installing Mercurial extensions, please see
+the UsingExtensions_ page in the Mercurial wiki. Look at the example
+for specifying an absolute path near the bottom of the page. You want
+to give the path to the top level of your clone of this repository.
+
+.. _UsingExtensions: http://mercurial.selenic.com/wiki/UsingExtensions
+
+Before using hgsubversion, I *strongly* encourage you to run the
+automated tests. Just use nose_ if you have it (or ``easy_install
+nose`` if you want it), or use ``python tests/run.py`` to run the
+suite with the conventional test runner. Note that because I use nose,
+there's a lot of stdout spew in the tests right now. The important
+part is that all the tests pass.
+
+.. _nose: http://code.google.com/p/python-nose/
+
+You can check that hgsubversion is installed and properly activated using the
+following command::
+
+ $ hg version --svn
+ Mercurial Distributed SCM (version ...)
+
+ Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ hgsubversion: ...
+ Subversion: ...
+ bindings: Subvertpy ...
+
+If your bindings are listed as `SWIG`, please consider installing Subvertpy_.
+
+.. _Subvertpy: http://pypi.python.org/pypi/subvertpy
+
+Further Reading
+---------------
+
+More information on how to use hgsubversion is available from within Mercurial
+in the `subversion` help topic. To view it, use::
+
+ $ hg help subversion
+
+The Restructured Text source for this topic is also available in the file
+``hgsubverson/help/subversion.rst``.
diff --git a/opt/hgsubversion/hgsubversion/__init__.py b/opt/hgsubversion/hgsubversion/__init__.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/__init__.py
@@ -0,0 +1,213 @@
+'''integration with Subversion repositories
+
+hgsubversion is an extension for Mercurial that allows it to act as a Subversion
+client, offering fast, incremental and bidirectional synchronisation.
+
+At this point, hgsubversion is usable by users reasonably familiar with
+Mercurial as a VCS. It's not recommended to dive into hgsubversion as an
+introduction to Mercurial, since hgsubversion "bends the rules" a little
+and violates some of the typical assumptions of early Mercurial users.
+
+Before using hgsubversion, we *strongly* encourage running the
+automated tests. See 'README' in the hgsubversion directory for
+details.
+
+For more information and instructions, see :hg:`help subversion`.
+'''
+
+import os
+import sys
+import traceback
+
+from mercurial import commands
+from mercurial import extensions
+from mercurial import help
+from mercurial import hg
+from mercurial import util as hgutil
+from mercurial import demandimport
+demandimport.ignore.extend([
+    'svn',
+    'svn.client',
+    'svn.core',
+    'svn.delta',
+    '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
+
+import svncommands
+import util
+import svnrepo
+import wrappers
+import svnexternals
+
+svnopts = [
+    ('', 'stupid', None,
+     'use slower, but more compatible, protocol for Subversion'),
+]
+
+# generic means it picks up all options from svnopts
+# fixdoc means update the docstring
+# TODO: fixdoc hoses l18n
+wrapcmds = { # cmd: generic, target, fixdoc, ppopts, opts
+    'parents': (False, None, False, False, [
+        ('', 'svn', None, 'show parent svn revision instead'),
+    ]),
+    'diff': (False, None, False, False, [
+        ('', 'svn', None, 'show svn diffs against svn parent'),
+    ]),
+    'pull': (True, 'sources', True, True, []),
+    'push': (True, 'destinations', True, True, []),
+    'incoming': (False, 'sources', True, True, []),
+    'version': (False, None, False, False, [
+        ('', 'svn', None, 'print hgsubversion information as well')]),
+    'clone': (False, 'sources', True, True, [
+        ('T', 'tagpaths', '',
+         'list of paths to search for tags in Subversion repositories'),
+        ('A', 'authors', '',
+         'file mapping Subversion usernames to Mercurial authors'),
+        ('', 'filemap', '',
+         'file containing rules for remapping Subversion repository paths'),
+        ('', 'layout', 'auto', ('import standard layout or single '
+                                'directory? Can be standard, single, or auto.')),
+        ('', 'branchmap', '', 'file containing rules for branch conversion'),
+        ('', 'tagmap', '', 'file containing rules for renaming tags'),
+        ('', 'startrev', '', ('convert Subversion revisions starting at the one '
+                              'specified, either an integer revision or HEAD; '
+                              'HEAD causes only the latest revision to be '
+                              'pulled')),
+    ]),
+}
+
+try:
+    from mercurial import discovery
+    def findcommonoutgoing(orig, *args, **opts):
+        capable = getattr(args[1], 'capable', lambda x: False)
+        if capable('subversion'):
+            return wrappers.findcommonoutgoing(*args, **opts)
+        else:
+            return orig(*args, **opts)
+    extensions.wrapfunction(discovery, 'findcommonoutgoing', findcommonoutgoing)
+except AttributeError:
+    # only need the discovery variant of this code when we drop hg < 1.6
+    def findoutgoing(orig, *args, **opts):
+        capable = getattr(args[1], 'capable', lambda x: False)
+        if capable('subversion'):
+            return wrappers.findoutgoing(*args, **opts)
+        else:
+            return orig(*args, **opts)
+    extensions.wrapfunction(discovery, 'findoutgoing', findoutgoing)
+except ImportError:
+    pass
+
+def extsetup():
+    """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():
+
+        if fixdoc and wrappers.generic.__doc__:
+            docvals['command'] = cmd
+            docvals['Command'] = cmd.capitalize()
+            docvals['target'] = target
+            doc = wrappers.generic.__doc__.strip() % docvals
+            fn = getattr(commands, cmd)
+            fn.__doc__ = fn.__doc__.rstrip() + '\n\n    ' + doc
+
+        wrapped = generic and wrappers.generic or getattr(wrappers, cmd)
+        entry = extensions.wrapcommand(commands.table, cmd, wrapped)
+        if ppopts:
+            entry[1].extend(svnopts)
+        if opts:
+            entry[1].extend(opts)
+
+    try:
+        rebase = extensions.find('rebase')
+        if not rebase:
+            return
+        entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', wrappers.rebase)
+        entry[1].append(('', 'svn', None, 'automatic svn rebase'))
+    except:
+        pass
+
+    helpdir = os.path.join(os.path.dirname(__file__), 'help')
+
+    entries = (
+        (['subversion'],
+         "Working with Subversion Repositories",
+         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
+
+    if templatekw:
+        templatekw.keywords.update(util.templatekeywords)
+
+    if revset:
+        revset.symbols.update(util.revsets)
+
+    if subrepo:
+        subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
+
+def reposetup(ui, repo):
+    if repo.local():
+        svnrepo.generate_repo_class(ui, repo)
+
+_old_local = hg.schemes['file']
+def _lookup(url):
+    if util.islocalrepo(url):
+        return svnrepo
+    else:
+        return _old_local(url)
+
+# install scheme handlers
+hg.schemes.update({ 'file': _lookup, 'http': svnrepo, 'https': svnrepo,
+                    'svn': svnrepo, 'svn+ssh': svnrepo, 'svn+http': svnrepo,
+                    'svn+https': svnrepo})
+
+commands.optionalrepo += ' svn'
+
+cmdtable = {
+    "svn":
+        (svncommands.svn,
+         [('u', 'svn-url', '', 'path to the Subversion server.'),
+          ('', 'stupid', False, 'be stupid and use diffy replay.'),
+          ('A', 'authors', '', 'username mapping filename'),
+          ('', 'filemap', '',
+           'remap file to exclude paths or include only certain paths'),
+          ('', 'force', False, 'force an operation to happen'),
+          ('', 'username', '', 'username for authentication'),
+          ('', 'password', '', 'password for authentication'),
+          ('r', 'rev', '', 'Mercurial revision'),
+          ],
+         'hg svn <subcommand> ...',
+         ),
+}
+
+# only these methods are public
+__all__ = ('cmdtable', 'reposetup', 'uisetup')
diff --git a/opt/hgsubversion/hgsubversion/editor.py b/opt/hgsubversion/hgsubversion/editor.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/editor.py
@@ -0,0 +1,358 @@
+import errno
+import cStringIO
+import sys
+
+from mercurial import util as hgutil
+from mercurial import revlog
+from mercurial import node
+
+import svnwrap
+import util
+import svnexternals
+
+class RevisionData(object):
+
+    __slots__ = [
+        'file', 'files', 'deleted', 'rev', 'execfiles', 'symlinks', 'batons',
+        'copies', 'missing', 'emptybranches', 'base', 'externals', 'ui',
+        'exception',
+    ]
+
+    def __init__(self, ui):
+        self.ui = ui
+        self.clear()
+
+    def clear(self):
+        self.file = None
+        self.files = {}
+        self.deleted = {}
+        self.rev = None
+        self.execfiles = {}
+        self.symlinks = {}
+        self.batons = {}
+        # Map fully qualified destination file paths to module source path
+        self.copies = {}
+        self.missing = set()
+        self.emptybranches = {}
+        self.base = None
+        self.externals = {}
+        self.exception = None
+
+    def set(self, path, data, isexec=False, islink=False):
+        self.files[path] = data
+        self.execfiles[path] = isexec
+        self.symlinks[path] = islink
+        if path in self.deleted:
+            del self.deleted[path]
+        if path in self.missing:
+            self.missing.remove(path)
+
+    def delete(self, path):
+        self.deleted[path] = True
+        if path in self.files:
+            del self.files[path]
+        self.execfiles[path] = False
+        self.symlinks[path] = False
+        self.ui.note('D %s\n' % path)
+
+    def findmissing(self, svn):
+
+        if not self.missing:
+            return
+
+        msg = 'fetching %s files that could not use replay.\n'
+        self.ui.debug(msg % len(self.missing))
+        root = svn.subdir and svn.subdir[1:] or ''
+        r = self.rev.revnum
+
+        files = set()
+        for p in self.missing:
+            self.ui.note('.')
+            self.ui.flush()
+            if p[-1] == '/':
+                dir = p[len(root):]
+                new = [p + f for f, k in svn.list_files(dir, r) if k == 'f']
+                files.update(new)
+            else:
+                files.add(p)
+
+        i = 1
+        self.ui.note('\nfetching files...\n')
+        for p in files:
+            self.ui.note('.')
+            self.ui.flush()
+            if i % 50 == 0:
+                svn.init_ra_and_client()
+            i += 1
+            data, mode = svn.get_file(p[len(root):], r)
+            self.set(p, data, 'x' in mode, 'l' in mode)
+
+        self.missing = set()
+        self.ui.note('\n')
+
+
+class HgEditor(svnwrap.Editor):
+
+    def __init__(self, meta):
+        self.meta = meta
+        self.ui = meta.ui
+        self.repo = meta.repo
+        self.current = RevisionData(meta.ui)
+
+    @svnwrap.ieditor
+    def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
+        br_path, branch = self.meta.split_branch_path(path)[:2]
+        if br_path == '':
+            if self.meta.get_path_tag(path):
+                # Tag deletion is not handled as branched deletion
+                return
+            self.meta.closebranches.add(branch)
+        if br_path is not None:
+            ha = self.meta.get_parent_revision(self.current.rev.revnum, branch)
+            if ha == revlog.nullid:
+                return
+            ctx = self.repo.changectx(ha)
+            if br_path not in ctx:
+                br_path2 = ''
+                if br_path != '':
+                    br_path2 = br_path + '/'
+                # assuming it is a directory
+                self.current.externals[path] = None
+                map(self.current.delete, [pat for pat in self.current.files.iterkeys()
+                                          if pat.startswith(path+'/')])
+                for f in ctx.walk(util.PrefixMatch(br_path2)):
+                    f_p = '%s/%s' % (path, f[len(br_path2):])
+                    if f_p not in self.current.files:
+                        self.current.delete(f_p)
+            self.current.delete(path)
+
+    @svnwrap.ieditor
+    def open_file(self, path, parent_baton, base_revision, p=None):
+        self.current.file = None
+        fpath, branch = self.meta.split_branch_path(path)[:2]
+        if not fpath:
+            self.ui.debug('WARNING: Opening non-existant file %s\n' % path)
+            return
+
+        self.current.file = path
+        self.ui.note('M %s\n' % path)
+        if base_revision != -1:
+            self.current.base = base_revision
+        else:
+            self.current.base = None
+
+        if self.current.file in self.current.files:
+            return
+
+        if not self.meta.is_path_valid(path):
+            return
+
+        baserev = base_revision
+        if baserev is None or baserev == -1:
+            baserev = self.current.rev.revnum - 1
+        # Use exact=True because during replacements ('R' action) we select
+        # replacing branch as parent, but svn delta editor provides delta
+        # agains replaced branch.
+        parent = self.meta.get_parent_revision(baserev + 1, branch, True)
+        ctx = self.repo[parent]
+        if fpath not in ctx:
+            self.current.missing.add(path)
+            return
+
+        fctx = ctx.filectx(fpath)
+        base = fctx.data()
+        if 'l' in fctx.flags():
+            base = 'link ' + base
+        self.current.set(path, base, 'x' in fctx.flags(), 'l' in fctx.flags())
+
+    @svnwrap.ieditor
+    def add_file(self, path, parent_baton=None, copyfrom_path=None,
+                 copyfrom_revision=None, file_pool=None):
+        self.current.file = None
+        self.current.base = None
+        if path in self.current.deleted:
+            del self.current.deleted[path]
+        fpath, branch = self.meta.split_branch_path(path, existing=False)[:2]
+        if not fpath:
+            return
+        if (branch not in self.meta.branches and
+            not self.meta.get_path_tag(self.meta.remotename(branch))):
+            # we know this branch will exist now, because it has at least one file. Rock.
+            self.meta.branches[branch] = None, 0, self.current.rev.revnum
+        self.current.file = path
+        if not copyfrom_path:
+            self.ui.note('A %s\n' % path)
+            self.current.set(path, '', False, False)
+            return
+        self.ui.note('A+ %s\n' % path)
+        (from_file,
+         from_branch) = self.meta.split_branch_path(copyfrom_path)[:2]
+        if not from_file:
+            self.current.missing.add(path)
+            return
+        # Use exact=True because during replacements ('R' action) we select
+        # replacing branch as parent, but svn delta editor provides delta
+        # agains replaced branch.
+        ha = self.meta.get_parent_revision(copyfrom_revision + 1,
+                                           from_branch, True)
+        ctx = self.repo.changectx(ha)
+        if from_file in ctx:
+            fctx = ctx.filectx(from_file)
+            flags = fctx.flags()
+            self.current.set(path, fctx.data(), 'x' in flags, 'l' in flags)
+            if from_branch == branch:
+                parentid = self.meta.get_parent_revision(
+                    self.current.rev.revnum, branch)
+                if parentid != revlog.nullid:
+                    parentctx = self.repo.changectx(parentid)
+                    if util.issamefile(parentctx, ctx, from_file):
+                        self.current.copies[path] = from_file
+
+    @svnwrap.ieditor
+    def add_directory(self, path, parent_baton, copyfrom_path,
+                      copyfrom_revision, dir_pool=None):
+        self.current.batons[path] = path
+        br_path, branch = self.meta.split_branch_path(path)[:2]
+        if br_path is not None:
+            if not copyfrom_path and not br_path:
+                self.current.emptybranches[branch] = True
+            else:
+                self.current.emptybranches[branch] = False
+        if br_path is None or not copyfrom_path:
+            return path
+        if self.meta.get_path_tag(path):
+            del self.current.emptybranches[branch]
+            return path
+        tag = self.meta.get_path_tag(copyfrom_path)
+        if tag not in self.meta.tags:
+            tag = None
+            if not self.meta.is_path_valid(copyfrom_path):
+                self.current.missing.add('%s/' % path)
+                return path
+        if tag:
+            changeid = self.meta.tags[tag]
+            source_rev, source_branch = self.meta.get_source_rev(changeid)[:2]
+            frompath = ''
+        else:
+            source_rev = copyfrom_revision
+            frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
+            if frompath == '' and br_path == '':
+                assert br_path is not None
+                tmp = source_branch, source_rev, self.current.rev.revnum
+                self.meta.branches[branch] = tmp
+        new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
+        if new_hash == node.nullid:
+            self.current.missing.add('%s/' % path)
+            return path
+        fromctx = self.repo.changectx(new_hash)
+        if frompath != '/' and frompath != '':
+            frompath = '%s/' % frompath
+        else:
+            frompath = ''
+        copies = {}
+        for f in fromctx:
+            if not f.startswith(frompath):
+                continue
+            fctx = fromctx.filectx(f)
+            dest = path + '/' + f[len(frompath):]
+            self.current.set(dest, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags())
+            if dest in self.current.deleted:
+                del self.current.deleted[dest]
+            if branch == source_branch:
+                copies[dest] = f
+        if copies:
+            # Preserve the directory copy records if no file was changed between
+            # the source and destination revisions, or discard it completely.
+            parentid = self.meta.get_parent_revision(self.current.rev.revnum, branch)
+            if parentid != revlog.nullid:
+                parentctx = self.repo.changectx(parentid)
+                for k, v in copies.iteritems():
+                    if util.issamefile(parentctx, fromctx, v):
+                        self.current.copies[k] = v
+        # Copy the externals definitions of copied directories
+        fromext = svnexternals.parse(self.ui, fromctx)
+        for p, v in fromext.iteritems():
+            pp = p and (p + '/') or ''
+            if pp.startswith(frompath):
+                dest = (path + '/' + pp[len(frompath):]).rstrip('/')
+                self.current.externals[dest] = v
+        return path
+
+    @svnwrap.ieditor
+    def change_file_prop(self, file_baton, name, value, pool=None):
+        if name == 'svn:executable':
+            self.current.execfiles[self.current.file] = bool(value is not None)
+        elif name == 'svn:special':
+            self.current.symlinks[self.current.file] = bool(value is not None)
+
+    @svnwrap.ieditor
+    def change_dir_prop(self, dir_baton, name, value, pool=None):
+        if dir_baton is None:
+            return
+        path = self.current.batons[dir_baton]
+        if name == 'svn:externals':
+            self.current.externals[path] = value
+
+    @svnwrap.ieditor
+    def open_directory(self, path, parent_baton, base_revision, dir_pool=None):
+        self.current.batons[path] = path
+        p_, branch = self.meta.split_branch_path(path)[:2]
+        if p_ == '' or (self.meta.layout == 'single' and p_):
+            if not self.meta.get_path_tag(path):
+                self.current.emptybranches[branch] = False
+        return path
+
+    @svnwrap.ieditor
+    def close_directory(self, dir_baton, dir_pool=None):
+        if dir_baton is not None:
+            del self.current.batons[dir_baton]
+
+    @svnwrap.ieditor
+    def apply_textdelta(self, file_baton, base_checksum, pool=None):
+        # We know coming in here the file must be one of the following options:
+        # 1) Deleted (invalid, fail an assertion)
+        # 2) Missing a base text (bail quick since we have to fetch a full plaintext)
+        # 3) Has a base text in self.current.files, apply deltas
+        base = ''
+        if not self.meta.is_path_valid(self.current.file):
+            return lambda x: None
+
+        if self.current.file in self.current.deleted:
+            msg = ('cannot apply textdelta to %s: file is deleted'
+                   % self.current.file)
+            raise IOError(errno.ENOENT, msg)
+
+        if (self.current.file not in self.current.files and
+            self.current.file not in self.current.missing):
+            msg = ('cannot apply textdelta to %s: file not found'
+                   % self.current.file)
+            raise IOError(errno.ENOENT, msg)
+
+        if self.current.file in self.current.missing:
+            return lambda x: None
+        base = self.current.files[self.current.file]
+        target = cStringIO.StringIO()
+        self.stream = target
+
+        handler = svnwrap.apply_txdelta(base, target)
+        if not callable(handler): #pragma: no cover
+            raise hgutil.Abort('Error in Subversion bindings: '
+                               'cannot call handler!')
+        def txdelt_window(window):
+            try:
+                if not self.meta.is_path_valid(self.current.file):
+                    return
+                handler(window)
+                # window being None means commit this file
+                if not window:
+                    self.current.files[self.current.file] = target.getvalue()
+            except svnwrap.SubversionException, e: #pragma: no cover
+                if e.args[1] == svnwrap.ERR_INCOMPLETE_DATA:
+                    self.current.missing.add(self.current.file)
+                else: #pragma: no cover
+                    raise hgutil.Abort(*e.args)
+            except: #pragma: no cover
+                print len(base), self.current.file
+                self._exception_info = sys.exc_info()
+                raise
+        return txdelt_window
diff --git a/opt/hgsubversion/hgsubversion/help/subversion.rst b/opt/hgsubversion/hgsubversion/help/subversion.rst
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/help/subversion.rst
@@ -0,0 +1,356 @@
+Basic Use
+---------
+
+Converting a Subversion repository to Mercurial with hgsubversion is done by
+cloning it. Subversion repositories are specified using the same, regular URL
+syntax that Subversion uses. hgsubversion accepts URIs such as the following::
+
+  http://user:sekrit@example.com/repo
+  https://user@example.com/repo
+  svn://example.com/repo
+  svn+ssh://example.com/repo
+  file:///tmp/repo
+
+In the case of the two first schemas, HTTP and HTTPS, the repository is first
+treated as a Mercurial repository, and a Subversion pull attempted should it
+fail. As this can be particularly annoying for repositories that require
+authentication, such repositories may also specified using a svn+http or
+svn+https schema.
+
+To create a new Mercurial clone, you can use a command such as the following::
+
+ $ hg clone <repository URI> [destination]
+
+Or with a real example::
+
+ $ hg clone http://python-nose.googlecode.com/svn nose-hg
+
+Please note that there are two slightly different ways of cloning repositories:
+
+The most common desire is to have the full history of a repository, including
+all its tags and branches. In such cases you should clone from one level above
+trunk, as in the example above. This is known as `standard layout`, and works
+with repositories that use the conventional ``trunk``, ``tags`` and ``branches``
+directories. By default, hgsubversion will use this layout whenever it finds any
+of these directories at the specified directory on the server.
+
+If you instead want to clone just a single directory or branch, clone the
+specific directory path. In the example above, to get *only* trunk, you would
+issue ``hg clone http://python-nose.googlecode.com/svn/trunk nose-trunk``. This
+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) ::
+
+ $ hg clone --layout single svn+http://python-nose.googlecode.com/svn nose-hg
+
+Pulling new revisions into an already-converted repository is the same
+as from any other Mercurial source. Within the first example above,
+the following three commands are all equivalent::
+
+ $ hg pull
+ $ hg pull default
+ $ hg pull http://python-nose.googlecode.com/svn
+
+Sometimes, past repository history is of little or no interest, and
+all one wants is to start from today and work forward. Using
+``--startrev HEAD`` causes the initial clone to only convert the
+latest revision; later pulls will convert all subsequent
+revisions. Please note that this only works for single-directory
+clones::
+
+ $ hg clone --startrev HEAD http://python-nose.googlecode.com/svn/trunk nose-hg
+
+Finding and displaying Subversion revisions
+-------------------------------------------
+
+For revealing the relationship between Mercurial changesets and
+Subversion revisions, hgsubversion provides three template keywords:
+
+  :svnrev: Expanded to the original Subversion revision number.
+  :svnpath: The path within the repository that the changeset represents.
+  :svnuuid: The Universally Unique Identifier of the Subversion repository.
+
+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:
+
+  :fromsvn: Select changesets that originate from Subversion. Takes no
+    arguments.
+  :svnrev: Select changesets that originate in a specific Subversion
+    revision. Takes a revision argument.
+
+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.
+
+Support for externals
+---------------------
+
+Subversion externals conversion is implemented for standard layouts.
+
+Using .hgsvnexternals (default mode)
+====================================
+
+.hgsvnexternals has been implemented before Mercurial supported proper
+subrepositories. Externals as subrepositories should now be preferred
+as they offer almost all .hgsvnexternals features with the benefit of
+a better integration with Mercurial commands.
+
+``svn:externals`` properties are serialized into a single
+``.hgsvnexternals`` file having the following syntax::
+
+  [.]
+   common1 http://path/to/external/svn/repo1
+   ...additional svn:externals properties lines...
+  [dir2]
+   common2 -r123 http://path/to/external/svn/repo2
+   ...additional svn:externals properties lines...
+
+A header line in brackets specifies the directory the property applies
+to, where '.' indicates the project root directory. The property content
+follows the header, with every content line being prefixed by a single
+space. Note that the property lines have a format identical to
+svn:externals properties as used in Subversion, and do not support the
+hgsubversion extended svn+http:// URL format.
+
+Issuing the command ``hg svn updateexternals`` with the ``.hgsvnexternals``
+example above would fetch the latest revision of `repo1` into the subdirectory
+`./common1`, and revision 123 of `repo2` into `dir2/common2`. Note that
+``.hgsvnexternals`` must be tracked by Mercurial before this will work. If
+``.hgsvnexternals`` is created or changed, it
+will not be pushed to the related Subversion repository, but its
+contents **will** be used to update ``svn:externals`` properties on the
+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.
+
+
+Using Subrepositories
+=====================
+
+Set:
+
+  [hgsubversion]
+  externals = subrepos
+
+to enable this mode.
+
+``svn:externals`` properties are serialized into the subrepositories
+metadata files, ``.hgsub`` and ``.hgsubstate``. The following
+``svn:externals`` entry:
+
+  -r23 ^/externals/project1 deps/project1
+
+set on the "subdir" directory becomes:
+
+    (.hgsub)
+    subdir/deps/project1 = [hgsubversion] subdir:-r{REV} ^/externals/project1 deps/project1
+
+    (.hgsubstate)
+    23 subdir/deps/project1
+
+At this point everything works like a regular svn subrepository. The
+right part of the .hgsub entry reads like:
+
+    TARGETDIR:REWRITTEN_EXTERNAL_DEFINITION
+
+where REWRITTEN_EXTERNAL_DEFINITION is like the original definition
+with the revision identifier replaced with {REV}.
+
+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
+  format. Besides, relative externals require a parent subversion
+  repository to be resolved while stock Mercurial only supports
+  absolute subversion paths.
+
+* Leading or trailing whitespaces in the external definitions are lost
+
+* Leading or trailing whitespaces in the target directory are lost
+
+* The external definition should not contain {REV}
+
+* Unversioned definitions are pulled but the behaviour upon
+  update/merge is not clearly defined. We tried to preserve the
+  .hgsubstate as "HEAD" but the subrepository will probably not be
+  updated when the hg repository is updated. Given subrepositories
+  were designed not to support unversioned dependencies, this is
+  unlikely to be fixed.
+
+* .hgsub and .hgsubstate are currently overwritten and
+  non-[hgsubversion] subrepos entries are lost. This could be fixed by
+  editing these files more carefully.
+
+Limitations
+-----------
+
+Currently, pushing to Subversion destroys the original changesets and replaces
+them with new ones converted from the resulting commits. Due to the intricacies
+of Subversion semantics, these converted changesets may differ in subtle ways
+from the original Mercurial changesets. For example, the commit date almost
+always changes. This makes hgsubversion unsuitable for use as a two-way bridge.
+
+When converting from Subversion, hgsubversion does not recognize merge-info, and
+does not create merges based on it. Similarly, Mercurial merges cannot be pushed
+to Subversion.
+
+Changesets that create tags cannot be pushed to Subversion, as support for
+creating Subversion tags has not yet been implemented.
+
+Standard layout does not work with repositories that use unconventional
+layouts. Thus, only single directory clones can be made of such repositories.
+
+When interacting with Subversion, hgsubversion relies on information about the
+previously converted changesets. This information will not be updated if pushing
+or pulling converted changesets to or from any other source. To regenerate the
+stored metadata, run ``hg svn rebuildmeta [URI]``. This must also be done if any
+converted changesets are ever removed from the repository.
+
+Under certain circumstances a long-running conversion can leak substantial
+amounts of memory, on the order of 100MB per 1000 converted revisions. The
+leaks appear to be persistent and unavoidable using the SWIG bindings. When
+using the new experimental Subvertpy bindings, leaks have only been observed
+accessing FSFS repositories over the file protocol.
+
+Should the initial clone fail with an error, Mercurial will delete the entire
+repository, including any revisions successfully converted. This can be
+particularly undesirable for long-running clones. In these cases, we suggest
+using the ``-r/--rev`` option to only clone a few revisions initially. After
+that, an ``hg pull`` in the cloned repository will be perfectly safe.
+
+It is not possible to interact with more than one Subversion repository per
+Mercurial clone. Please note that this also applies to more than one path within
+the same Subversion repository.
+
+Mercurial does not track directories, and as a result, any empty directories
+in Subversion cannot be represented in the resulting Mercurial repository.
+
+Externals support requires that the ``svn`` command line utility is available.
+In addition, externals support has been disabled for single directory clones,
+due to known bugs.
+
+Advanced Configuration
+----------------------
+
+The operation of hgsubversion can be customized by the following configuration
+settings:
+
+  ``hgsubversion.authormap``
+
+    Path to a file for mapping usernames from Subversion to Mercurial. For
+    example::
+
+      joe = Joe User <joe@example.com>
+
+    Some Subversion conversion tools create revisions without
+    specifying an author. Such author names are mapped to ``(no
+    author)``, similar to how ``svn log`` will display them.
+
+  ``hgsubversion.defaulthost``
+
+    This option specifies the hostname to append to unmapped Subversion
+    usernames. The default is to append the UUID of the Subversion repository
+    as a hostname. That is, an author of ``bob`` may be mapped to
+    ``bob@0b1d8996-7ded-4192-9199-38e2bec458fb``.
+
+    If this option set to an empty string, the Subversion authors will be used
+    with no hostname component.
+
+  ``hgsubversion.defaultmessage``
+
+    This option selects what to substitute for an empty log
+    message. The default is to substitute three dots, or ``...``.
+
+  ``hgsubversion.defaultauthors``
+
+    Setting this boolean option to false will cause hgsubversion to abort a
+    conversion if a revision has an author not listed in the author map.
+
+  ``hgsubversion.branch``
+
+    Mark converted changesets as belonging to this branch or, if unspecified,
+    ``default``. Please note that this option is not supported for standard
+    layout clones.
+
+  ``hgsubversion.branchmap``
+
+    Path to a file for changing branch names during the conversion from
+    Subversion to Mercurial.
+
+  ``hgsubversion.filemap``
+
+    Path to a file for filtering files during the conversion. Files may either
+    be included or excluded. See the documentation for ``hg convert`` for more
+    information on filemaps.
+
+  ``hgsubversion.username``, ``hgsubversion.password``
+
+    Set the username or password for accessing Subversion repositories.
+
+  ``hgsubversion.stupid``
+    Setting this boolean option to true will force using a slower method for
+    pulling revisions from Subversion. This method is compatible with servers
+    using very old versions of Subversion, and hgsubversion falls back to it
+    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.
+
+The following options only have an effect on the initial clone of a repository:
+
+  ``hgsubversion.layout``
+
+    Set the layout of the repository. ``standard`` assumes a normal
+    trunk/branches/tags layout. ``single`` means that the entire
+    repository is converted into a single branch. The default,
+    ``auto``, causes hgsubversion to assume a standard layout if any
+    of trunk, branches, or tags exist within the specified directory
+    on the server.
+
+  ``hgsubversion.startrev``
+
+    Convert Subversion revisions starting at the one specified, either an
+    integer revision or ``HEAD``; ``HEAD`` causes only the latest revision to be
+    pulled. The default is to pull everything.
+
+  ``hgsubversion.tagpaths``
+
+    Specifies one or more paths in the Subversion repository that
+    contain tags. The default is to only look in ``tags``. This option has no
+    effect for single-directory clones.
+
+Please note that some of these options may be specified as command line options
+as well, and when done so, will override the configuration. If an authormap,
+filemap or branchmap is specified, its contents will be read and stored for use
+in future pulls.
+
+Finally, the following environment variables can be used for testing a
+deployment of hgsubversion:
+
+  ``HGSUBVERSION_BINDINGS``
+
+    By default, hgsubversion will use Subvertpy, but fall back to the SWIG
+    bindings. Set this variable to either ``SWIG`` or ``Subvertpy`` (case-
+    insensitive) to force that set of bindings.
diff --git a/opt/hgsubversion/hgsubversion/maps.py b/opt/hgsubversion/hgsubversion/maps.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/maps.py
@@ -0,0 +1,419 @@
+''' Module for self-contained maps. '''
+
+import os
+from mercurial import util as hgutil
+from mercurial import node
+
+import svncommands
+
+class AuthorMap(dict):
+    '''A mapping from Subversion-style authors to Mercurial-style
+    authors, and back. The data is stored persistently on disk.
+
+    If the 'hgsubversion.defaultauthors' configuration option is set to false,
+    attempting to obtain an unknown author will fail with an Abort.
+    '''
+
+    def __init__(self, ui, path, defaulthost=None):
+        '''Initialise a new AuthorMap.
+
+        The ui argument is used to print diagnostic messages.
+
+        The path argument is the location of the backing store,
+        typically .hg/authormap.
+        '''
+        self.ui = ui
+        self.path = path
+        if defaulthost:
+            self.defaulthost = '@%s' % defaulthost.lstrip('@')
+        else:
+            self.defaulthost = ''
+        self.super = super(AuthorMap, self)
+        self.super.__init__()
+        self.load(path)
+
+    def load(self, path):
+        ''' Load mappings from a file at the specified path. '''
+
+        path = os.path.expandvars(path)
+        if not os.path.exists(path):
+            return
+
+        writing = False
+        if path != self.path:
+            writing = open(self.path, 'a')
+
+        self.ui.note('reading authormap from %s\n' % path)
+        f = open(path, 'r')
+        for number, line in enumerate(f):
+
+            if writing:
+                writing.write(line)
+
+            line = line.split('#')[0]
+            if not line.strip():
+                continue
+
+            try:
+                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()))
+                continue
+
+            src = src.strip()
+            dst = dst.strip()
+            self.ui.debug('adding author %s to author map\n' % src)
+            if src in self and dst != self[src]:
+                msg = 'overriding author: "%s" to "%s" (%s)\n'
+                self.ui.status(msg % (self[src], dst, src))
+            self[src] = dst
+
+        f.close()
+        if writing:
+            writing.close()
+
+    def __getitem__(self, author):
+        ''' Similar to dict.__getitem__, except in case of an unknown author.
+        In such cases, a new value is generated and added to the dictionary
+        as well as the backing store. '''
+        if author is None:
+            author = '(no author)'
+        if author in self:
+            result = self.super.__getitem__(author)
+        elif self.ui.configbool('hgsubversion', 'defaultauthors', True):
+            self[author] = result = '%s%s' % (author, self.defaulthost)
+            msg = 'substituting author "%s" for default "%s"\n'
+            self.ui.note(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))
+        return result
+
+    def reverselookup(self, author):
+        for svnauthor, hgauthor in self.iteritems():
+            if author == hgauthor:
+                return svnauthor
+        else:
+            # Mercurial incorrectly splits at e.g. '.', so we roll our own.
+            return author.rsplit('@', 1)[0]
+
+
+class Tags(dict):
+    """Map tags to converted node identifier.
+
+    tag names are non-empty strings. Tags are saved in a file
+    called tagmap, for backwards compatibility reasons.
+    """
+    VERSION = 2
+
+    @classmethod
+    def filepath(cls, repo):
+        return os.path.join(repo.path, 'svn', 'tagmap')
+
+    def __init__(self, repo, endrev=None):
+        dict.__init__(self)
+        self.path = self.filepath(repo)
+        self.endrev = endrev
+        if os.path.isfile(self.path):
+            self._load(repo)
+        else:
+            self._write()
+
+    def _load(self, repo):
+        f = open(self.path)
+        ver = int(f.readline())
+        if ver < self.VERSION:
+            repo.ui.status('tag map outdated, running rebuildmeta...\n')
+            f.close()
+            os.unlink(self.path)
+            svncommands.rebuildmeta(repo.ui, repo, ())
+            return
+        elif ver != self.VERSION:
+            print 'tagmap too new -- please upgrade'
+            raise NotImplementedError
+        for l in f:
+            hash, revision, tag = l.split(' ', 2)
+            revision = int(revision)
+            tag = tag[:-1]
+            if self.endrev is not None and revision > self.endrev:
+                break
+            if not tag:
+                continue
+            dict.__setitem__(self, tag, node.bin(hash))
+        f.close()
+
+    def _write(self):
+        assert self.endrev is None
+        f = open(self.path, 'w')
+        f.write('%s\n' % self.VERSION)
+        f.close()
+
+    def update(self, other):
+        for k, v in other.iteritems():
+            self[k] = v
+
+    def __contains__(self, tag):
+        return (tag and dict.__contains__(self, tag)
+                and dict.__getitem__(self, tag) != node.nullid)
+
+    def __getitem__(self, tag):
+        if tag and tag in self:
+            return dict.__getitem__(self, tag)
+        raise KeyError()
+
+    def __setitem__(self, tag, info):
+        if not tag:
+            raise hgutil.Abort('tag cannot be empty')
+        hash, revision = info
+        f = open(self.path, 'a')
+        f.write('%s %s %s\n' % (node.hex(hash), revision, tag))
+        f.close()
+        dict.__setitem__(self, tag, hash)
+
+
+class RevMap(dict):
+
+    VERSION = 1
+
+    def __init__(self, repo):
+        dict.__init__(self)
+        self.path = os.path.join(repo.path, 'svn', 'rev_map')
+        self.youngest = 0
+        self.oldest = 0
+        if os.path.isfile(self.path):
+            self._load()
+        else:
+            self._write()
+
+    def hashes(self):
+        return dict((v, k) for (k, v) in self.iteritems())
+
+    def branchedits(self, branch, rev):
+        check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
+        return sorted(filter(check, self.iteritems()), reverse=True)
+
+    def _load(self):
+        f = open(self.path)
+        ver = int(f.readline())
+        if ver != self.VERSION:
+            print 'revmap too new -- please upgrade'
+            raise NotImplementedError
+        for l in f:
+            revnum, hash, 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(hash))
+        f.close()
+
+    def _write(self):
+        f = open(self.path, 'w')
+        f.write('%s\n' % self.VERSION)
+        f.close()
+
+    def __setitem__(self, key, hash):
+        revnum, branch = key
+        f = open(self.path, 'a')
+        b = branch or ''
+        f.write(str(revnum) + ' ' + node.hex(hash) + ' ' + 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
+        dict.__setitem__(self, (revnum, branch), hash)
+
+
+class FileMap(object):
+
+    def __init__(self, repo):
+        self.ui = repo.ui
+        self.include = {}
+        self.exclude = {}
+        filemap = repo.ui.config('hgsubversion', 'filemap')
+        if filemap and os.path.exists(filemap):
+            self.load(filemap)
+
+    def _rpairs(self, name):
+        yield '.', name
+        e = len(name)
+        while e != -1:
+            yield name[:e], name[e+1:]
+            e = name.rfind('/', 0, e)
+
+    def check(self, map, path):
+        map = getattr(self, map)
+        for pre, suf in self._rpairs(path):
+            if pre not in map:
+                continue
+            return map[pre]
+        return None
+
+    def __contains__(self, path):
+        if len(self.include) and len(path):
+            inc = self.check('include', path)
+        else:
+            inc = path
+        if len(self.exclude) and len(path):
+            exc = self.check('exclude', path)
+        else:
+            exc = None
+        if inc is None or exc is not None:
+            return False
+        return True
+
+    def add(self, fn, map, path):
+        mapping = getattr(self, map)
+        if path in mapping:
+            msg = 'duplicate %s entry in %s: "%s"\n'
+            self.ui.status(msg % (map, fn, path))
+            return
+        bits = map.strip('e'), path
+        self.ui.debug('%sing %s\n' % bits)
+        mapping[path] = path
+
+    def load(self, fn):
+        self.ui.note('reading file map from %s\n' % fn)
+        f = open(fn, 'r')
+        for line in f:
+            if line.strip() == '' or line.strip()[0] == '#':
+                continue
+            try:
+                cmd, path = line.split(' ', 1)
+                cmd = cmd.strip()
+                path = path.strip()
+                if cmd in ('include', 'exclude'):
+                    self.add(fn, cmd, path)
+                    continue
+                self.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()))
+        f.close()
+
+class BranchMap(dict):
+    '''Facility for controlled renaming of branch names. Example:
+
+    oldname = newname
+    other = default
+
+    All changes on the oldname branch will now be on the newname branch; all
+    changes on other will now be on default (have no branch name set).
+    '''
+
+    def __init__(self, ui, path):
+        self.ui = ui
+        self.path = path
+        self.super = super(BranchMap, self)
+        self.super.__init__()
+        self.load(path)
+
+    def load(self, path):
+        '''Load mappings from a file at the specified path.'''
+        if not os.path.exists(path):
+            return
+
+        writing = False
+        if path != self.path:
+            writing = open(self.path, 'a')
+
+        self.ui.note('reading branchmap from %s\n' % path)
+        f = open(path, 'r')
+        for number, line in enumerate(f):
+
+            if writing:
+                writing.write(line)
+
+            line = line.split('#')[0]
+            if not line.strip():
+                continue
+
+            try:
+                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()))
+                continue
+
+            src = src.strip()
+            dst = dst.strip()
+            self.ui.debug('adding branch %s to branch map\n' % src)
+
+            if not dst:
+                # prevent people from assuming such lines are valid
+                raise hgutil.Abort('removing branches is not supported, yet\n'
+                                   '(line %i in branch map %s)'
+                                   % (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[src] = dst
+
+        f.close()
+        if writing:
+            writing.close()
+
+class TagMap(dict):
+    '''Facility for controlled renaming of tags. Example:
+
+    oldname = newname
+    other =
+
+        The oldname tag from SVN will be represented as newname in the hg tags;
+        the other tag will not be reflected in the hg repository.
+    '''
+
+    def __init__(self, ui, path):
+        self.ui = ui
+        self.path = path
+        self.super = super(TagMap, self)
+        self.super.__init__()
+        self.load(path)
+
+    def load(self, path):
+        '''Load mappings from a file at the specified path.'''
+        if not os.path.exists(path):
+            return
+
+        writing = False
+        if path != self.path:
+            writing = open(self.path, 'a')
+
+        self.ui.note('reading tag renames from %s\n' % path)
+        f = open(path, 'r')
+        for number, line in enumerate(f):
+
+            if writing:
+                writing.write(line)
+
+            line = line.split('#')[0]
+            if not line.strip():
+                continue
+
+            try:
+                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()))
+                continue
+
+            src = src.strip()
+            dst = dst.strip()
+            self.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[src] = dst
+
+        f.close()
+        if writing:
+            writing.close()
diff --git a/opt/hgsubversion/hgsubversion/pushmod.py b/opt/hgsubversion/hgsubversion/pushmod.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/pushmod.py
@@ -0,0 +1,207 @@
+from mercurial import util as hgutil
+
+import svnwrap
+import svnexternals
+import util
+
+class NoFilesException(Exception):
+    """Exception raised when you try and commit without files.
+    """
+
+
+def _isdir(svn, branchpath, svndir):
+    try:
+        path = ''
+        if branchpath:
+            path = branchpath + '/'
+        svn.list_dir('%s%s' % (path, svndir))
+        return True
+    except svnwrap.SubversionException:
+        return False
+
+
+def _getdirchanges(svn, branchpath, parentctx, ctx, changedfiles, extchanges):
+    """Compute directories to add or delete when moving from parentctx
+    to ctx, assuming only 'changedfiles' files changed, and 'extchanges'
+    external references changed (as returned by svnexternals.diff()).
+
+    Return (added, deleted) where 'added' is the list of all added
+    directories and 'deleted' the list of deleted directories.
+    Intermediate directories are included: if a/b/c is new and requires
+    the addition of a/b and a, those will be listed too. Intermediate
+    deleted directories are also listed, but item order of undefined
+    in either list.
+    """
+    def finddirs(path, includeself=False):
+        if includeself and path:
+            yield path
+        pos = path.rfind('/')
+        while pos != -1:
+            yield path[:pos]
+            pos = path.rfind('/', 0, pos)
+        # Include the root path, properties can be set explicitely on it
+        # (like externals), and you want to preserve it if there are any
+        # other child item still existing.
+        yield ''
+
+    def getctxdirs(ctx, keptdirs, extdirs):
+        dirs = {}
+        for f in ctx.manifest():
+            for d in finddirs(f):
+                if d in dirs:
+                    break
+                if d in keptdirs:
+                    dirs[d] = 1
+        for extdir in extdirs:
+            for d in finddirs(extdir, True):
+                dirs[d] = 1
+        return dirs
+
+    deleted, added = [], []
+    changeddirs = {}
+    for f in changedfiles:
+        if f in parentctx and f in ctx:
+            # Updated files cannot cause directories to be created
+            # or removed.
+            continue
+        for d in finddirs(f):
+            changeddirs[d] = 1
+    for e in extchanges:
+        if not e[1] or not e[2]:
+            for d in finddirs(e[0], True):
+                changeddirs[d] = 1
+    if not changeddirs:
+        return added, deleted
+    olddirs = getctxdirs(parentctx, changeddirs,
+                         [e[0] for e in extchanges if e[1]])
+    newdirs = getctxdirs(ctx, changeddirs,
+                         [e[0] for e in extchanges if e[2]])
+
+    for d in newdirs:
+        if d not in olddirs and not _isdir(svn, branchpath, d):
+            added.append(d)
+
+    for d in olddirs:
+        if d not in newdirs and _isdir(svn, branchpath, d):
+            deleted.append(d)
+
+    return added, deleted
+
+
+def commit(ui, repo, rev_ctx, meta, base_revision, svn):
+    """Build and send a commit from Mercurial to Subversion.
+    """
+    file_data = {}
+    parent = rev_ctx.parents()[0]
+    parent_branch = rev_ctx.parents()[0].branch()
+    branch_path = 'trunk'
+
+    if meta.layout == 'single':
+        branch_path = ''
+    elif parent_branch and parent_branch != 'default':
+        branch_path = 'branches/%s' % parent_branch
+
+    extchanges = svnexternals.diff(svnexternals.parse(ui, parent),
+                                   svnexternals.parse(ui, rev_ctx))
+    addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx,
+                                            rev_ctx.files(), extchanges)
+    deleteddirs = set(deleteddirs)
+
+    props = {}
+    copies = {}
+    for file in rev_ctx.files():
+        if file in util.ignoredfiles:
+            continue
+        new_data = base_data = ''
+        action = ''
+        if file in rev_ctx:
+            fctx = rev_ctx.filectx(file)
+            new_data = fctx.data()
+
+            if 'x' in fctx.flags():
+                props.setdefault(file, {})['svn:executable'] = '*'
+            if 'l' in fctx.flags():
+                props.setdefault(file, {})['svn:special'] = '*'
+            isbinary = hgutil.binary(new_data)
+            if isbinary:
+                props.setdefault(file, {})['svn:mime-type'] = 'application/octet-stream'
+
+            if file not in parent:
+                renamed = fctx.renamed()
+                if renamed:
+                    # TODO current model (and perhaps svn model) does not support
+                    # this kind of renames: a -> b, b -> c
+                    copies[file] = renamed[0]
+                    base_data = parent[renamed[0]].data()
+
+                action = 'add'
+                dirname = '/'.join(file.split('/')[:-1] + [''])
+            else:
+                base_data = parent.filectx(file).data()
+                if ('x' in parent.filectx(file).flags()
+                    and 'x' not in rev_ctx.filectx(file).flags()):
+                    props.setdefault(file, {})['svn:executable'] = None
+                if ('l' in parent.filectx(file).flags()
+                    and 'l' not in rev_ctx.filectx(file).flags()):
+                    props.setdefault(file, {})['svn:special'] = None
+                if hgutil.binary(base_data) and not isbinary:
+                    props.setdefault(file, {})['svn:mime-type'] = None
+                action = 'modify'
+        else:
+            pos = file.rfind('/')
+            if pos >= 0:
+                if file[:pos] in deleteddirs:
+                    # This file will be removed when its directory is removed
+                    continue
+            action = 'delete'
+        file_data[file] = base_data, new_data, action
+
+    def svnpath(p):
+        return ('%s/%s' % (branch_path, p)).strip('/')
+
+    changeddirs = []
+    for d, v1, v2 in extchanges:
+        props.setdefault(svnpath(d), {})['svn:externals'] = v2
+        if d not in deleteddirs and d not in addeddirs:
+            changeddirs.append(svnpath(d))
+
+    # Now we are done with files, we can prune deleted directories
+    # against themselves: ignore a/b if a/ is already removed
+    deleteddirs2 = list(deleteddirs)
+    deleteddirs2.sort(reverse=True)
+    for d in deleteddirs2:
+        pos = d.rfind('/')
+        if pos >= 0 and d[:pos] in deleteddirs:
+            deleteddirs.remove(d)
+
+    newcopies = {}
+    for source, dest in copies.iteritems():
+        newcopies[svnpath(source)] = (svnpath(dest), base_revision)
+
+    new_target_files = [svnpath(f) for f in file_data]
+    for tf, ntf in zip(file_data, new_target_files):
+        if tf in file_data and tf != ntf:
+            file_data[ntf] = file_data[tf]
+            if tf in props:
+                props[ntf] = props.pop(tf)
+            del file_data[tf]
+
+    addeddirs = [svnpath(d) for d in addeddirs]
+    deleteddirs = [svnpath(d) for d in deleteddirs]
+    new_target_files += addeddirs + deleteddirs + changeddirs
+    if not new_target_files:
+        raise NoFilesException()
+    try:
+        svn.commit(new_target_files, rev_ctx.description(), file_data,
+                   base_revision, set(addeddirs), set(deleteddirs),
+                   props, newcopies)
+    except svnwrap.SubversionException, e:
+        if len(e.args) > 0 and e.args[1] in (svnwrap.ERR_FS_TXN_OUT_OF_DATE,
+                                             svnwrap.ERR_FS_CONFLICT):
+            raise hgutil.Abort('Outgoing changesets parent is not at '
+                               'subversion HEAD\n'
+                               '(pull again and rebase on a newer revision)')
+        else:
+            raise
+
+    return True
diff --git a/opt/hgsubversion/hgsubversion/replay.py b/opt/hgsubversion/hgsubversion/replay.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/replay.py
@@ -0,0 +1,217 @@
+import errno
+import traceback
+
+from mercurial import revlog
+from mercurial import node
+from mercurial import context
+from mercurial import util as hgutil
+
+import svnexternals
+import util
+
+
+class MissingPlainTextError(Exception):
+    """Exception raised when the repo lacks a source file required for replaying
+    a txdelta.
+    """
+
+class ReplayException(Exception):
+    """Exception raised when you try and commit but the replay encountered an
+    exception.
+    """
+
+def updateexternals(ui, meta, current):
+    # TODO fix and re-enable externals for single-directory clones
+    if not current.externals or meta.layout == 'single':
+        return
+
+    # accumulate externals records for all branches
+    revnum = current.rev.revnum
+    branches = {}
+    for path, entry in current.externals.iteritems():
+        if not meta.is_path_valid(path):
+            ui.warn('WARNING: Invalid path %s in externals\n' % path)
+            continue
+
+        p, b, bp = meta.split_branch_path(path)
+        if bp not in branches:
+            parent = meta.get_parent_revision(revnum, b)
+            pctx = meta.repo[parent]
+            branches[bp] = (svnexternals.parse(ui, pctx), pctx)
+        branches[bp][0][p] = entry
+
+    # register externals file changes
+    for bp, (external, pctx) in branches.iteritems():
+        if bp and bp[-1] != '/':
+            bp += '/'
+        updates = svnexternals.getchanges(ui, meta.repo, pctx, external)
+        for fn, data in updates.iteritems():
+            path = (bp and bp + fn) or fn
+            if data is not None:
+                current.set(path, data, False, False)
+            else:
+                current.delete(path)
+
+def convert_rev(ui, meta, svn, r, tbdelta, firstrun):
+
+    editor = meta.editor
+    editor.current.clear()
+    editor.current.rev = r
+
+    if firstrun and meta.revmap.oldest <= 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)
+
+    current = editor.current
+    current.findmissing(svn)
+
+    updateexternals(ui, meta, current)
+
+    if current.exception is not None:  #pragma: no cover
+        traceback.print_exception(*current.exception)
+        raise ReplayException()
+    if current.missing:
+        raise MissingPlainTextError()
+
+    # paranoidly generate the list of files to commit
+    files_to_commit = set(current.files.keys())
+    files_to_commit.update(current.symlinks.keys())
+    files_to_commit.update(current.execfiles.keys())
+    files_to_commit.update(current.deleted.keys())
+    # back to a list and sort so we get sane behavior
+    files_to_commit = list(files_to_commit)
+    files_to_commit.sort()
+    branch_batches = {}
+    rev = current.rev
+    date = meta.fixdate(rev.date)
+
+    # build up the branches that have files on them
+    for f in files_to_commit:
+        if not meta.is_path_valid(f):
+            continue
+        p, b = meta.split_branch_path(f)[:2]
+        if b not in branch_batches:
+            branch_batches[b] = []
+        branch_batches[b].append((p, f))
+
+    closebranches = {}
+    for branch in tbdelta['branches'][1]:
+        branchedits = meta.revmap.branchedits(branch, rev)
+        if len(branchedits) < 1:
+            # can't close a branch that never existed
+            continue
+        ha = branchedits[0][1]
+        closebranches[branch] = ha
+
+    extraempty = (set(tbdelta['branches'][0]) -
+                  (set(current.emptybranches) | set(branch_batches.keys())))
+    current.emptybranches.update([(x, False) for x in extraempty])
+
+    # 1. handle normal commits
+    closedrevs = closebranches.values()
+    for branch, files in branch_batches.iteritems():
+
+        if branch in current.emptybranches and files:
+            del current.emptybranches[branch]
+
+        files = dict(files)
+        parents = meta.get_parent_revision(rev.revnum, branch), revlog.nullid
+        if parents[0] in closedrevs and branch in meta.closebranches:
+            continue
+
+        extra = meta.genextra(rev.revnum, branch)
+        tag = None
+        if branch is not None:
+            # New regular tag without modifications, it will be committed by
+            # svnmeta.committag(), we can skip the whole branch for now
+            tag = meta.get_path_tag(meta.remotename(branch))
+            if (tag and tag not in meta.tags
+                and branch not in meta.branches
+                and branch not in meta.repo.branchtags()
+                and not files):
+                continue
+
+        parentctx = meta.repo.changectx(parents[0])
+        if tag:
+            if parentctx.node() == node.nullid:
+                continue
+            extra.update({'branch': parentctx.extra().get('branch', None),
+                          'close': 1})
+
+        def filectxfn(repo, memctx, path):
+            current_file = files[path]
+            if current_file in current.deleted:
+                raise IOError(errno.ENOENT, '%s is deleted' % path)
+            copied = current.copies.get(current_file)
+            flags = parentctx.flags(path)
+            is_exec = current.execfiles.get(current_file, 'x' in flags)
+            is_link = current.symlinks.get(current_file, 'l' in flags)
+            if current_file in current.files:
+                data = current.files[current_file]
+                if is_link and data.startswith('link '):
+                    data = data[len('link '):]
+                elif is_link:
+                    ui.debug('file marked as link, but may contain data: '
+                             '%s (%r)\n' % (current_file, flags))
+            else:
+                data = parentctx.filectx(path).data()
+            return context.memfilectx(path=path,
+                                      data=data,
+                                      islink=is_link, isexec=is_exec,
+                                      copied=copied)
+
+        meta.mapbranch(extra)
+        current_ctx = context.memctx(meta.repo,
+                                     parents,
+                                     rev.message or util.default_commit_msg(ui),
+                                     files.keys(),
+                                     filectxfn,
+                                     meta.authors[rev.author],
+                                     date,
+                                     extra)
+
+        new_hash = meta.repo.commitctx(current_ctx)
+        util.describe_commit(ui, new_hash, branch)
+        if (rev.revnum, branch) not in meta.revmap and not tag:
+            meta.revmap[rev.revnum, branch] = new_hash
+        if tag:
+            meta.movetag(tag, new_hash, rev, date)
+            meta.addedtags.pop(tag, None)
+
+    # 2. handle branches that need to be committed without any files
+    for branch in current.emptybranches:
+
+        ha = meta.get_parent_revision(rev.revnum, branch)
+        if ha == node.nullid:
+            continue
+
+        parent_ctx = meta.repo.changectx(ha)
+        def del_all_files(*args):
+            raise IOError(errno.ENOENT, 'deleting all files')
+
+        # True here meant nuke all files, shouldn't happen with branch closing
+        if current.emptybranches[branch]: #pragma: no cover
+            raise hgutil.Abort('Empty commit to an open branch attempted. '
+                               'Please report this issue.')
+
+        extra = meta.genextra(rev.revnum, branch)
+        meta.mapbranch(extra)
+
+        current_ctx = context.memctx(meta.repo,
+                                     (ha, node.nullid),
+                                     rev.message or ' ',
+                                     [],
+                                     del_all_files,
+                                     meta.authors[rev.author],
+                                     date,
+                                     extra)
+        new_hash = meta.repo.commitctx(current_ctx)
+        util.describe_commit(ui, new_hash, branch)
+        if (rev.revnum, branch) not in meta.revmap:
+            meta.revmap[rev.revnum, branch] = new_hash
+
+    return closebranches
diff --git a/opt/hgsubversion/hgsubversion/stupid.py b/opt/hgsubversion/hgsubversion/stupid.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/stupid.py
@@ -0,0 +1,731 @@
+import cStringIO
+import errno
+import re
+
+from mercurial import patch
+from mercurial import node
+from mercurial import context
+from mercurial import revlog
+
+import svnwrap
+import svnexternals
+import util
+
+
+binary_file_re = re.compile(r'''Index: ([^\n]*)
+=*
+Cannot display: file marked as a binary type.''')
+
+property_exec_set_re = re.compile(r'''Property changes on: ([^\n]*)
+_*
+(?:Added|Name): svn:executable
+   \+''')
+
+property_exec_removed_re = re.compile(r'''Property changes on: ([^\n]*)
+_*
+(?:Deleted|Name): svn:executable
+   -''')
+
+empty_file_patch_wont_make_re = re.compile(r'''Index: ([^\n]*)\n=*\n(?=Index:)''')
+
+any_file_re = re.compile(r'''^Index: ([^\n]*)\n=*\n''', re.MULTILINE)
+
+property_special_set_re = re.compile(r'''Property changes on: ([^\n]*)
+_*
+(?:Added|Name): svn:special
+   \+''')
+
+property_special_removed_re = re.compile(r'''Property changes on: ([^\n]*)
+_*
+(?:Deleted|Name): svn:special
+   \-''')
+
+
+class BadPatchApply(Exception):
+    pass
+
+
+def print_your_svn_is_old_message(ui): #pragma: no cover
+    ui.status("In light of that, I'll fall back and do diffs, but it won't do "
+              "as good a job. You should really upgrade your server.\n")
+
+def mempatchproxy(parentctx, files):
+    # Avoid circular references patch.patchfile -> mempatch
+    patchfile = patch.patchfile
+
+    # TODO(durin42): make this a compat path for hg < 1.6.
+    class mempatch(patchfile):
+        def __init__(self, ui, fname, opener, missing=False, eolmode=None):
+            patchfile.__init__(self, ui, fname, None, False, eolmode)
+
+        def readlines(self, fname):
+            if fname not in parentctx:
+                raise IOError(errno.ENOENT, 'Cannot find %r to patch' % fname)
+            fctx = parentctx[fname]
+            data = fctx.data()
+            if 'l' in fctx.flags():
+                data = 'link ' + data
+            return cStringIO.StringIO(data).readlines()
+
+        def writelines(self, fname, lines):
+            files[fname] = ''.join(lines)
+
+        def unlink(self, fname):
+            files[fname] = None
+
+    return mempatch
+
+
+def filteriterhunks(meta):
+    iterhunks = patch.iterhunks
+    def filterhunks(*args, **kwargs):
+        # ui, fp, sourcefile=None, textmode=False
+        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)
+        for data in gen:
+            if data[0] == 'file':
+                if data[1][1] in meta.filemap:
+                    applycurrent = True
+                else:
+                    applycurrent = False
+            assert data[0] != 'git', 'Filtering git hunks not supported.'
+            if applycurrent:
+                yield data
+    return filterhunks
+
+def patchrepoold(ui, meta, parentctx, patchfp):
+    files = {}
+    try:
+        oldpatchfile = patch.patchfile
+        olditerhunks = patch.iterhunks
+        patch.patchfile = mempatchproxy(parentctx, files)
+        patch.iterhunks = filteriterhunks(meta)
+        try:
+            # We can safely ignore the changed list since we are
+            # handling non-git patches. Touched files are known
+            # by our memory patcher.
+            patch_st = patch.applydiff(ui, patchfp, {}, strip=0)
+        finally:
+            patch.patchfile = oldpatchfile
+            patch.iterhunks = olditerhunks
+    except patch.PatchError:
+        # TODO: this happens if the svn server has the wrong mime
+        # type stored and doesn't know a file is binary. It would
+        # be better to do one file at a time and only do a
+        # full fetch on files that had problems.
+        raise BadPatchApply('patching failed')
+    # if this patch didn't apply right, fall back to exporting the
+    # entire rev.
+    if patch_st == -1:
+        assert False, ('This should only happen on case-insensitive'
+                       ' volumes.')
+    elif patch_st == 1:
+        # When converting Django, I saw fuzz on .po files that was
+        # causing revisions to end up failing verification. If that
+        # can be fixed, maybe this won't ever be reached.
+        raise BadPatchApply('patching succeeded with fuzz')
+    return files
+
+try:
+    class svnbackend(patch.repobackend):
+        def getfile(self, fname):
+            data, (islink, isexec) = super(svnbackend, self).getfile(fname)
+            if islink:
+                data = 'link ' + data
+            return data, (islink, isexec)
+except AttributeError:
+    svnbackend = None
+
+def patchrepo(ui, meta, parentctx, patchfp):
+    if not svnbackend:
+        return patchrepoold(ui, meta, parentctx, patchfp)
+    store = patch.filestore()
+    try:
+        touched = set()
+        backend = svnbackend(ui, meta.repo, parentctx, store)
+        ret = patch.patchbackend(ui, backend, patchfp, 0, touched)
+        if ret < 0:
+            raise BadPatchApply('patching failed')
+        if ret > 0:
+            raise BadPatchApply('patching succeeded with fuzz')
+        files = {}
+        for f in touched:
+            try:
+                data, mode, copied = store.getfile(f)
+                files[f] = data
+            except IOError:
+                files[f] = None
+        return files
+    finally:
+        store.close()
+
+def diff_branchrev(ui, svn, meta, branch, branchpath, r, parentctx):
+    """Extract all 'branch' content at a given revision.
+
+    Return a tuple (files, filectxfn) where 'files' is the list of all files
+    in the branch at the given revision, and 'filectxfn' is a memctx compatible
+    callable to retrieve individual file information. Raise BadPatchApply upon
+    error.
+    """
+    try:
+        prev, pbranch, ppath = meta.get_source_rev(ctx=parentctx)
+    except KeyError:
+        prev, pbranch, ppath = None, None, None
+    try:
+        if prev is None or pbranch == branch:
+            # letting patch handle binaries sounded
+            # cool, but it breaks patch in sad ways
+            d = svn.get_unified_diff(branchpath, r.revnum, deleted=False,
+                                     ignore_type=False)
+        else:
+            d = svn.get_unified_diff(branchpath, r.revnum,
+                                     other_path=ppath, other_rev=prev,
+                                     deleted=True, ignore_type=True)
+            if d:
+                raise BadPatchApply('branch creation with mods')
+    except svnwrap.SubversionRepoCanNotDiff:
+        raise BadPatchApply('subversion diffing code is not supported')
+    except svnwrap.SubversionException, e:
+        if len(e.args) > 1 and e.args[1] != svnwrap.ERR_FS_NOT_FOUND:
+            raise
+        raise BadPatchApply('previous revision does not exist')
+    if '\0' in d:
+        raise BadPatchApply('binary diffs are not supported')
+    files_data = {}
+    # we have to pull each binary file by hand as a fulltext,
+    # which sucks but we've got no choice
+    binary_files = set(binary_file_re.findall(d))
+    touched_files = set(binary_files)
+    d2 = empty_file_patch_wont_make_re.sub('', d)
+    d2 = property_exec_set_re.sub('', d2)
+    d2 = property_exec_removed_re.sub('', d2)
+    # Here we ensure that all files, including the new empty ones
+    # are marked as touched. Content is loaded on demand.
+    touched_files.update(any_file_re.findall(d))
+    if d2.strip() and len(re.findall('\n[-+]', d2.strip())) > 0:
+        files_data = patchrepo(ui, meta, parentctx, cStringIO.StringIO(d2))
+        for x in files_data.iterkeys():
+            ui.note('M  %s\n' % x)
+    else:
+        ui.status('Not using patch for %s, diff had no hunks.\n' %
+                  r.revnum)
+
+    exec_files = {}
+    for m in property_exec_removed_re.findall(d):
+        exec_files[m] = False
+    for m in property_exec_set_re.findall(d):
+        exec_files[m] = True
+    touched_files.update(exec_files)
+    link_files = {}
+    for m in property_special_set_re.findall(d):
+        # TODO(augie) when a symlink is removed, patching will fail.
+        # We're seeing that above - there's gotta be a better
+        # workaround than just bailing like that.
+        assert m in files_data
+        link_files[m] = True
+    for m in property_special_removed_re.findall(d):
+        assert m in files_data
+        link_files[m] = False
+
+    unknown_files = set()
+    for p in r.paths:
+        action = r.paths[p].action
+        if not p.startswith(branchpath) or action not in 'DR':
+            continue
+        if branchpath:
+            p2 = p[len(branchpath)+1:].strip('/')
+        else:
+            p2 = p
+        if p2 in parentctx:
+            toucheds = [p2]
+        else:
+            # If this isn't in the parent ctx, it must've been a dir
+            toucheds = [f for f in parentctx if f.startswith(p2 + '/')]
+        if action == 'R':
+            # Files were replaced, we don't know if they still exist
+            unknown_files.update(toucheds)
+        else:
+            files_data.update((f, None) for f in toucheds)
+
+    touched_files.update(files_data)
+    touched_files.update(unknown_files)
+
+    copies = getcopies(svn, meta, branch, branchpath, r, touched_files,
+                       parentctx)
+
+    def filectxfn(repo, memctx, path):
+        if path in files_data and files_data[path] is None:
+            raise IOError(errno.ENOENT, '%s is deleted' % 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)
+            isexe = 'x' in mode
+            islink = 'l' in mode
+        else:
+            isexe = exec_files.get(path, 'x' in parentctx.flags(path))
+            islink = link_files.get(path, 'l' in parentctx.flags(path))
+            data = ''
+            if path in files_data:
+                data = files_data[path]
+                if islink:
+                    data = data[len('link '):]
+            elif path in parentctx:
+                data = parentctx[path].data()
+
+        copied = copies.get(path)
+        # TODO this branch feels like it should not be required,
+        # and this may actually imply a bug in getcopies
+        if copied not in parentctx.manifest():
+            copied = None
+        return context.memfilectx(path=path, data=data, islink=islink,
+                                  isexec=isexe, copied=copied)
+
+    return list(touched_files), filectxfn
+
+def makecopyfinder(meta, r, branchpath):
+    """Return a function detecting copies.
+
+    Returned copyfinder(path) returns None if no copy information can
+    be found or ((source, sourcerev), sourcepath) where "sourcepath" is the
+    copy source path, "sourcerev" the source svn revision and "source" is the
+    copy record path causing the copy to occur. If a single file was copied
+    "sourcepath" and "source" are the same, while file copies dectected from
+    directory copies return the copied source directory in "source".
+    """
+    # cache changeset contexts and map them to source svn revisions
+    ctxs = {}
+    def getctx(branch, svnrev):
+        if svnrev in ctxs:
+            return ctxs[svnrev]
+        changeid = meta.get_parent_revision(svnrev + 1, branch, True)
+        ctx = None
+        if changeid != revlog.nullid:
+            ctx = meta.repo.changectx(changeid)
+        ctxs[svnrev] = ctx
+        return ctx
+
+    # filter copy information for current branch
+    branchpath = (branchpath and branchpath + '/') or ''
+    copies = []
+    for path, e in r.paths.iteritems():
+        if not e.copyfrom_path:
+            continue
+        if not path.startswith(branchpath):
+            continue
+        # compute converted source path and revision
+        frompath, frombranch = meta.split_branch_path(e.copyfrom_path)[:2]
+        if frompath is None:
+            continue
+        fromctx = getctx(frombranch, e.copyfrom_rev)
+        if fromctx is None:
+            continue
+        destpath = path[len(branchpath):]
+        copies.append((destpath, (frompath, fromctx)))
+
+    copies.sort(reverse=True)
+    exactcopies = dict(copies)
+
+    def finder(path):
+        if path in exactcopies:
+            return exactcopies[path], exactcopies[path][0]
+        # look for parent directory copy, longest first
+        for dest, (source, sourcectx) in copies:
+            dest = dest + '/'
+            if not path.startswith(dest):
+                continue
+            sourcepath = source + '/' + path[len(dest):]
+            return (source, sourcectx), sourcepath
+        return None
+
+    return finder
+
+def getcopies(svn, meta, branch, branchpath, r, files, parentctx):
+    """Return a mapping {dest: source} for every file copied into r.
+    """
+    if parentctx.node() == revlog.nullid:
+        return {}
+
+    # Extract svn copy information, group them by copy source.
+    # The idea is to duplicate the replay behaviour where copies are
+    # evaluated per copy event (one event for all files in a directory copy,
+    # one event for single file copy). We assume that copy events match
+    # copy sources in revision info.
+    svncopies = {}
+    finder = makecopyfinder(meta, r, branchpath)
+    for f in files:
+        copy = finder(f)
+        if copy:
+            svncopies.setdefault(copy[0], []).append((f, copy[1]))
+    if not svncopies:
+        return {}
+
+    # check svn copies really make sense in mercurial
+    hgcopies = {}
+    for (sourcepath, sourcectx), copies in svncopies.iteritems():
+        for k, v in copies:
+            if not util.issamefile(sourcectx, parentctx, v):
+                continue
+            hgcopies.update({k: v})
+    return hgcopies
+
+def fetch_externals(ui, svn, branchpath, r, parentctx):
+    """Extract svn:externals for the current revision and branch
+
+    Return an externalsfile instance or None if there are no externals
+    to convert and never were.
+    """
+    externals = svnexternals.parse(ui, parentctx)
+    # Detect property additions only, changes are handled by checking
+    # existing entries individually. Projects are unlikely to store
+    # externals on many different root directories, so we trade code
+    # duplication and complexity for a constant lookup price at every
+    # revision in the common case.
+    dirs = set(externals)
+    if parentctx.node() == revlog.nullid:
+        dirs.update([p for p,k in svn.list_files(branchpath, r.revnum) if k == 'd'])
+        dirs.add('')
+    else:
+        branchprefix = (branchpath and branchpath + '/') or branchpath
+        for path, e in r.paths.iteritems():
+            if e.action == 'D':
+                continue
+            if not path.startswith(branchprefix) and path != branchpath:
+                continue
+            kind = svn.checkpath(path, r.revnum)
+            if kind != 'd':
+                continue
+            path = path[len(branchprefix):]
+            dirs.add(path)
+            if e.action == 'M' or (e.action == 'A' and e.copyfrom_path):
+                # Do not recurse in copied directories, changes are marked
+                # as 'M', except for the copied one.
+                continue
+            for child, k in svn.list_files(branchprefix + path, r.revnum):
+                if k == 'd':
+                    dirs.add((path + '/' + child).strip('/'))
+
+    # Retrieve new or updated values
+    for dir in dirs:
+        try:
+            dpath = (branchpath and branchpath + '/' + dir) or dir
+            values = svn.list_props(dpath, r.revnum)
+            externals[dir] = values.get('svn:externals', '')
+        except IOError:
+            externals[dir] = ''
+    return externals
+
+
+def fetch_branchrev(svn, meta, branch, branchpath, r, parentctx):
+    """Extract all 'branch' content at a given revision.
+
+    Return a tuple (files, filectxfn) where 'files' is the list of all files
+    in the branch at the given revision, and 'filectxfn' is a memctx compatible
+    callable to retrieve individual file information.
+    """
+    files = []
+    if parentctx.node() == revlog.nullid:
+        # Initial revision, fetch all files
+        for path, kind in svn.list_files(branchpath, r.revnum):
+            if kind == 'f':
+                files.append(path)
+    else:
+        branchprefix = (branchpath and branchpath + '/') or ''
+        for path, e in r.paths.iteritems():
+            if not path.startswith(branchprefix):
+                continue
+            if not meta.is_path_valid(path):
+                continue
+            kind = svn.checkpath(path, r.revnum)
+            path = path[len(branchprefix):]
+            if kind == 'f':
+                files.append(path)
+            elif kind == 'd':
+                if e.action == 'M':
+                    continue
+                dirpath = branchprefix + path
+                for child, k in svn.list_files(dirpath, r.revnum):
+                    if k == 'f':
+                        if path:
+                            childpath = '%s/%s' % (path, child)
+                        else:
+                            childpath = child
+                        files.append(childpath)
+                if e.action == 'R':
+                    # Check all files in replaced directory
+                    path = path + '/'
+                    files += [f for f in parentctx if f.startswith(path)]
+            else:
+                if path in parentctx:
+                    files.append(path)
+                    continue
+                # Assume it's a deleted directory
+                path = path + '/'
+                deleted = [f for f in parentctx if f.startswith(path)]
+                files += deleted
+
+    copies = getcopies(svn, meta, branch, branchpath, r, files, parentctx)
+
+    def filectxfn(repo, memctx, path):
+        svnpath = path
+        if branchpath:
+            svnpath = branchpath + '/' + path
+        data, mode = svn.get_file(svnpath, r.revnum)
+        isexec = 'x' in mode
+        islink = 'l' in mode
+        copied = copies.get(path)
+        # TODO this branch feels like it should not be required,
+        # and this may actually imply a bug in getcopies
+        if copied not in parentctx.manifest():
+            copied = None
+        return context.memfilectx(path=path, data=data, islink=islink,
+                                  isexec=isexec, copied=copied)
+
+    return files, filectxfn
+
+def checkbranch(meta, r, branch):
+    branchedits = meta.revmap.branchedits(branch, r)
+    if not branchedits:
+        return None
+    branchtip = branchedits[0][1]
+    for child in meta.repo[branchtip].children():
+        b = child.branch() != 'default' and child.branch() or None
+        if b == branch and child.extra().get('close'):
+            return None
+    return branchtip
+
+def branches_in_paths(meta, tbdelta, paths, revnum, checkpath, listdir):
+    '''Given a list of paths, return mapping of all branches touched
+    to their branch path.
+    '''
+    branches = {}
+    paths_need_discovery = []
+    for p in paths:
+        relpath, branch, branchpath = meta.split_branch_path(p)
+        if relpath is not None:
+            branches[branch] = branchpath
+        elif paths[p].action == 'D' and not meta.get_path_tag(p):
+            ln = meta.localname(p)
+            # must check in branches_to_delete as well, because this runs after we
+            # already updated the branch map
+            if ln in meta.branches or ln in tbdelta['branches'][1]:
+                branches[ln] = p
+        else:
+            paths_need_discovery.append(p)
+
+    if not paths_need_discovery:
+        return branches
+
+    paths_need_discovery = [(len(p), p) for p in paths_need_discovery]
+    paths_need_discovery.sort()
+    paths_need_discovery = [p[1] for p in paths_need_discovery]
+    actually_files = []
+    while paths_need_discovery:
+        p = paths_need_discovery.pop(0)
+        path_could_be_file = True
+        ind = 0
+        while ind < len(paths_need_discovery) and not paths_need_discovery:
+            if op.startswith(p):
+                path_could_be_file = False
+            ind += 1
+        if path_could_be_file:
+            if checkpath(p, revnum) == 'f':
+                actually_files.append(p)
+            # if there's a copyfrom_path and there were files inside that copyfrom,
+            # we need to detect those branches. It's a little thorny and slow, but
+            # seems to be the best option.
+            elif paths[p].copyfrom_path and not p.startswith('tags/'):
+                paths_need_discovery.extend(['%s/%s' % (p,x[0])
+                                             for x in listdir(p, revnum)
+                                             if x[1] == 'f'])
+
+        if not actually_files:
+            continue
+
+        filepaths = [p.split('/') for p in actually_files]
+        filepaths = [(len(p), p) for p in filepaths]
+        filepaths.sort()
+        filepaths = [p[1] for p in filepaths]
+        while filepaths:
+            path = filepaths.pop(0)
+            parentdir = '/'.join(path[:-1])
+            filepaths = [p for p in filepaths if not '/'.join(p).startswith(parentdir)]
+            branchpath = meta.normalize(parentdir)
+            if branchpath.startswith('tags/'):
+                continue
+            branchname = meta.localname(branchpath)
+            if branchpath.startswith('trunk/'):
+                branches[meta.localname('trunk')] = 'trunk'
+                continue
+            if branchname and branchname.startswith('../'):
+                continue
+            branches[branchname] = branchpath
+    return branches
+
+def convert_rev(ui, meta, svn, r, tbdelta, firstrun):
+    # this server fails at replay
+
+    branches = branches_in_paths(meta, tbdelta, r.paths, r.revnum,
+                                 svn.checkpath, svn.list_files)
+    brpaths = branches.values()
+    bad_branch_paths = {}
+    for br, bp in branches.iteritems():
+        bad_branch_paths[br] = []
+
+        # This next block might be needed, but for now I'm omitting it until it
+        # can be proven necessary.
+        # for bad in brpaths:
+        #     if bad.startswith(bp) and len(bad) > len(bp):
+        #         bad_branch_paths[br].append(bad[len(bp)+1:])
+
+        # We've go a branch that contains other branches. We have to be careful
+        # to get results similar to real replay in this case.
+        for existingbr in meta.branches:
+            bad = meta.remotename(existingbr)
+            if bad.startswith(bp) and len(bad) > len(bp):
+                bad_branch_paths[br].append(bad[len(bp)+1:])
+
+    deleted_branches = {}
+    for p in r.paths:
+        tag = meta.get_path_tag(p)
+        if tag and tag not in meta.tags:
+            continue
+        branch = meta.localname(p)
+        if not (r.paths[p].action == 'R' and branch in meta.branches):
+            continue
+        closed = checkbranch(meta, r, branch)
+        if closed is not None:
+            deleted_branches[branch] = closed
+
+    date = meta.fixdate(r.date)
+    check_deleted_branches = set(tbdelta['branches'][1])
+    for b in branches:
+        parentctx = meta.repo[meta.get_parent_revision(r.revnum, b)]
+        tag = meta.get_path_tag(meta.remotename(b))
+        kind = svn.checkpath(branches[b], r.revnum)
+        if kind != 'd':
+            if not tag:
+                # Branch does not exist at this revision. Get parent
+                # revision and remove everything.
+                deleted_branches[b] = parentctx.node()
+            continue
+
+        incremental = (meta.revmap.oldest > 0)
+
+        if incremental:
+            try:
+                files_touched, filectxfn2 = diff_branchrev(
+                    ui, svn, meta, b, branches[b], r, parentctx)
+            except BadPatchApply, e:
+                # Either this revision or the previous one does not exist.
+                ui.note("Fetching entire revision: %s.\n" % e.args[0])
+                incremental = False
+        if not incremental:
+            files_touched, filectxfn2 = fetch_branchrev(
+                svn, meta, b, branches[b], r, parentctx)
+
+        externals = {}
+        if meta.layout != 'single':
+            externals = fetch_externals(ui, svn, branches[b], r, parentctx)
+            externals = svnexternals.getchanges(ui, meta.repo, parentctx,
+                                                externals)
+            files_touched.extend(externals)
+
+        def filectxfn(repo, memctx, path):
+            if path in externals:
+                if externals[path] is None:
+                    raise IOError(errno.ENOENT, 'no externals')
+                return context.memfilectx(path=path, data=externals[path],
+                                          islink=False, isexec=False,
+                                          copied=None)
+            for bad in bad_branch_paths[b]:
+                if path.startswith(bad):
+                    raise IOError(errno.ENOENT, 'Path %s is bad' % path)
+            return filectxfn2(repo, memctx, path)
+
+        if '' in files_touched:
+            files_touched.remove('')
+        excluded = [f for f in files_touched if f not in meta.filemap]
+        for f in excluded:
+            files_touched.remove(f)
+
+        if b:
+            # Regular tag without modifications, it will be committed by
+            # svnmeta.committag(), we can skip the whole branch for now
+            if (tag and tag not in meta.tags and
+                b not in meta.branches
+                and b not in meta.repo.branchtags()
+                and not files_touched):
+                continue
+
+        if parentctx.node() == node.nullid and not files_touched:
+            meta.repo.ui.debug('skipping commit since parent is null and no files touched.\n')
+            continue
+
+        for f in files_touched:
+            if f:
+                # this is a case that really shouldn't ever happen, it means
+                # something is very wrong
+                assert f[0] != '/'
+
+        extra = meta.genextra(r.revnum, b)
+        if tag:
+            if parentctx.node() == node.nullid:
+                continue
+            extra.update({'branch': parentctx.extra().get('branch', None),
+                          'close': 1})
+
+        origbranch = extra.get('branch', None)
+        meta.mapbranch(extra)
+        current_ctx = context.memctx(meta.repo,
+                                     [parentctx.node(), revlog.nullid],
+                                     r.message or util.default_commit_msg(ui),
+                                     files_touched,
+                                     filectxfn,
+                                     meta.authors[r.author],
+                                     date,
+                                     extra)
+        ha = meta.repo.commitctx(current_ctx)
+
+        if not tag:
+            if (not origbranch in meta.branches
+                and not meta.get_path_tag(meta.remotename(origbranch))):
+                meta.branches[origbranch] = None, 0, r.revnum
+            meta.revmap[r.revnum, b] = ha
+        else:
+            meta.movetag(tag, ha, r, date)
+            meta.addedtags.pop(tag, None)
+        util.describe_commit(ui, ha, b)
+
+    # These are branches with an 'R' status in svn log. This means they were
+    # replaced by some other branch, so we need to verify they get marked as closed.
+    for branch in check_deleted_branches:
+        closed = checkbranch(meta, r, branch)
+        if closed is not None:
+            deleted_branches[branch] = closed
+
+    return deleted_branches
diff --git a/opt/hgsubversion/hgsubversion/svncommands.py b/opt/hgsubversion/hgsubversion/svncommands.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svncommands.py
@@ -0,0 +1,505 @@
+import os
+import posixpath
+import cPickle as pickle
+import sys
+import traceback
+
+from mercurial import commands
+from mercurial import hg
+from mercurial import node
+from mercurial import util as hgutil
+from mercurial import error
+
+import maps
+import svnwrap
+import svnrepo
+import util
+import svnexternals
+
+
+def verify(ui, repo, args=None, **opts):
+    '''verify current revision against Subversion repository
+    '''
+
+    if repo is None:
+        raise error.RepoError("There is no Mercurial repository"
+                              " here (.hg not found)")
+
+    ctx = repo[opts.get('rev', '.')]
+    if 'close' in ctx.extra():
+        ui.write('cannot verify closed branch')
+        return 0
+    convert_revision = ctx.extra().get('convert_revision')
+    if convert_revision is None or not convert_revision.startswith('svn:'):
+        raise hgutil.Abort('revision %s not from SVN' % ctx)
+
+    if args:
+        url = repo.ui.expandpath(args[0])
+    else:
+        url = repo.ui.expandpath('default')
+
+    svn = svnrepo.svnremoterepo(ui, url).svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+    srev, branch, branchpath = meta.get_source_rev(ctx=ctx)
+
+    branchpath = branchpath[len(svn.subdir.lstrip('/')):]
+
+    ui.write('verifying %s against r%i\n' % (ctx, srev))
+
+    svnfiles = set()
+    result = 0
+
+    for fn, type in svn.list_files(branchpath, srev):
+        if type != 'f':
+            continue
+        svnfiles.add(fn)
+        fp = fn
+        if branchpath:
+            fp = branchpath + '/'  + fn
+        data, mode = svn.get_file(posixpath.normpath(fp), srev)
+        fctx = ctx[fn]
+        dmatch = fctx.data() == data
+        mmatch = fctx.flags() == mode
+        if not (dmatch and mmatch):
+            ui.write('difference in file %s\n' % fn)
+            result = 1
+
+    hgfiles = set(ctx) - util.ignoredfiles
+    if hgfiles != svnfiles:
+        missing = set(hgfiles).symmetric_difference(svnfiles)
+        ui.write('missing files: %s\n' % (', '.join(missing)))
+        result = 1
+
+    return result
+
+
+def rebuildmeta(ui, repo, args, **opts):
+    """rebuild hgsubversion metadata using values stored in revisions
+    """
+
+    if repo is None:
+        raise error.RepoError("There is no Mercurial repository"
+                              " here (.hg not found)")
+
+    dest = None
+    if len(args) == 1:
+        dest = args[0]
+    elif len(args) > 1:
+        raise hgutil.Abort('rebuildmeta takes 1 or no arguments')
+    uuid = None
+    url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or
+                             repo.ui.config('paths', 'default') or '')
+    svn = svnrepo.svnremoterepo(ui, url).svn
+    subdir = svn.subdir
+    svnmetadir = os.path.join(repo.path, 'svn')
+    if not os.path.exists(svnmetadir):
+        os.makedirs(svnmetadir)
+
+    revmap = open(os.path.join(svnmetadir, 'rev_map'), 'w')
+    revmap.write('1\n')
+    last_rev = -1
+    branchinfo = {}
+    noderevnums = {}
+    tagfile = os.path.join(svnmetadir, 'tagmap')
+    if os.path.exists(maps.Tags.filepath(repo)):
+        os.unlink(maps.Tags.filepath(repo))
+    tags = maps.Tags(repo)
+
+    layout = None
+
+    skipped = set()
+    closed = set()
+
+    numrevs = len(repo)
+
+    subdirfile = open(os.path.join(svnmetadir, 'subdir'), 'w')
+    subdirfile.write(subdir.strip('/'))
+    subdirfile.close()
+
+    # ctx.children() visits all revisions in the repository after ctx. Calling
+    # it would make us use O(revisions^2) time, so we perform an extra traversal
+    # of the repository instead. During this traversal, we find all converted
+    # changesets that close a branch, and store their first parent
+    for rev in repo:
+        util.progress(ui, 'prepare', rev, total=numrevs)
+        ctx = repo[rev]
+        extra = ctx.extra()
+        convinfo = extra.get('convert_revision', None)
+
+        if not convinfo or not extra.get('close', None):
+            continue
+
+        droprev = lambda x: x.rsplit('@', 1)[0]
+        parentctx = ctx.parents()[0]
+        parentinfo = parentctx.extra().get('convert_revision', '@')
+
+        if droprev(parentinfo) == droprev(convinfo):
+            closed.add(parentctx.rev())
+
+    util.progress(ui, 'prepare', None, total=numrevs)
+
+    for rev in repo:
+        util.progress(ui, 'rebuild', rev, total=numrevs)
+        ctx = repo[rev]
+        convinfo = ctx.extra().get('convert_revision', None)
+        if not convinfo:
+            continue
+        if '.hgtags' in ctx.files():
+            parent = ctx.parents()[0]
+            parentdata = ''
+            if '.hgtags' in parent:
+                parentdata = parent.filectx('.hgtags').data()
+            newdata = ctx.filectx('.hgtags').data()
+            for newtag in newdata[len(parentdata):-1].split('\n'):
+                ha, tag = newtag.split(' ', 1)
+                tagged = repo[ha].extra().get('convert_revision', None)
+                if tagged is None:
+                    tagged = -1
+                else:
+                    tagged = int(tagged[40:].split('@')[1])
+                # This is max(tagged rev, tagging rev) because if it is a normal
+                # tag, the tagging revision has the right rev number. However, if it
+                # was an edited tag, then the tagged revision has the correct revision
+                # number.
+                tagging = int(convinfo[40:].split('@')[1])
+                tagrev = max(tagged, tagging)
+                tags[tag] = node.bin(ha), tagrev
+
+        # check that the conversion metadata matches expectations
+        assert convinfo.startswith('svn:')
+        revpath, revision = convinfo[40:].split('@')
+        if subdir and subdir[0] != '/':
+            subdir = '/' + subdir
+        if subdir and subdir[-1] == '/':
+            subdir = subdir[:-1]
+        assert revpath.startswith(subdir), ('That does not look like the '
+                                            'right location in the repo.')
+
+        if layout is None:
+            if (subdir or '/') == revpath:
+                layout = 'single'
+            else:
+                layout = 'standard'
+            f = open(os.path.join(svnmetadir, 'layout'), 'w')
+            f.write(layout)
+            f.close()
+        elif layout == 'single':
+            assert (subdir or '/') == revpath, ('Possible layout detection'
+                                                ' defect in replay')
+
+        # write repository uuid if required
+        if uuid is None:
+            uuid = convinfo[4:40]
+            assert uuid == svn.uuid, 'UUIDs did not match!'
+            uuidfile = open(os.path.join(svnmetadir, 'uuid'), 'w')
+            uuidfile.write(uuid)
+            uuidfile.close()
+
+        # don't reflect closed branches
+        if (ctx.extra().get('close') and not ctx.files() or
+            ctx.parents()[0].node() in skipped):
+            skipped.add(ctx.node())
+            continue
+
+        # find commitpath, write to revmap
+        commitpath = revpath[len(subdir)+1:]
+        if layout == 'standard':
+            if commitpath.startswith('branches/'):
+                commitpath = commitpath[len('branches/'):]
+            elif commitpath == 'trunk':
+                commitpath = ''
+            else:
+                if commitpath.startswith('tags/') and ctx.extra().get('close'):
+                    continue
+                commitpath = '../' + commitpath
+        else:
+            commitpath = ''
+        revmap.write('%s %s %s\n' % (revision, ctx.hex(), commitpath))
+
+        revision = int(revision)
+        noderevnums[ctx.node()] = revision
+        if revision > last_rev:
+            last_rev = revision
+
+        # deal with branches
+        if not commitpath:
+            branch = None
+        elif not commitpath.startswith('../'):
+            branch = commitpath
+        elif ctx.parents()[0].node() != node.nullid:
+            parent = ctx
+            while parent.node() != node.nullid:
+                parentextra = parent.extra()
+                parentinfo = parentextra.get('convert_revision')
+                assert parentinfo
+                parent = parent.parents()[0]
+
+                parentpath = parentinfo[40:].split('@')[0][len(subdir) + 1:]
+
+                if parentpath.startswith('tags/') and parentextra.get('close'):
+                    continue
+                elif parentpath.startswith('branches/'):
+                    branch = parentpath[len('branches/'):]
+                elif parentpath == 'trunk':
+                    branch = None
+                else:
+                    branch = '../' + parentpath
+                break
+        else:
+            branch = commitpath
+
+        if rev in closed:
+            # a direct child of this changeset closes the branch; drop it
+            branchinfo.pop(branch, None)
+        elif ctx.extra().get('close'):
+            pass
+        elif branch not in branchinfo:
+            parent = ctx.parents()[0]
+            if (parent.node() in noderevnums
+                and parent.branch() != ctx.branch()):
+                parentbranch = parent.branch()
+                if parentbranch == 'default':
+                    parentbranch = None
+            else:
+                parentbranch = None
+            branchinfo[branch] = (parentbranch,
+                                  noderevnums.get(parent.node(), 0),
+                                  revision)
+
+    util.progress(ui, 'rebuild', None, total=numrevs)
+
+    # save off branch info
+    branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w')
+    pickle.dump(branchinfo, branchinfofile)
+    branchinfofile.close()
+
+
+def help_(ui, args=None, **opts):
+    """show help for a given subcommands or a help overview
+    """
+    if args:
+        subcommand = args[0]
+        if subcommand not in table:
+            candidates = []
+            for c in table:
+                if c.startswith(subcommand):
+                    candidates.append(c)
+            if len(candidates) == 1:
+                subcommand = candidates[0]
+            elif len(candidates) > 1:
+                raise error.AmbiguousCommand(subcommand, candidates)
+                return
+        doc = table[subcommand].__doc__
+        if doc is None:
+            doc = "No documentation available for %s." % subcommand
+        ui.status(doc.strip(), '\n')
+        return
+    commands.help_(ui, 'svn')
+
+
+def update(ui, args, repo, clean=False, **opts):
+    """update to a specified Subversion revision number
+    """
+
+    try:
+        rev = int(args[0])
+    except IndexError:
+        raise error.CommandError('svn',
+                                 "no revision number specified for 'update'")
+    except ValueError:
+        raise error.Abort("'%s' is not a valid Subversion revision number"
+                          % args[0])
+
+    meta = repo.svnmeta()
+
+    answers = []
+    for k, v in meta.revmap.iteritems():
+        if k[0] == rev:
+            answers.append((v, k[1]))
+
+    if len(answers) == 1:
+        if clean:
+            return hg.clean(repo, answers[0][0])
+        return hg.update(repo, answers[0][0])
+    elif len(answers) == 0:
+        ui.status('revision %s did not produce an hg revision\n' % rev)
+        return 1
+    else:
+        ui.status('ambiguous revision!\n')
+        revs = ['%s on %s' % (node.hex(a[0]), a[1]) for a in answers] + ['']
+        ui.status('\n'.join(revs))
+    return 1
+
+
+def genignore(ui, repo, force=False, **opts):
+    """generate .hgignore from svn:ignore properties.
+    """
+
+    if repo is None:
+        raise error.RepoError("There is no Mercurial repository"
+                              " here (.hg not found)")
+
+    ignpath = repo.wjoin('.hgignore')
+    if not force and os.path.exists(ignpath):
+        raise hgutil.Abort('not overwriting existing .hgignore, try --force?')
+    svn = svnrepo.svnremoterepo(repo.ui).svn
+    meta = repo.svnmeta()
+    hashes = meta.revmap.hashes()
+    parent = util.parentrev(ui, repo, meta, hashes)
+    r, br = hashes[parent.node()]
+    if meta.layout == 'single':
+        branchpath = ''
+    else:
+        branchpath = br and ('branches/%s/' % br) or 'trunk/'
+    ignorelines = ['.hgignore', 'syntax:glob']
+    dirs = [''] + [d[0] for d in svn.list_files(branchpath, r)
+                   if d[1] == 'd']
+    for dir in dirs:
+        path = '%s%s' % (branchpath, dir)
+        props = svn.list_props(path, r)
+        if 'svn:ignore' not in props:
+            continue
+        lines = props['svn:ignore'].strip().split('\n')
+        ignorelines += [dir and (dir + '/' + prop) or prop for prop in lines]
+
+    repo.wopener('.hgignore', 'w').write('\n'.join(ignorelines) + '\n')
+
+
+def info(ui, repo, **opts):
+    """show Subversion details similar to `svn info'
+    """
+
+    if repo is None:
+        raise error.RepoError("There is no Mercurial repository"
+                              " here (.hg not found)")
+
+    meta = repo.svnmeta()
+    hashes = meta.revmap.hashes()
+
+    if opts.get('rev'):
+        parent = repo[opts['rev']]
+    else:
+        parent = util.parentrev(ui, repo, meta, hashes)
+
+    pn = parent.node()
+    if pn not in hashes:
+        ui.status('Not a child of an svn revision.\n')
+        return 0
+    r, br = hashes[pn]
+    subdir = parent.extra()['convert_revision'][40:].split('@')[0]
+    if meta.layout == 'single':
+        branchpath = ''
+    elif br == None:
+        branchpath = '/trunk'
+    elif br.startswith('../'):
+        branchpath = '/%s' % br[3:]
+        subdir = subdir.replace('branches/../', '')
+    else:
+        branchpath = '/branches/%s' % br
+    remoterepo = svnrepo.svnremoterepo(repo.ui)
+    url = '%s%s' % (remoterepo.svnurl, branchpath)
+    author = meta.authors.reverselookup(parent.user())
+    # cleverly figure out repo root w/o actually contacting the server
+    reporoot = url[:len(url)-len(subdir)]
+    ui.write('''URL: %(url)s
+Repository Root: %(reporoot)s
+Repository UUID: %(uuid)s
+Revision: %(revision)s
+Node Kind: directory
+Last Changed Author: %(author)s
+Last Changed Rev: %(revision)s
+Last Changed Date: %(date)s\n''' %
+              {'reporoot': reporoot,
+               'uuid': meta.uuid,
+               'url': url,
+               'author': author,
+               'revision': r,
+               # TODO I'd like to format this to the user's local TZ if possible
+               'date': hgutil.datestr(parent.date(),
+                                      '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
+              })
+
+
+def listauthors(ui, args, authors=None, **opts):
+    """list all authors in a Subversion repository
+    """
+    if not len(args):
+        ui.status('No repository specified.\n')
+        return
+    svn = svnrepo.svnremoterepo(ui, args[0]).svn
+    author_set = set()
+    for rev in svn.revisions():
+        if rev.author is None:
+            author_set.add('(no author)')
+        else:
+            author_set.add(rev.author)
+    if authors:
+        authorfile = open(authors, 'w')
+        authorfile.write('%s=\n' % '=\n'.join(sorted(author_set)))
+        authorfile.close()
+    else:
+        ui.write('%s\n' % '\n'.join(sorted(author_set)))
+
+
+def _helpgen():
+    ret = ['subcommands for Subversion integration', '',
+           'list of subcommands:', '']
+    for name, func in sorted(table.items()):
+        if func.__doc__:
+            short_description = func.__doc__.splitlines()[0]
+        else:
+            short_description = ''
+        ret.append(" :%s: %s" % (name, short_description))
+    return '\n'.join(ret) + '\n'
+
+def svn(ui, repo, subcommand, *args, **opts):
+    '''see detailed help for list of subcommands'''
+
+    # guess command if prefix
+    if subcommand not in table:
+        candidates = []
+        for c in table:
+            if c.startswith(subcommand):
+                candidates.append(c)
+        if len(candidates) == 1:
+            subcommand = candidates[0]
+        elif not candidates:
+            raise error.CommandError('svn',
+                                     "unknown subcommand '%s'" % subcommand)
+        else:
+            raise error.AmbiguousCommand(subcommand, candidates)
+
+    # override subversion credentials
+    for key in ('username', 'password'):
+        if key in opts:
+            ui.setconfig('hgsubversion', key, opts[key])
+
+    try:
+        commandfunc = table[subcommand]
+        return commandfunc(ui, args=args, repo=repo, **opts)
+    except svnwrap.SubversionConnectionException, e:
+        raise hgutil.Abort(*e.args)
+    except TypeError:
+        tb = traceback.extract_tb(sys.exc_info()[2])
+        if len(tb) == 1:
+            ui.status('Bad arguments for subcommand %s\n' % subcommand)
+        else:
+            raise
+    except KeyError, e:
+        tb = traceback.extract_tb(sys.exc_info()[2])
+        if len(tb) == 1:
+            ui.status('Unknown subcommand %s\n' % subcommand)
+        else:
+            raise
+
+table = {
+    'genignore': genignore,
+    'info': info,
+    'listauthors': listauthors,
+    'update': update,
+    'help': help_,
+    'rebuildmeta': rebuildmeta,
+    'updateexternals': svnexternals.updateexternals,
+    'verify': verify,
+}
+svn.__doc__ = _helpgen()
diff --git a/opt/hgsubversion/hgsubversion/svnexternals.py b/opt/hgsubversion/hgsubversion/svnexternals.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnexternals.py
@@ -0,0 +1,446 @@
+import cStringIO
+
+import os, re, shutil, stat, subprocess
+from mercurial import util as hgutil
+from mercurial.i18n import _
+
+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
+except (ImportError, AttributeError):
+    from mercurial import scmutil
+    canonpath = scmutil.canonpath    
+    passpegrev = False
+
+import util
+
+class externalsfile(dict):
+    """Map svn directories to lists of externals entries.
+    """
+    def __init__(self):
+        super(externalsfile, self).__init__()
+        self.encoding = 'utf-8'
+
+    def __setitem__(self, key, value):
+        if value is None:
+            value = []
+        elif isinstance(value, basestring):
+            value = value.splitlines()
+        if key == '.':
+            key = ''
+        if not value:
+            if key in self:
+                del self[key]
+        else:
+            super(externalsfile, self).__setitem__(key, value)
+
+    def write(self):
+        fp = cStringIO.StringIO()
+        for target in sorted(self):
+            lines = self[target]
+            if not lines:
+                continue
+            if not target:
+                target = '.'
+            fp.write('[%s]\n' % target)
+            for l in lines:
+                l = ' ' + l + '\n'
+                fp.write(l)
+        return fp.getvalue()
+
+    def read(self, data):
+        self.clear()
+        fp = cStringIO.StringIO(data)
+        dirs = {}
+        target = None
+        for line in fp.readlines():
+            if not line.strip():
+                continue
+            if line.startswith('['):
+                line = line.strip()
+                if line[-1] != ']':
+                    raise hgutil.Abort('invalid externals section name: %s' % line)
+                target = line[1:-1]
+                if target == '.':
+                    target = ''
+            elif line.startswith(' '):
+                line = line.rstrip('\n')
+                if target is None or not line:
+                    continue
+                self.setdefault(target, []).append(line[1:])
+
+def diff(ext1, ext2):
+    """Compare 2 externalsfile and return a list of tuples like (dir,
+    value1, value2) where value1 is the external value in ext1 for dir
+    or None, and value2 the same in ext2.
+    """
+    changes = []
+    for d in ext1:
+        if d not in ext2:
+            changes.append((d, '\n'.join(ext1[d]), None))
+        elif ext1[d] != ext2[d]:
+            changes.append((d, '\n'.join(ext1[d]), '\n'.join(ext2[d])))
+    for d in ext2:
+        if d not in ext1:
+            changes.append((d, None, '\n'.join(ext2[d])))
+    return changes
+
+class BadDefinition(Exception):
+    pass
+
+re_defold = re.compile(r'^\s*(.*?)\s+(?:-r\s*(\d+|\{REV\})\s+)?([a-zA-Z]+://.*)\s*$')
+re_defnew = re.compile(r'^\s*(?:-r\s*(\d+|\{REV\})\s+)?((?:[a-zA-Z]+://|\^/).*)\s+(\S+)\s*$')
+re_scheme = re.compile(r'^[a-zA-Z]+://')
+
+def parsedefinition(line):
+    """Parse an external definition line, return a tuple (path, rev, source)
+    or raise BadDefinition.
+    """
+    # The parsing is probably not correct wrt path with whitespaces or
+    # potential quotes. svn documentation is not really talkative about
+    # these either.
+    pegrev, revgroup = None, 1
+    m = re_defnew.search(line)
+    if m:
+        rev, source, path = m.group(1, 2, 3)
+        if '@' in source:
+            source, pegrev = source.rsplit('@', 1)
+    else:
+        m = re_defold.search(line)
+        if not m:
+            raise BadDefinition()
+        revgroup = 2
+        path, rev, source = m.group(1, 2, 3)
+    try:
+        nrev = int(rev)
+        norevline = line[:m.start(revgroup)] + '{REV}' + line[m.end(revgroup):]
+    except (TypeError, ValueError):
+        norevline = line
+    return (path, rev, source, pegrev, norevline)
+
+class RelativeSourceError(Exception):
+    pass
+
+def resolvesource(ui, svnroot, source):
+    if re_scheme.search(source):
+        return source
+    if source.startswith('^/'):
+        if svnroot is None:
+            raise RelativeSourceError()
+        return svnroot + source[1:]
+    ui.warn(_('ignoring unsupported non-fully qualified external: %r\n'
+              % source))
+    return None
+
+def parsedefinitions(ui, repo, svnroot, exts):
+    """Return (targetdir, revision, source) tuples. Fail if nested
+    targetdirs are detected. source is an svn project URL.
+    """
+    defs = []
+    for base in sorted(exts):
+        for line in exts[base]:
+            if not line.strip() or line.lstrip().startswith('#'):
+                # Ignore comments and blank lines
+                continue
+            try:
+                path, rev, source, pegrev, norevline = parsedefinition(line)
+            except BadDefinition:
+                ui.warn(_('ignoring invalid external definition: %r\n' % line))
+                continue
+            source = resolvesource(ui, svnroot, source)
+            if source is None:
+                continue
+            wpath = hgutil.pconvert(os.path.join(base, path))
+            wpath = canonpath(repo.root, '', wpath)
+            defs.append((wpath, rev, source, pegrev, norevline, base))
+    # Check target dirs are not nested
+    defs.sort()
+    for i, d in enumerate(defs):
+        for d2 in defs[i+1:]:
+            if d2[0].startswith(d[0] + '/'):
+                raise hgutil.Abort(_('external directories cannot nest:\n%s\n%s')
+                                   % (d[0], d2[0]))
+    return defs
+
+def computeactions(ui, repo, svnroot, ext1, ext2):
+
+    def listdefs(data):
+        defs = {}
+        exts = externalsfile()
+        exts.read(data)
+        for d in parsedefinitions(ui, repo, svnroot, exts):
+            defs[d[0]] = d
+        return defs
+
+    ext1 = listdefs(ext1)
+    ext2 = listdefs(ext2)
+    for wp1 in ext1:
+        if wp1 in ext2:
+            yield 'u', ext2[wp1]
+        else:
+            yield 'd', ext1[wp1]
+    for wp2 in ext2:
+        if wp2 not in ext1:
+            yield 'u', ext2[wp2]
+
+def getsvninfo(svnurl):
+    """Return a tuple (url, root) for supplied svn URL or working
+    directory path.
+    """
+    # Yes, this is ugly, but good enough for now
+    args = ['svn', 'info', '--xml', svnurl]
+    shell = os.name == 'nt'
+    p = subprocess.Popen(args, shell=shell,
+                         stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    stdout = p.communicate()[0]
+    if p.returncode:
+        raise hgutil.Abort(_('cannot get information about %s')
+                         % svnurl)
+    m = re.search(r'<root>(.*)</root>', stdout, re.S)
+    if not m:
+        raise hgutil.Abort(_('cannot find SVN repository root from %s')
+                           % svnurl)
+    root = m.group(1).rstrip('/')
+
+    m = re.search(r'<url>(.*)</url>', stdout, re.S)
+    if not m:
+        raise hgutil.Abort(_('cannot find SVN repository URL from %s') % svnurl)
+    url = m.group(1)
+
+    m = re.search(r'<entry[^>]+revision="([^"]+)"', stdout, re.S)
+    if not m:
+        raise hgutil.Abort(_('cannot find SVN revision from %s') % svnurl)
+    rev = m.group(1)
+    return url, root, rev
+
+class externalsupdater:
+    def __init__(self, ui, repo):
+        self.repo = repo
+        self.ui = ui
+
+    def update(self, wpath, rev, source, pegrev):
+        path = self.repo.wjoin(wpath)
+        revspec = []
+        if rev:
+            revspec = ['-r', rev]
+        if os.path.isdir(path):
+            exturl, extroot, extrev = getsvninfo(path)
+            # Comparing the source paths is not enough, but I don't
+            # know how to compare path+pegrev. The following update
+            # might fail if the path was replaced by another unrelated
+            # one. It can be fixed manually by deleting the externals
+            # and updating again.
+            if source == exturl:
+                if extrev != rev:
+                    self.ui.status(_('updating external on %s@%s\n') %
+                                   (wpath, rev or 'HEAD'))
+                    cwd = os.path.join(self.repo.root, path)
+                    self.svn(['update'] + revspec, cwd)
+                return
+            self.delete(wpath)
+        cwd, dest = os.path.split(path)
+        cwd = os.path.join(self.repo.root, cwd)
+        if not os.path.isdir(cwd):
+            os.makedirs(cwd)
+        if not pegrev and rev:
+            pegrev = rev
+        if pegrev:
+            source = '%s@%s' % (source, pegrev)
+        self.ui.status(_('fetching external %s@%s\n') % (wpath, rev or 'HEAD'))
+        self.svn(['co'] + revspec + [source, dest], cwd)
+
+    def delete(self, wpath):
+        path = self.repo.wjoin(wpath)
+        if os.path.isdir(path):
+            self.ui.status(_('removing external %s\n') % wpath)
+
+            def onerror(function, path, excinfo):
+                if function is not os.remove:
+                    raise
+                # read-only files cannot be unlinked under Windows
+                s = os.stat(path)
+                if (s.st_mode & stat.S_IWRITE) != 0:
+                    raise
+                os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
+                os.remove(path)
+
+            shutil.rmtree(path, onerror=onerror)
+            return 1
+
+    def svn(self, args, cwd):
+        args = ['svn'] + args
+        self.ui.debug(_('updating externals: %r, cwd=%s\n') % (args, cwd))
+        shell = os.name == 'nt'
+        p = subprocess.Popen(args, cwd=cwd, shell=shell,
+                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        for line in p.stdout:
+            self.ui.note(line)
+        p.wait()
+        if p.returncode != 0:
+            raise hgutil.Abort("subprocess '%s' failed" % ' '.join(args))
+
+def updateexternals(ui, args, repo, **opts):
+    """update repository externals
+    """
+    if len(args) > 2:
+        raise hgutil.Abort(_('updateexternals expects at most one changeset'))
+    node = None
+    if len(args) == 2:
+        svnurl = util.normalize_url(repo.ui.expandpath(args[0]))
+        args = args[1:]
+    else:
+        svnurl = util.normalize_url(repo.ui.expandpath('default'))
+    if args:
+        node = args[0]
+
+    svnroot = getsvninfo(svnurl)[1]
+
+    # Retrieve current externals status
+    try:
+        oldext = file(repo.join('svn/externals'), 'rb').read()
+    except IOError:
+        oldext = ''
+    newext = ''
+    ctx = repo[node]
+    if '.hgsvnexternals' in ctx:
+        newext = ctx['.hgsvnexternals'].data()
+
+    updater = externalsupdater(ui, repo)
+    actions = computeactions(ui, repo, svnroot, oldext, newext)
+    for action, ext in actions:
+        if action == 'u':
+            updater.update(ext[0], ext[1], ext[2], ext[3])
+        elif action == 'd':
+            updater.delete(ext[0])
+        else:
+            raise hgutil.Abort(_('unknown update actions: %r') % action)
+
+    file(repo.join('svn/externals'), 'wb').write(newext)
+
+def getchanges(ui, repo, parentctx, exts):
+    """Take a parent changectx and the new externals definitions as an
+    externalsfile and return a dictionary mapping the special file
+    hgsubversion needs for externals bookkeeping, to their new content
+    as raw bytes or None if the file has to be removed.
+    """
+    mode = ui.config('hgsubversion', 'externals', 'svnexternals')
+    if mode == 'svnexternals':
+        files = {
+            '.hgsvnexternals': None,
+            }
+        if exts:
+            files['.hgsvnexternals'] = exts.write()
+    elif mode == 'subrepos':
+        # XXX: clobering the subrepos files is good enough for now
+        files = {
+            '.hgsub': None,
+            '.hgsubstate': None,
+            }
+        if exts:
+            defs = parsedefinitions(ui, repo, '', exts)
+            hgsub, hgsubstate = [], []
+            for path, rev, source, pegrev, norevline, base in sorted(defs):
+                hgsub.append('%s = [hgsubversion] %s:%s\n'
+                             % (path, base, norevline))
+                if rev is None:
+                    rev = 'HEAD'
+                hgsubstate.append('%s %s\n' % (rev, path))
+            files['.hgsub'] = ''.join(hgsub)
+            files['.hgsubstate'] = ''.join(hgsubstate)
+    elif mode == 'ignore':
+        files = {}
+    else:
+        raise hgutil.Abort(_('unknown externals modes: %s') % mode)
+
+    # Should the really be updated?
+    updates = {}
+    for fn, data in files.iteritems():
+        if data is not None:
+            if fn not in parentctx or parentctx[fn].data() != data:
+                updates[fn] = data
+        else:
+            if fn in parentctx:
+                updates[fn] = None
+    return updates
+
+def parse(ui, ctx):
+    """Return the externals definitions stored in ctx as a (possibly empty)
+    externalsfile().
+    """
+    external = externalsfile()
+    mode = ui.config('hgsubversion', 'externals', 'svnexternals')
+    if mode == 'svnexternals':
+        if '.hgsvnexternals' in ctx:
+            external.read(ctx['.hgsvnexternals'].data())
+    elif mode == 'subrepos':
+        for path in ctx.substate:
+            src, rev = ctx.substate[path][:2]
+            base, norevline = src.split(':', 1)
+            base = base.strip()
+            if rev is None:
+                rev = 'HEAD'
+            line = norevline.replace('{REV}', rev)
+            external.setdefault(base, []).append(line)
+    elif mode == 'ignore':
+        pass
+    else:
+        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
diff --git a/opt/hgsubversion/hgsubversion/svnmeta.py b/opt/hgsubversion/hgsubversion/svnmeta.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnmeta.py
@@ -0,0 +1,730 @@
+import cPickle as pickle
+import posixpath
+import os
+import tempfile
+
+from mercurial import context
+from mercurial import util as hgutil
+from mercurial import revlog
+from mercurial import node
+
+import util
+import maps
+import editor
+
+
+def pickle_atomic(data, file_path, dir=None):
+    """pickle some data to a path atomically.
+
+    This is present because I kept corrupting my revmap by managing to hit ^C
+    during the pickle of that file.
+    """
+    try:
+        f, path = tempfile.mkstemp(prefix='pickling', dir=dir)
+        f = os.fdopen(f, 'w')
+        pickle.dump(data, f)
+        f.close()
+    except: #pragma: no cover
+        raise
+    else:
+        hgutil.rename(path, file_path)
+
+
+class SVNMeta(object):
+
+    def __init__(self, repo, uuid=None, subdir=None):
+        """path is the path to the target hg repo.
+
+        subdir is the subdirectory of the edits *on the svn server*.
+        It is needed for stripping paths off in certain cases.
+        """
+        self.ui = repo.ui
+        self.repo = repo
+        self.path = os.path.normpath(repo.join('..'))
+
+        if not os.path.isdir(self.meta_data_dir):
+            os.makedirs(self.meta_data_dir)
+        self.uuid = uuid
+        self.subdir = subdir
+        self.revmap = maps.RevMap(repo)
+
+        author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
+        authors = self.ui.config('hgsubversion', 'authormap')
+        tag_locations = self.ui.configlist('hgsubversion', 'tagpaths', ['tags'])
+        self.usebranchnames = self.ui.configbool('hgsubversion',
+                                                 'usebranchnames', True)
+        branchmap = self.ui.config('hgsubversion', 'branchmap')
+        tagmap = self.ui.config('hgsubversion', 'tagmap')
+
+        self.branches = {}
+        if os.path.exists(self.branch_info_file):
+            f = open(self.branch_info_file)
+            self.branches = pickle.load(f)
+            f.close()
+        self.prevbranches = dict(self.branches)
+        self.tags = maps.Tags(repo)
+        if os.path.exists(self.tag_locations_file):
+            f = open(self.tag_locations_file)
+            self.tag_locations = pickle.load(f)
+            f.close()
+        else:
+            self.tag_locations = tag_locations
+        if os.path.exists(self.layoutfile):
+            f = open(self.layoutfile)
+            self._layout = f.read().strip()
+            f.close()
+            self.repo.ui.setconfig('hgsubversion', 'layout', self._layout)
+        else:
+            self._layout = None
+        pickle_atomic(self.tag_locations, self.tag_locations_file,
+                      self.meta_data_dir)
+        # ensure nested paths are handled properly
+        self.tag_locations.sort()
+        self.tag_locations.reverse()
+
+        self.authors = maps.AuthorMap(self.ui, self.authors_file,
+                                 defaulthost=author_host)
+        if authors: self.authors.load(authors)
+
+        self.branchmap = maps.BranchMap(self.ui, self.branchmapfile)
+        if branchmap:
+            self.branchmap.load(branchmap)
+
+        self.tagmap = maps.TagMap(self.ui, self.tagmapfile)
+        if tagmap:
+            self.tagmap.load(tagmap)
+
+        self.lastdate = '1970-01-01 00:00:00 -0000'
+        self.filemap = maps.FileMap(repo)
+        self.addedtags = {}
+        self.deletedtags = {}
+
+    @property
+    def layout(self):
+        # this method can't determine the layout, but it needs to be
+        # resolved into something other than auto before this ever
+        # gets called
+        if not self._layout or self._layout == 'auto':
+            lo = self.repo.ui.config('hgsubversion', 'layout', default='auto')
+            if lo == 'auto':
+                raise hgutil.Abort('layout not yet determined')
+            self._layout = lo
+            f = open(self.layoutfile, 'w')
+            f.write(self._layout)
+            f.close()
+        return self._layout
+
+    @property
+    def editor(self):
+        if not hasattr(self, '_editor'):
+            self._editor = editor.HgEditor(self)
+        return self._editor
+
+    def _get_subdir(self):
+        return self.__subdir
+
+    def _set_subdir(self, subdir):
+        if subdir:
+            subdir = '/'.join(p for p in subdir.split('/') if p)
+
+        subdirfile = os.path.join(self.meta_data_dir, 'subdir')
+
+        if os.path.isfile(subdirfile):
+            stored_subdir = open(subdirfile).read()
+            assert stored_subdir is not None
+            if subdir is None:
+                self.__subdir = stored_subdir
+            elif 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()
+            self.__subdir = subdir
+        else:
+            raise hgutil.Abort("hgsubversion metadata unavailable; "
+                               "please run 'hg svn rebuildmeta'")
+
+    subdir = property(_get_subdir, _set_subdir, None,
+                    'Error-checked sub-directory of source Subversion '
+                    'repository.')
+
+    def _get_uuid(self):
+        return self.__uuid
+
+    def _set_uuid(self, uuid):
+        uuidfile = os.path.join(self.meta_data_dir, 'uuid')
+        if os.path.isfile(uuidfile):
+            stored_uuid = open(uuidfile).read()
+            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()
+            self.__uuid = uuid
+        else:
+            raise hgutil.Abort("hgsubversion metadata unavailable; "
+                               "please run 'hg svn rebuildmeta'")
+
+    uuid = property(_get_uuid, _set_uuid, None,
+                    'Error-checked UUID of source Subversion repository.')
+
+    @property
+    def meta_data_dir(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')
+
+    @property
+    def tag_locations_file(self):
+        return os.path.join(self.meta_data_dir, 'tag_locations')
+
+    @property
+    def authors_file(self):
+        return os.path.join(self.meta_data_dir, 'authors')
+
+    @property
+    def branchmapfile(self):
+        return os.path.join(self.meta_data_dir, 'branchmap')
+
+    @property
+    def tagmapfile(self):
+        # called tag-renames for backwards compatibility
+        return os.path.join(self.meta_data_dir, 'tag-renames')
+
+    @property
+    def layoutfile(self):
+        return os.path.join(self.meta_data_dir, 'layout')
+
+    def fixdate(self, date):
+        if date is not None:
+            date = date.replace('T', ' ').replace('Z', '').split('.')[0]
+            date += ' -0000'
+            self.lastdate = date
+        else:
+            date = self.lastdate
+        return date
+
+    def save(self):
+        '''Save the Subversion metadata. This should really be called after
+        every revision is created.
+        '''
+        pickle_atomic(self.branches, self.branch_info_file, self.meta_data_dir)
+
+    def localname(self, path):
+        """Compute the local name for a branch located at path.
+        """
+        if self.layout == 'single':
+            return 'default'
+        if path == 'trunk':
+            return None
+        elif path.startswith('branches/'):
+            return path[len('branches/'):]
+        return  '../%s' % path
+
+    def remotename(self, branch):
+        if self.layout == 'single':
+            return ''
+        if branch == 'default' or branch is None:
+            return 'trunk'
+        elif branch.startswith('../'):
+            return branch[3:]
+        return 'branches/%s' % branch
+
+    def genextra(self, revnum, branch):
+        extra = {}
+        subdir = self.subdir
+        if subdir and subdir[-1] == '/':
+            subdir = subdir[:-1]
+        if subdir and subdir[0] != '/':
+            subdir = '/' + subdir
+
+        if self.layout == 'single':
+            path = subdir or '/'
+        else:
+            branchpath = 'trunk'
+            if branch:
+                extra['branch'] = branch
+                if branch.startswith('../'):
+                    branchpath = branch[3:]
+                else:
+                    branchpath = 'branches/%s' % branch
+            path = '%s/%s' % (subdir , branchpath)
+
+        extra['convert_revision'] = 'svn:%(uuid)s%(path)s@%(rev)s' % {
+            'uuid': self.uuid,
+            'path': path,
+            'rev': revnum,
+        }
+        return extra
+
+    def mapbranch(self, extra, close=False):
+        if close:
+            extra['close'] = 1
+        mapped = self.branchmap.get(extra.get('branch', 'default'))
+        if not self.usebranchnames or mapped == 'default':
+            extra.pop('branch', None)
+        elif mapped:
+            extra['branch'] = mapped
+
+        if extra.get('branch') == 'default':
+            extra.pop('branch', None)
+
+    def normalize(self, path):
+        '''Normalize a path to strip of leading slashes and our subdir if we
+        have one.
+        '''
+        if self.subdir and path == self.subdir[:-1]:
+            return ''
+        if path and path[0] == '/':
+            path = path[1:]
+        if path and path.startswith(self.subdir):
+            path = path[len(self.subdir):]
+        if path and path[0] == '/':
+            path = path[1:]
+        return path
+
+    def get_path_tag(self, path):
+        """If path could represent the path to a tag, returns the
+        potential (non-empty) tag name. Otherwise, returns None
+
+        Note that it's only a tag if it was copied from the path '' in a branch
+        (or tag) we have, for our purposes.
+        """
+        if self.layout != 'single':
+            path = self.normalize(path)
+            for tagspath in self.tag_locations:
+                if path.startswith(tagspath + '/'):
+                    tag = path[len(tagspath) + 1:]
+                    if tag:
+                        return tag
+        return None
+
+    def split_branch_path(self, path, existing=True):
+        """Figure out which branch inside our repo this path represents, and
+        also figure out which path inside that branch it is.
+
+        Returns a tuple of (path within branch, local branch name, server-side
+        branch path).
+
+        Note that tag paths can also be matched: assuming tags/tag-1.1
+        is a tag then:
+        tags/tag-1.1 => ('', '../tags/tag-1.1', 'tags/tag-1.1')
+        tags/tag-1.1/file => ('file', '../tags/tag-1.1', 'tags/tag-1.1')
+        tags/tag-1.2 => (None, None, None)
+
+        If existing=True, will return None, None, None if the file isn't on
+        some known branch. If existing=False, then it will guess what the
+        branch would be if it were known. Server-side branch path should be
+        relative to our subdirectory.
+        """
+        path = self.normalize(path)
+        if self.layout == 'single':
+            return (path, None, '')
+        tag = self.get_path_tag(path)
+        if tag:
+            # consider the new tags when dispatching entries
+            matched = []
+            for tags in (self.tags, self.addedtags):
+                matched += [t for t in tags
+                            if (tag == t or tag.startswith(t + '/'))]
+            if not matched:
+                return None, None, None
+            matched.sort(key=len, reverse=True)
+            if tag == matched[0]:
+                brpath = ''
+                svrpath = path
+            else:
+                brpath = tag[len(matched[0])+1:]
+                svrpath = path[:-(len(brpath)+1)]
+            ln = self.localname(svrpath)
+            return brpath, ln, svrpath
+        test = ''
+        path_comps = path.split('/')
+        while self.localname(test) not in self.branches and len(path_comps):
+            if not test:
+                test = path_comps.pop(0)
+            else:
+                test += '/%s' % path_comps.pop(0)
+        if self.localname(test) in self.branches:
+            return path[len(test)+1:], self.localname(test), test
+        if existing:
+            return None, None, None
+        if path == 'trunk' or path.startswith('trunk/'):
+            path = path.split('/')[1:]
+            test = 'trunk'
+        elif path.startswith('branches/'):
+            elts = path.split('/')
+            test = '/'.join(elts[:2])
+            path = '/'.join(elts[2:])
+        else:
+            path = test.split('/')[-1]
+            test = '/'.join(test.split('/')[:-1])
+        ln =  self.localname(test)
+        if ln and ln.startswith('../'):
+            return None, None, None
+        return path, ln, test
+
+    def _determine_parent_branch(self, p, src_path, src_rev, revnum):
+        if src_path is not None:
+            src_file, src_branch = self.split_branch_path(src_path)[:2]
+            src_tag = self.get_path_tag(src_path)
+            if src_tag or src_file == '':
+                ln = self.localname(p)
+                if src_tag in self.tags:
+                    changeid = self.tags[src_tag]
+                    src_rev, src_branch = self.get_source_rev(changeid)[:2]
+                return {ln: (src_branch, src_rev, revnum)}
+        return {}
+
+    def is_path_valid(self, path):
+        if path is None:
+            return False
+        subpath = self.split_branch_path(path)[0]
+        if subpath is None:
+            return False
+        return subpath in self.filemap
+
+    def get_parent_svn_branch_and_rev(self, number, branch, exact=False):
+        """Return the parent revision of branch at number as a tuple
+        (parentnum, parentbranch) or (None, None) if undefined.
+
+        By default, current revision copy records will be used to resolve
+        the parent. For instance, if branch1 is replaced by branch2 in
+        current revision, then the parent of current revision on branch1
+        will be branch2. In this case, use exact=True to select the
+        existing branch before looking at the copy records.
+        """
+        if (number, branch) in self.revmap:
+            return number, branch
+        real_num = 0
+        for num, br in self.revmap.iterkeys():
+            if br != branch:
+                continue
+            if num <= number and num > real_num:
+                real_num = num
+        if branch in self.branches:
+            parent_branch = self.branches[branch][0]
+            parent_branch_rev = self.branches[branch][1]
+            # check to see if this branch already existed and is the same
+            if parent_branch_rev < real_num:
+                return real_num, branch
+            # if that wasn't true, then this is the a new branch with the
+            # same name as some old deleted branch
+            if parent_branch_rev <= 0 and real_num == 0:
+                return None, None
+            branch_created_rev = self.branches[branch][2]
+            if parent_branch == 'trunk':
+                parent_branch = None
+            if branch_created_rev <= number+1 and branch != parent_branch:
+                # did the branch exist in previous run
+                if exact and branch in self.prevbranches:
+                    if self.prevbranches[branch][1] < real_num:
+                        return real_num, branch
+                return self.get_parent_svn_branch_and_rev(
+                    parent_branch_rev, parent_branch)
+        if real_num != 0:
+            return real_num, branch
+        return None, None
+
+    def get_parent_revision(self, number, branch, exact=False):
+        '''Get the parent revision hash for a commit on a specific branch.
+        '''
+        tag = self.get_path_tag(self.remotename(branch))
+        if tag:
+            # Reference a tag being created
+            if tag in self.addedtags:
+                tbranch, trev = self.addedtags[tag]
+                fromtag = self.get_path_tag(self.remotename(tbranch))
+                if not fromtag:
+                    # Created from a regular branch, not another tag
+                    tagged = self.get_parent_svn_branch_and_rev(trev, tbranch)
+                    return node.hex(self.revmap[tagged])
+                tag = fromtag
+            # Reference an existing tag
+            limitedtags = maps.Tags(self.repo, endrev=number-1)
+            if tag in limitedtags:
+                return limitedtags[tag]
+        r, br = self.get_parent_svn_branch_and_rev(number - 1, branch, exact)
+        if r is not None:
+            return self.revmap[r, br]
+        return revlog.nullid
+
+    def get_source_rev(self, changeid=None, ctx=None):
+        """Return the source svn revision, the branch name and the svn
+        branch path or a converted changeset. If supplied revision
+        has no conversion record, raise KeyError.
+
+        If ctx is None, build one from supplied changeid
+        """
+        if ctx is None:
+            ctx = self.repo[changeid]
+        extra = ctx.extra()
+        if 'convert_revision' not in extra:
+            raise KeyError('%s has no conversion record' % ctx)
+        branchpath, revnum = extra['convert_revision'][40:].rsplit('@', 1)
+        branch = self.localname(self.normalize(branchpath))
+        if self.layout == 'single':
+            branchpath = ''
+        if branchpath and branchpath[0] == '/':
+            branchpath = branchpath[1:]
+        return int(revnum), branch, branchpath
+
+    def update_branch_tag_map_for_rev(self, revision):
+        """Given a revision object, determine changes to branches.
+
+        Returns: a dict of {
+            'branches': (added_branches, self.closebranches),
+        } where adds are dicts where the keys are branch names and
+        values are the place the branch came from. The deletions are
+        sets of the deleted branches.
+        """
+        if self.layout == 'single':
+            return {'branches': ({None: (None, 0, -1), }, set()),
+                    }
+        paths = revision.paths
+        added_branches = {}
+        # Reset the tags delta before detecting the new one, and take
+        # care not to fill them until done since split_branch_path()
+        # use them.
+        self.addedtags, self.deletedtags = {}, {}
+        addedtags, deletedtags = {}, {}
+        self.closebranches = set()
+        for p in sorted(paths):
+            t_name = self.get_path_tag(p)
+            if t_name:
+                src_p, src_rev = paths[p].copyfrom_path, paths[p].copyfrom_rev
+                if src_p is not None and src_rev is not None:
+                    file, branch = self.split_branch_path(src_p)[:2]
+                    from_tag = self.get_path_tag(src_p)
+                    if file is None and not from_tag:
+                        continue
+                    if from_tag and from_tag not in self.tags:
+                        # Ignore copies from unknown tags
+                        continue
+                    if not file:
+                        # Direct branch or tag copy
+                        if from_tag:
+                            changeid = self.tags[from_tag]
+                            src_rev, branch = self.get_source_rev(changeid)[:2]
+                        if t_name not in addedtags:
+                            addedtags[t_name] = branch, src_rev
+                    else:
+                        # Subbranch or subtag copy
+                        t_name = t_name[:-(len(file)+1)]
+                        found = t_name in addedtags
+                        if found and src_rev > addedtags[t_name][1]:
+                            addedtags[t_name] = branch, src_rev
+                elif (paths[p].action == 'D' and p.endswith(t_name)
+                      and t_name in self.tags):
+                    branch = self.get_source_rev(self.tags[t_name])[1]
+                    deletedtags[t_name] = branch, None
+                continue
+
+            # At this point we know the path is not a tag. In that
+            # case, we only care if it is the root of a new branch (in
+            # this function). This is determined by the following
+            # checks:
+            # 1. Is the file located inside any currently known
+            #    branch?  If yes, then we're done with it, this isn't
+            #    interesting.
+            # 2. Does the file have copyfrom information? If yes, then
+            #    we're done: this is a new branch, and we record the
+            #    copyfrom in added_branches if it comes from the root
+            #    of another branch, or create it from scratch.
+            # 3. Neither of the above. This could be a branch, but it
+            #    might never work out for us. It's only ever a branch
+            #    (as far as we're concerned) if it gets committed to,
+            #    which we have to detect at file-write time anyway. So
+            #    we do nothing here.
+            # 4. It's the root of an already-known branch, with an
+            #    action of 'D'. We mark the branch as deleted.
+            # 5. It's the parent directory of one or more
+            #    already-known branches, so we mark them as deleted.
+            # 6. It's a branch being replaced by another branch - the
+            #    action will be 'R'.
+            fi, br = self.split_branch_path(p)[:2]
+            if fi is not None:
+                if fi == '':
+                    if paths[p].action == 'D':
+                        self.closebranches.add(br) # case 4
+                    elif paths[p].action == 'R':
+                        parent = self._determine_parent_branch(
+                            p, paths[p].copyfrom_path, paths[p].copyfrom_rev,
+                            revision.revnum)
+                        added_branches.update(parent)
+                continue # case 1
+            if paths[p].action == 'D':
+                for known in self.branches:
+                    if self.remotename(known).startswith(p):
+                        self.closebranches.add(known) # case 5
+            parent = self._determine_parent_branch(
+                p, paths[p].copyfrom_path, paths[p].copyfrom_rev,
+                revision.revnum)
+            if not parent and paths[p].copyfrom_path:
+                bpath, branch = self.split_branch_path(p, False)[:2]
+                if (bpath is not None
+                    and branch not in self.branches
+                    and branch not in added_branches):
+                    parent = {branch: (None, 0, revision.revnum)}
+                elif bpath is None:
+                    srcpath = paths[p].copyfrom_path
+                    srcrev = paths[p].copyfrom_rev
+                    parent = {}
+                    for br in self.branches:
+                        rn = self.remotename(br)
+                        if rn.startswith(srcpath[1:] + '/'):
+                            bname = posixpath.basename(rn)
+                            newbr = posixpath.join(p, bname)
+                            parent.update(
+                                self._determine_parent_branch(
+                                    newbr, rn, srcrev, revision.revnum))
+            added_branches.update(parent)
+        self.addedtags, self.deletedtags = addedtags, deletedtags
+        return {
+            'branches': (added_branches, self.closebranches),
+        }
+
+    def save_tbdelta(self, tbdelta):
+        self.prevbranches = dict(self.branches)
+        for br in tbdelta['branches'][1]:
+            del self.branches[br]
+        self.branches.update(tbdelta['branches'][0])
+
+    def movetag(self, tag, hash, rev, date):
+        if tag in self.tags and self.tags[tag] == hash:
+            return
+
+        # determine branch from earliest unclosed ancestor
+        branchparent = self.repo[hash]
+        while branchparent.extra().get('close'):
+            branchparent = branchparent.parents()[0]
+        branch = self.get_source_rev(ctx=branchparent)[1]
+
+        parentctx = self.repo[self.get_parent_revision(rev.revnum+1, branch)]
+        if '.hgtags' in parentctx:
+            tagdata = parentctx.filectx('.hgtags').data()
+        else:
+            tagdata = ''
+        tagdata += '%s %s\n' % (node.hex(hash), self.tagmap.get(tag, tag))
+        def hgtagsfn(repo, memctx, path):
+            assert path == '.hgtags'
+            return context.memfilectx(path=path,
+                                      data=tagdata,
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        revnum, branch = self.get_source_rev(ctx=parentctx)[:2]
+        newparent = None
+        for child in parentctx.children():
+            if (self.get_source_rev(ctx=child)[1] == branch
+                and child.extra().get('close', False)):
+                newparent = child
+        if newparent:
+            parentctx = newparent
+            revnum, branch = self.get_source_rev(ctx=parentctx)[:2]
+        ctx = context.memctx(self.repo,
+                             (parentctx.node(), node.nullid),
+                             rev.message or util.default_commit_msg(self.ui),
+                             ['.hgtags', ],
+                             hgtagsfn,
+                             self.authors[rev.author],
+                             date,
+                             parentctx.extra())
+        new_hash = self.repo.commitctx(ctx)
+        if not newparent:
+            assert self.revmap[revnum, branch] == parentctx.node()
+            self.revmap[revnum, branch] = new_hash
+        self.tags[tag] = hash, rev.revnum
+        util.describe_commit(self.ui, new_hash, branch)
+
+    def committags(self, rev, endbranches):
+        if not self.addedtags and not self.deletedtags:
+            return
+        date = self.fixdate(rev.date)
+        # determine additions/deletions per branch
+        branches = {}
+        for tags in (self.addedtags, self.deletedtags):
+            for tag, (branch, srcrev) in tags.iteritems():
+                op = srcrev is None and 'rm' or 'add'
+                branches.setdefault(branch, []).append((op, tag, srcrev))
+
+        for b, tags in branches.iteritems():
+
+            # modify parent's .hgtags source
+
+            parent = self.repo[self.get_parent_revision(rev.revnum, b)]
+            if '.hgtags' not in parent:
+                src = ''
+            else:
+                src = parent['.hgtags'].data()
+
+            fromtag = self.get_path_tag(self.remotename(b))
+            for op, tag, r in sorted(tags, reverse=True):
+
+                if tag in self.tagmap and not self.tagmap[tag]:
+                    continue
+
+                tagged = node.hex(node.nullid) # op != 'add'
+                if op == 'add':
+                    if fromtag:
+                        if fromtag in self.tags:
+                            tagged = node.hex(self.tags[fromtag])
+                    else:
+                        tagged = node.hex(self.revmap[
+                            self.get_parent_svn_branch_and_rev(r, b)])
+
+                src += '%s %s\n' % (tagged, self.tagmap.get(tag, tag))
+                self.tags[tag] = node.bin(tagged), rev.revnum
+
+            # add new changeset containing updated .hgtags
+            def fctxfun(repo, memctx, path):
+                return context.memfilectx(path='.hgtags', data=src,
+                                          islink=False, isexec=False,
+                                          copied=None)
+
+            extra = self.genextra(rev.revnum, b)
+            if fromtag:
+                extra['branch'] = parent.extra().get('branch', 'default')
+            self.mapbranch(extra, b in endbranches or fromtag)
+
+            ctx = context.memctx(self.repo,
+                                 (parent.node(), node.nullid),
+                                 rev.message or ' ',
+                                 ['.hgtags'],
+                                 fctxfun,
+                                 self.authors[rev.author],
+                                 date,
+                                 extra)
+            new = self.repo.commitctx(ctx)
+
+            if not fromtag and (rev.revnum, b) not in self.revmap:
+                self.revmap[rev.revnum, b] = new
+            if b in endbranches:
+                endbranches.pop(b)
+                bname = b or 'default'
+                self.ui.status('Marked branch %s as closed.\n' % bname)
+
+    def delbranch(self, branch, node, rev):
+        pctx = self.repo[node]
+        files = pctx.manifest().keys()
+        extra = self.genextra(rev.revnum, branch)
+        self.mapbranch(extra, True)
+        ctx = context.memctx(self.repo,
+                             (node, revlog.nullid),
+                             rev.message or util.default_commit_msg(self.ui),
+                             [],
+                             lambda x, y, z: None,
+                             self.authors[rev.author],
+                             self.fixdate(rev.date),
+                             extra)
+        new = self.repo.commitctx(ctx)
+        self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
diff --git a/opt/hgsubversion/hgsubversion/svnrepo.py b/opt/hgsubversion/hgsubversion/svnrepo.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnrepo.py
@@ -0,0 +1,144 @@
+"""
+repository class-based interface for hgsubversion
+
+  Copyright (C) 2009, Dan Villiom Podlaski Christiansen <danchr@gmail.com>
+  See parent package for licensing.
+
+Internally, Mercurial assumes that every single repository is a localrepository
+subclass: pull() is called on the instance pull *to*, but not the one pulled
+*from*. To work around this, we create two classes:
+
+- svnremoterepo for Subversion repositories, but it doesn't really do anything.
+- svnlocalrepo for local repositories which handles both operations on itself --
+  the local, hgsubversion-enabled clone -- and the remote repository. Decorators
+  are used to distinguish and filter these operations from others.
+"""
+
+from mercurial import error
+from mercurial import util as hgutil
+from mercurial import httprepo
+import mercurial.repo
+
+import util
+import wrappers
+import svnwrap
+import svnmeta
+
+propertycache = hgutil.propertycache
+
+def generate_repo_class(ui, repo):
+    """ This function generates the local repository wrapper. """
+
+    superclass = repo.__class__
+
+    def remotesvn(fn):
+        """
+        Filter for instance methods which require the first argument
+        to be a remote Subversion repository instance.
+        """
+        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'):
+                return fn(self, *args, **opts)
+            else:
+                return original(*args, **opts)
+        wrapper.__name__ = fn.__name__ + '_wrapper'
+        wrapper.__doc__ = fn.__doc__
+        return wrapper
+
+    class svnlocalrepo(superclass):
+        # 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)
+
+        @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)
+
+    repo.__class__ = svnlocalrepo
+
+class svnremoterepo(mercurial.repo.repository):
+    """ the dumb wrapper for actual Subversion repositories """
+
+    def __init__(self, ui, path=None):
+        self.ui = ui
+        if path is None:
+            path = self.ui.config('paths', 'default')
+        if not path:
+            raise hgutil.Abort('no Subversion URL specified')
+        self.path = path
+        self.capabilities = set(['lookup', 'subversion'])
+
+    @propertycache
+    def svnauth(self):
+        # DO NOT default the user to hg's getuser(). If you provide
+        # *any* default username to Subversion, it won't use any remembered
+        # username for the desired realm, breaking OS X Keychain support,
+        # GNOME keyring support, and all similar tools.
+        user = self.ui.config('hgsubversion', 'username')
+        passwd = self.ui.config('hgsubversion', 'password')
+        url = util.normalize_url(self.path)
+        user, passwd, url = svnwrap.parse_url(url, user, passwd)
+        return url, user, passwd
+
+    @property
+    def svnurl(self):
+        return self.svn.svn_url
+
+    @propertycache
+    def svn(self):
+        try:
+            return svnwrap.SubversionRepo(*self.svnauth)
+        except svnwrap.SubversionConnectionException, e:
+            self.ui.traceback()
+            raise hgutil.Abort(e)
+
+    def url(self):
+        return self.path
+
+    def lookup(self, key):
+        return key
+
+    def cancopy(self):
+        return False
+
+    def heads(self, *args, **opts):
+        """
+        Whenever this function is hit, we abort. The traceback is useful for
+        figuring out where to intercept the functionality.
+        """
+        raise hgutil.Abort('command unavailable for Subversion repositories')
+
+    def pushkey(self, namespace, key, old, new):
+        return False
+
+    def listkeys(self, namespace):
+        return {}
+
+def instance(ui, url, create):
+    if url.startswith('http://') or url.startswith('https://'):
+        try:
+            # may yield a bogus 'real URL...' message
+            return httprepo.instance(ui, url, create)
+        except error.RepoError:
+            ui.traceback()
+            ui.note('(falling back to Subversion support)\n')
+
+    if create:
+        raise hgutil.Abort('cannot create new remote Subversion repository')
+
+    return svnremoterepo(ui, url)
diff --git a/opt/hgsubversion/hgsubversion/svnwrap/__init__.py b/opt/hgsubversion/hgsubversion/svnwrap/__init__.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnwrap/__init__.py
@@ -0,0 +1,36 @@
+"""This is a special package because it contains (or will contain, as of now)
+two parallel implementations of the same code. One implementation, the original,
+uses the SWIG Python bindings. That's great, but those leak RAM and have a few
+other quirks. The goal is to have this file automatically contain the "best"
+available implementation without the user having to configure what is actually
+present.
+"""
+
+from common import *
+
+import os
+
+choice = os.environ.get('HGSUBVERSION_BINDINGS', '').lower()
+
+if choice == 'subvertpy':
+    from subvertpy_wrapper import *
+elif choice == 'swig':
+    from svn_swig_wrapper import *
+elif choice == 'none':
+    # useful for verifying that demandimport works properly
+    raise ImportError('cannot use hgsubversion; '
+                      'bindings disabled using HGSUBVERSION_BINDINGS')
+else:
+    try:
+        from subvertpy_wrapper import *
+    except ImportError, e1:
+        try:
+            from svn_swig_wrapper import *
+        except ImportError, e2:
+            raise ImportError('no compatible bindings available:\n\n'
+                              '%s\n%s\n\n'
+                              'Please install either Subvertpy or the '
+                              'Subversion Python SWIG bindings'
+                              % (e2, e1))
+
+del os, choice
diff --git a/opt/hgsubversion/hgsubversion/svnwrap/common.py b/opt/hgsubversion/hgsubversion/svnwrap/common.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnwrap/common.py
@@ -0,0 +1,80 @@
+import cStringIO
+import getpass
+import errno
+import os
+import shutil
+import sys
+import tempfile
+import urlparse
+import urllib
+import collections
+
+class SubversionRepoCanNotReplay(Exception):
+    """Exception raised when the svn server is too old to have replay.
+    """
+
+class SubversionRepoCanNotDiff(Exception):
+    """Exception raised when the svn API diff3() command cannot be used
+    """
+
+class SubversionConnectionException(Exception):
+    """Exception raised when a generic error occurs when connecting to a
+       repository.
+    """
+
+# Default chunk size used in fetch_history_at_paths() and revisions().
+chunk_size = 1000
+
+def parse_url(url, user=None, passwd=None):
+    """Parse a URL and return a tuple (username, password, url)
+    """
+    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
+    if '@' in netloc:
+        userpass, netloc = netloc.split('@')
+        if not user and not passwd:
+            if ':' in userpass:
+                user, passwd = userpass.split(':')
+            else:
+                user, passwd = userpass, ''
+            user, passwd = urllib.unquote(user), urllib.unquote(passwd)
+    if user and scheme == 'svn+ssh':
+        netloc = '@'.join((user, netloc, ))
+    url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
+    return (user or None, passwd or None, url)
+
+
+class Revision(tuple):
+    """Wrapper for a Subversion revision.
+
+    Derives from tuple in an attempt to minimise the memory footprint.
+    """
+    def __new__(self, revnum, author, message, date, paths, strip_path=''):
+        _paths = {}
+        if paths:
+            for p in paths:
+                _paths[p[len(strip_path):]] = paths[p]
+        return tuple.__new__(self,
+                             (revnum, author, message, date, _paths))
+
+    @property
+    def revnum(self):
+        return self[0]
+
+    @property
+    def author(self):
+        return self[1]
+
+    @property
+    def message(self):
+        return self[2]
+
+    @property
+    def date(self):
+        return self[3]
+
+    @property
+    def paths(self):
+        return self[4]
+
+    def __str__(self):
+        return 'r%d by %s' % (self.revnum, self.author)
diff --git a/opt/hgsubversion/hgsubversion/svnwrap/subvertpy_wrapper.py b/opt/hgsubversion/hgsubversion/svnwrap/subvertpy_wrapper.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnwrap/subvertpy_wrapper.py
@@ -0,0 +1,534 @@
+import cStringIO
+import getpass
+import errno
+import os
+import shutil
+import sys
+import tempfile
+import urllib
+import collections
+
+import common
+
+import warnings
+warnings.filterwarnings('ignore',
+                        module='svn.core',
+                        category=DeprecationWarning)
+
+subvertpy_required = (0, 7, 4)
+subversion_required = (1, 5, 0)
+
+try:
+    from subvertpy import client
+    from subvertpy import delta
+    from subvertpy import properties
+    from subvertpy import ra
+    import subvertpy
+except ImportError:
+    raise ImportError('Subvertpy %d.%d.%d or later required, but not found'
+                      % subvertpy_required)
+
+def _versionstr(v):
+    return '.'.join(str(d) for d in v)
+
+if subvertpy.__version__ < subvertpy_required: #pragma: no cover
+    raise ImportError('Subvertpy %s or later required, '
+                      'but %s found'
+                      % (_versionstr(subvertpy_required),
+                         _versionstr(subvertpy.__version__)))
+
+subversion_version = subvertpy.wc.api_version()
+
+if subversion_version[:3] < subversion_required:
+    raise ImportError('Subversion %s or later required, '
+                      'but Subvertpy is using %s'
+                      % (_versionstr(subversion_required),
+                         _versionstr(subversion_version[:3])))
+
+
+def version():
+    svnvers = _versionstr(subversion_version[:3])
+    if subversion_version[3]:
+        svnvers += '-' + subversion_version[3]
+    return (svnvers, 'Subvertpy ' + _versionstr(subvertpy.__version__))
+
+# exported values
+ERR_FS_CONFLICT = subvertpy.ERR_FS_CONFLICT
+ERR_FS_NOT_FOUND = subvertpy.ERR_FS_NOT_FOUND
+ERR_FS_TXN_OUT_OF_DATE = subvertpy.ERR_FS_TXN_OUT_OF_DATE
+ERR_INCOMPLETE_DATA = subvertpy.ERR_INCOMPLETE_DATA
+ERR_RA_DAV_PATH_NOT_FOUND = subvertpy.ERR_RA_DAV_PATH_NOT_FOUND
+ERR_RA_DAV_REQUEST_FAILED = subvertpy.ERR_RA_DAV_REQUEST_FAILED
+SubversionException = subvertpy.SubversionException
+apply_txdelta = delta.apply_txdelta_handler
+# superclass for editor.HgEditor
+Editor = object
+
+def ieditor(fn):
+    """No-op decorator to identify methods used by the SVN editor interface.
+
+    This decorator is not needed for Subvertpy, but is retained for
+    compatibility with the SWIG bindings.
+    """
+
+    return fn
+
+_svntypes = {
+    subvertpy.NODE_DIR: 'd',
+    subvertpy.NODE_FILE: 'f',
+}
+
+class PathAdapter(object):
+    __slots__ = ('action', 'copyfrom_path', 'copyfrom_rev')
+
+    def __init__(self, path):
+        self.action, self.copyfrom_path, self.copyfrom_rev = path
+        if self.copyfrom_path:
+            self.copyfrom_path = intern(self.copyfrom_path)
+
+class AbstractEditor(object):
+    __slots__ = ('editor', )
+
+    def __init__(self, editor):
+        self.editor = editor
+
+    def set_target_revision(self, rev):
+        pass
+
+    def open_root(self, base_revnum):
+        return self.open_directory('', base_revnum)
+
+    def open_directory(self, path, base_revnum):
+        self.editor.open_directory(path, None, base_revnum)
+        return DirectoryEditor(self.editor, path)
+
+    def open_file(self, path, base_revnum):
+        self.editor.open_file(path, None, base_revnum)
+        return FileEditor(self.editor, path)
+
+    def add_directory(self, path, copyfrom_path=None, copyfrom_rev=-1):
+        self.editor.add_directory(path, None, copyfrom_path, copyfrom_rev)
+        return DirectoryEditor(self.editor, path)
+
+    def add_file(self, path, copyfrom_path=None, copyfrom_rev=-1):
+        self.editor.add_file(path, None, copyfrom_path, copyfrom_rev)
+        return FileEditor(self.editor, path)
+
+    def apply_textdelta(self, base_checksum):
+        return self.editor.apply_textdelta(self, None, base_checksum)
+
+    def change_prop(self, name, value):
+        raise NotImplementedError()
+
+    def abort(self):
+        # TODO: should we do something special here?
+        self.close()
+
+    def close(self):
+        del self.editor
+
+    def delete_entry(self, path, revnum):
+        self.editor.delete_entry(path, revnum, None)
+
+class FileEditor(AbstractEditor):
+    __slots__ = ('path', )
+
+    def __init__(self, editor, path):
+        super(FileEditor, self).__init__(editor)
+        self.path = path
+
+    def change_prop(self, name, value):
+        self.editor.change_file_prop(self.path, name, value, pool=None)
+
+    def close(self, checksum=None):
+        super(FileEditor, self).close()
+        del self.path
+
+class DirectoryEditor(AbstractEditor):
+    __slots__ = ('path', )
+
+    def __init__(self, editor, path):
+        super(DirectoryEditor, self).__init__(editor)
+        self.path = path
+
+    def change_prop(self, name, value):
+        self.editor.change_dir_prop(self.path, name, value, pool=None)
+
+    def close(self):
+        self.editor.close_directory(self.path)
+        super(DirectoryEditor, self).close()
+        del self.path
+
+class SubversionRepo(object):
+    """Wrapper for a Subversion repository.
+
+    This wrapper uses Subvertpy, an alternate set of bindings for Subversion
+    that's more pythonic and sucks less. See earlier in this file for version
+    requirements.
+    """
+    def __init__(self, url='', username='', password='', head=None):
+        parsed = common.parse_url(url, username, password)
+        # --username and --password override URL credentials
+        self.username = parsed[0]
+        self.password = parsed[1]
+        self.svn_url = parsed[2]
+
+        self.init_ra_and_client()
+
+        self.svn_url = self.remote.get_url()
+        self.uuid = self.remote.get_uuid()
+        self.root = self.remote.get_repos_root()
+        assert self.svn_url.startswith(self.root)
+
+        # *will* have a leading '/', would not if we used get_repos_root2
+        self.subdir = self.svn_url[len(self.root):]
+        if not self.subdir or self.subdir[-1] != '/':
+            self.subdir += '/'
+        # the RA interface always yields quoted paths, but the editor interface
+        # expects unquoted paths
+        self.subdir = urllib.unquote(self.subdir)
+        self.hasdiff3 = True
+
+    def init_ra_and_client(self):
+        """
+        Initializes the RA and client layers.
+
+        With the SWIG bindings, getting unified diffs runs the remote server
+        sometimes runs out of open files. It is not known whether the Subvertpy
+        is affected by this.
+        """
+        def getclientstring():
+            return 'hgsubversion'
+        # TODO: handle certificate authentication, Mercurial style
+        def getpass(realm, username, may_save):
+            return self.username or username, self.password or '', False
+        def getuser(realm, may_save):
+            return self.username or '', False
+
+        providers = ra.get_platform_specific_client_providers()
+        providers += [
+            ra.get_simple_provider(),
+            ra.get_username_provider(),
+            ra.get_ssl_client_cert_file_provider(),
+            ra.get_ssl_client_cert_pw_file_provider(),
+            ra.get_ssl_server_trust_file_provider(),
+            ra.get_username_prompt_provider(getuser, 0),
+            ra.get_simple_prompt_provider(getpass, 0),
+        ]
+
+        auth = ra.Auth(providers)
+        if self.username:
+            auth.set_parameter(subvertpy.AUTH_PARAM_DEFAULT_USERNAME, self.username)
+        if self.password:
+            auth.set_parameter(subvertpy.AUTH_PARAM_DEFAULT_PASSWORD, self.password)
+
+        self.remote = ra.RemoteAccess(url=self.svn_url,
+                                      client_string_func=getclientstring,
+                                      auth=auth)
+
+        self.client = client.Client()
+        self.client.auth = auth
+
+    @property
+    def HEAD(self):
+        return self.remote.get_latest_revnum()
+
+    @property
+    def last_changed_rev(self):
+        try:
+            holder = []
+            def callback(paths, revnum, props, haschildren):
+                holder.append(revnum)
+
+            self.remote.get_log(paths=[''],
+                                start=self.HEAD, end=1, limit=1,
+                                discover_changed_paths=False,
+                                callback=callback)
+
+            return holder[-1]
+        except SubversionException, e:
+            if e.args[0] == ERR_FS_NOT_FOUND:
+                raise
+            else:
+                return self.HEAD
+
+    def list_dir(self, path, revision=None):
+        """List the contents of a server-side directory.
+
+        Returns a dict-like object with one dict key per directory entry.
+
+        Args:
+          dir: the directory to list, no leading slash
+          rev: the revision at which to list the directory, defaults to HEAD
+        """
+        # TODO: reject leading slashes like the docstring says
+        if path:
+            path = path.rstrip('/') + '/'
+
+        r = self.remote.get_dir(path, revision or self.HEAD, ra.DIRENT_ALL)
+        dirents, fetched_rev, properties = r
+        return dirents
+
+    def revisions(self, paths=None, start=0, stop=0,
+                  chunk_size=common.chunk_size):
+        """Load the history of this repo.
+
+        This is LAZY. It returns a generator, and fetches a small number
+        of revisions at a time.
+
+        The reason this is lazy is so that you can use the same repo object
+        to perform RA calls to get deltas.
+        """
+        if paths is None:
+            paths = ['']
+        if not stop:
+            stop = self.HEAD
+        while stop > start:
+            def callback(paths, revnum, props, haschildren):
+                if paths is None:
+                    return
+                r = common.Revision(revnum,
+                             props.get(properties.PROP_REVISION_AUTHOR),
+                             props.get(properties.PROP_REVISION_LOG),
+                             props.get(properties.PROP_REVISION_DATE),
+                             dict([(k, PathAdapter(v))
+                                   for k, v in paths.iteritems()]),
+                             strip_path=self.subdir)
+                revisions.append(r)
+            # we only access revisions in a FIFO manner
+            revisions = collections.deque()
+
+            revprops = [properties.PROP_REVISION_AUTHOR,
+                        properties.PROP_REVISION_DATE,
+                        properties.PROP_REVISION_LOG]
+            try:
+                # TODO: using min(start + chunk_size, stop) may be preferable;
+                #       ra.get_log(), even with chunk_size set, takes a while
+                #       when converting the 65k+ rev. in LLVM.
+                self.remote.get_log(paths=paths, revprops=revprops,
+                                    start=start+1, end=stop, limit=chunk_size,
+                                    discover_changed_paths=True,
+                                    callback=callback)
+            except SubversionException, e:
+                if e.args[1] == ERR_FS_NOT_FOUND:
+                    msg = ('%s not found at revision %d!'
+                           % (self.subdir.rstrip('/'), stop))
+                    raise common.SubversionConnectionException(msg)
+                elif e.args[1] == subvertpy.ERR_FS_NO_SUCH_REVISION:
+                    raise common.SubversionConnectionException(e.args[0])
+                else:
+                    raise
+
+            while len(revisions) > 1:
+                yield revisions.popleft()
+
+            if len(revisions) == 0:
+                # exit the loop; there is no history for the path.
+                break
+            else:
+                r = revisions.popleft()
+                start = r.revnum
+                yield r
+
+    def commit(self, paths, message, file_data, base_revision, addeddirs,
+               deleteddirs, props, copies):
+        """Commits the appropriate targets from revision in editor's store.
+        """
+        def commitcb(*args):
+            commit_info.append(args)
+        commit_info = []
+        revprops = { properties.PROP_REVISION_LOG: message }
+        #revprops.update(props)
+        commiteditor = self.remote.get_commit_editor(revprops, commitcb)
+
+        paths = set(paths)
+        paths.update(addeddirs)
+        paths.update(deleteddirs)
+
+        # ensure that all parents are visited too; this may be slow
+        for path in paths.copy():
+            for i in xrange(path.count('/'), -1, -1):
+                p = path.rsplit('/', i)[0]
+                if p in paths:
+                    continue
+                paths.add(p)
+        paths = sorted(paths)
+
+        def visitdir(editor, directory, paths, pathidx):
+            while pathidx < len(paths):
+                path = paths[pathidx]
+                if directory and not path.startswith(directory + '/'):
+                    return pathidx
+
+                pathidx += 1
+
+                if path in file_data:
+                    # visiting a file
+                    base_text, new_text, action = file_data[path]
+                    if action == 'modify':
+                        fileeditor = editor.open_file(path, base_revision)
+                    elif action == 'add':
+                        frompath, fromrev = copies.get(path, (None, -1))
+                        if frompath:
+                            frompath = self.path2url(frompath)
+                        fileeditor = editor.add_file(path, frompath, fromrev)
+                    elif action == 'delete':
+                        editor.delete_entry(path, base_revision)
+                        continue
+                    else:
+                        assert False, 'invalid action \'%s\'' % action
+
+                    if path in props:
+                        if props[path].get('svn:special', None):
+                            new_text = 'link %s' % new_text
+                        for p, v in props[path].iteritems():
+                            fileeditor.change_prop(p, v)
+
+
+                    handler = fileeditor.apply_textdelta()
+                    delta.send_stream(cStringIO.StringIO(new_text), handler)
+                    fileeditor.close()
+
+                else:
+                    # visiting a directory
+                    if path in addeddirs:
+                        direditor = editor.add_directory(path)
+                    elif path in deleteddirs:
+                        direditor = editor.delete_entry(path, base_revision)
+                        continue
+                    else:
+                        direditor = editor.open_directory(path)
+
+                    if path in props:
+                        for p, v in props[path].iteritems():
+                            direditor.change_prop(p, v)
+
+                    pathidx = visitdir(direditor, path, paths, pathidx)
+                    direditor.close()
+
+            return pathidx
+
+        rooteditor = commiteditor.open_root()
+        visitdir(rooteditor, '', paths, 0)
+        rooteditor.close()
+        commiteditor.close()
+
+    def get_replay(self, revision, editor, oldestrev=0):
+
+        try:
+            self.remote.replay(revision, oldestrev, AbstractEditor(editor))
+        except (SubversionException, NotImplementedError), e: #pragma: no cover
+            # can I depend on this number being constant?
+            if (isinstance(e, NotImplementedError) or
+                e.args[1] == subvertpy.ERR_RA_NOT_IMPLEMENTED or
+                e.args[1] == subvertpy.ERR_UNSUPPORTED_FEATURE):
+                msg = ('This Subversion server is older than 1.4.0, and '
+                       'cannot satisfy replay requests.')
+                raise common.SubversionRepoCanNotReplay(msg)
+            else:
+                raise
+
+    def get_revision(self, revision, editor):
+        ''' feed the contents of the given revision to the given editor '''
+        reporter = self.remote.do_update(revision, '', True,
+                                         AbstractEditor(editor))
+        reporter.set_path('', revision, True)
+        reporter.finish()
+
+    def get_unified_diff(self, path, revision, other_path=None, other_rev=None,
+                         deleted=True, ignore_type=False):
+        """Gets a unidiff of path at revision against revision-1.
+        """
+
+        url = self.path2url(path)
+        url2 = (other_path and self.path2url(other_path) or url)
+
+        if other_rev is None:
+            other_rev = revision - 1
+
+        outfile, errfile = self.client.diff(other_rev, revision, url2, url,
+                                            no_diff_deleted=deleted,
+                                            ignore_content_type=ignore_type)
+        error = errfile.read()
+        assert not error, error
+
+        return outfile.read()
+
+    def get_file(self, path, revision):
+        """Return content and mode of file at given path and revision.
+
+        "link " prefix is dropped from symlink content. Mode is 'x' if
+        file is executable, 'l' if a symlink, the empty string
+        otherwise. If the file does not exist at this revision, raise
+        IOError.
+        """
+        mode = ''
+        try:
+            out = cStringIO.StringIO()
+            rev, info = self.remote.get_file(path, out, revision)
+            data = out.getvalue()
+            out.close()
+            if isinstance(info, list):
+                info = info[-1]
+            mode = (properties.PROP_EXECUTABLE in info) and 'x' or ''
+            mode = (properties.PROP_SPECIAL in info) and 'l' or mode
+        except SubversionException, e:
+            if e.args[1] in (ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND):
+                # File not found
+                raise IOError(errno.ENOENT, e.args[0])
+            raise
+        if mode  == 'l':
+            linkprefix = "link "
+            if data.startswith(linkprefix):
+                data = data[len(linkprefix):]
+        return data, mode
+
+    def list_props(self, path, revision):
+        """Return a mapping of property names to values, raise IOError if
+        specified path does not exist.
+        """
+        try:
+            pl = self.client.proplist(self.path2url(path), revision,
+                                      client.depth_empty)
+        except SubversionException, e:
+            # Specified path does not exist at this revision
+            if e.args[1] == subvertpy.ERR_NODE_UNKNOWN_KIND:
+                raise IOError(errno.ENOENT, e.args[0])
+            raise
+        return pl and pl[0][1] or {}
+
+    def list_files(self, dirpath, revision):
+        """List the content of a directory at a given revision, recursively.
+
+        Yield tuples (path, kind) where 'path' is the entry path relatively to
+        'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a
+        directory. Raise IOError if the directory cannot be found at given
+        revision.
+        """
+        try:
+            entries = self.client.list(self.path2url(dirpath), revision,
+                                       client.depth_infinity, ra.DIRENT_KIND)
+        except SubversionException, e:
+            if e.args[1] == subvertpy.ERR_FS_NOT_FOUND:
+                raise IOError(errno.ENOENT,
+                              '%s cannot be found at r%d' % (dirpath, revision))
+            raise
+        for path, e in entries.iteritems():
+            if not path: continue
+            kind = _svntypes.get(e['kind'])
+            yield path, kind
+
+    def checkpath(self, path, revision):
+        """Return the entry type at the given revision, 'f', 'd' or None
+        if the entry does not exist.
+        """
+        kind = self.remote.check_path(path, revision)
+        return _svntypes.get(kind)
+
+    def path2url(self, path):
+        """Build svn URL for path, URL-escaping path.
+        """
+        if not path or path == '.':
+            return self.svn_url
+        assert path[0] != '/', path
+        return '/'.join((self.svn_url, urllib.quote(path).rstrip('/'), ))
diff --git a/opt/hgsubversion/hgsubversion/svnwrap/svn_swig_wrapper.py b/opt/hgsubversion/hgsubversion/svnwrap/svn_swig_wrapper.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/svnwrap/svn_swig_wrapper.py
@@ -0,0 +1,562 @@
+import cStringIO
+import getpass
+import errno
+import os
+import shutil
+import sys
+import tempfile
+import urllib
+import collections
+
+import common
+
+import warnings
+warnings.filterwarnings('ignore',
+                        module='svn.core',
+                        category=DeprecationWarning)
+
+required_bindings = (1, 5, 0)
+
+try:
+    from svn import client
+    from svn import core
+    from svn import delta
+    from svn import ra
+
+    current_bindings = (core.SVN_VER_MAJOR, core.SVN_VER_MINOR,
+                        core.SVN_VER_MICRO)
+except ImportError:
+    raise ImportError('Subversion %d.%d.%d or later required, '
+                      'but no bindings were found' % required_bindings)
+
+if current_bindings < required_bindings: #pragma: no cover
+    raise ImportError('Subversion %d.%d.%d or later required, '
+                      'but bindings for %d.%d.%d found' %
+                      (required_bindings + current_bindings))
+
+def version():
+    return '%d.%d.%d' % current_bindings, 'SWIG'
+
+# exported values
+ERR_FS_CONFLICT = core.SVN_ERR_FS_CONFLICT
+ERR_FS_NOT_FOUND = core.SVN_ERR_FS_NOT_FOUND
+ERR_FS_TXN_OUT_OF_DATE = core.SVN_ERR_FS_TXN_OUT_OF_DATE
+ERR_INCOMPLETE_DATA = core.SVN_ERR_INCOMPLETE_DATA
+ERR_RA_DAV_REQUEST_FAILED = core.SVN_ERR_RA_DAV_REQUEST_FAILED
+SubversionException = core.SubversionException
+Editor = delta.Editor
+
+def apply_txdelta(base, target):
+    handler, baton = delta.svn_txdelta_apply(cStringIO.StringIO(base),
+                                             target, None)
+    return (lambda window: handler(window, baton))
+
+def optrev(revnum):
+    optrev = core.svn_opt_revision_t()
+    optrev.kind = core.svn_opt_revision_number
+    optrev.value.number = revnum
+    return optrev
+
+svn_config = core.svn_config_get_config(None)
+class RaCallbacks(ra.Callbacks):
+    @staticmethod
+    def open_tmp_file(pool): #pragma: no cover
+        (fd, fn) = tempfile.mkstemp()
+        os.close(fd)
+        return fn
+
+    @staticmethod
+    def get_client_string(pool):
+        return 'hgsubversion'
+
+def ieditor(fn):
+    """Helps identify methods used by the SVN editor interface.
+
+    Stash any exception raised in the method on self.
+
+    This is required because the SWIG bindings just mutate any exception into
+    a generic Subversion exception with no way of telling what the original was.
+    This allows the editor object to notice when you try and commit and really
+    got an exception in the replay process.
+    """
+    def fun(self, *args, **kwargs):
+        try:
+            return fn(self, *args, **kwargs)
+        except: #pragma: no cover
+            if self.current.exception is not None:
+                self.current.exception = sys.exc_info()
+            raise
+    return fun
+
+def user_pass_prompt(realm, default_username, ms, pool): #pragma: no cover
+    # FIXME: should use getpass() and username() from mercurial.ui
+    creds = core.svn_auth_cred_simple_t()
+    creds.may_save = ms
+    if default_username:
+        sys.stderr.write('Auth realm: %s\n' % (realm,))
+        creds.username = default_username
+    else:
+        sys.stderr.write('Auth realm: %s\n' % (realm,))
+        sys.stderr.write('Username: ')
+        sys.stderr.flush()
+        creds.username = sys.stdin.readline().strip()
+    creds.password = getpass.getpass('Password for %s: ' % creds.username)
+    return creds
+
+def _create_auth_baton(pool):
+    """Create a Subversion authentication baton. """
+    # Give the client context baton a suite of authentication
+    # providers.h
+    platform_specific = ['svn_auth_get_gnome_keyring_simple_provider',
+                         'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider',
+                         'svn_auth_get_keychain_simple_provider',
+                         'svn_auth_get_keychain_ssl_client_cert_pw_provider',
+                         'svn_auth_get_kwallet_simple_provider',
+                         'svn_auth_get_kwallet_ssl_client_cert_pw_provider',
+                         'svn_auth_get_ssl_client_cert_file_provider',
+                         'svn_auth_get_windows_simple_provider',
+                         'svn_auth_get_windows_ssl_server_trust_provider',
+                         ]
+
+    providers = []
+    # Platform-dependant authentication methods
+    getprovider = getattr(core, 'svn_auth_get_platform_specific_provider',
+                          None)
+    if getprovider:
+        # Available in svn >= 1.6
+        for name in ('gnome_keyring', 'keychain', 'kwallet', 'windows'):
+            for type in ('simple', 'ssl_client_cert_pw', 'ssl_server_trust'):
+                p = getprovider(name, type, pool)
+                if p:
+                    providers.append(p)
+    else:
+        for p in platform_specific:
+            if hasattr(core, p):
+                try:
+                    providers.append(getattr(core, p)())
+                except RuntimeError:
+                    pass
+
+    providers += [
+        client.get_simple_provider(),
+        client.get_username_provider(),
+        client.get_ssl_client_cert_file_provider(),
+        client.get_ssl_client_cert_pw_file_provider(),
+        client.get_ssl_server_trust_file_provider(),
+        client.get_simple_prompt_provider(user_pass_prompt, 2),
+        ]
+
+    return core.svn_auth_open(providers, pool)
+
+_svntypes = {
+    core.svn_node_dir: 'd',
+    core.svn_node_file: 'f',
+    }
+
+class SubversionRepo(object):
+    """Wrapper for a Subversion repository.
+
+    It uses the SWIG Python bindings, see above for requirements.
+    """
+    def __init__(self, url='', username='', password='', head=None):
+        parsed = common.parse_url(url, username, password)
+        # --username and --password override URL credentials
+        self.username = parsed[0]
+        self.password = parsed[1]
+        self.svn_url = parsed[2]
+        self.auth_baton_pool = core.Pool()
+        self.auth_baton = _create_auth_baton(self.auth_baton_pool)
+        # self.init_ra_and_client() assumes that a pool already exists
+        self.pool = core.Pool()
+
+        self.init_ra_and_client()
+        self.uuid = ra.get_uuid(self.ra, self.pool)
+        self.svn_url = ra.get_session_url(self.ra, self.pool)
+        self.root = ra.get_repos_root(self.ra, self.pool)
+        assert self.svn_url.startswith(self.root)
+        # *will* have a leading '/', would not if we used get_repos_root2
+        self.subdir = self.svn_url[len(self.root):]
+        if not self.subdir or self.subdir[-1] != '/':
+            self.subdir += '/'
+        # the RA interface always yields quoted paths, but the editor interface
+        # expects unquoted paths
+        self.subdir = urllib.unquote(self.subdir)
+        self.hasdiff3 = True
+
+    def init_ra_and_client(self):
+        """Initializes the RA and client layers, because sometimes getting
+        unified diffs runs the remote server out of open files.
+        """
+        # while we're in here we'll recreate our pool
+        self.pool = core.Pool()
+        if self.username:
+            core.svn_auth_set_parameter(self.auth_baton,
+                                        core.SVN_AUTH_PARAM_DEFAULT_USERNAME,
+                                        self.username)
+        if self.password:
+            core.svn_auth_set_parameter(self.auth_baton,
+                                        core.SVN_AUTH_PARAM_DEFAULT_PASSWORD,
+                                        self.password)
+        self.client_context = client.create_context()
+
+        self.client_context.auth_baton = self.auth_baton
+        self.client_context.config = svn_config
+        callbacks = RaCallbacks()
+        callbacks.auth_baton = self.auth_baton
+        self.callbacks = callbacks
+        try:
+            self.ra = ra.open2(self.svn_url, callbacks,
+                               svn_config, self.pool)
+        except SubversionException, e:
+            if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED:
+                msg = ('Subversion does not trust the SSL certificate for this '
+                       'site; please try running \'svn ls %s\' first.'
+                       % self.svn_url)
+            elif e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED:
+                msg = ('Failed to open Subversion repository; please try '
+                       'running \'svn ls %s\' for details.' % self.svn_url)
+            else:
+                msg = e.args[0]
+                for k, v in vars(core).iteritems():
+                    if k.startswith('SVN_ERR_') and v == e.apr_err:
+                        msg = '%s (%s)' % (msg, k)
+                        break
+            raise common.SubversionConnectionException(msg)
+
+    @property
+    def HEAD(self):
+        return ra.get_latest_revnum(self.ra, self.pool)
+
+    @property
+    def last_changed_rev(self):
+        try:
+            holder = []
+            ra.get_log(self.ra, [''],
+                       self.HEAD, 1,
+                       1, #limit of how many log messages to load
+                       True, # don't need to know changed paths
+                       True, # stop on copies
+                       lambda paths, revnum, author, date, message, pool:
+                           holder.append(revnum),
+                       self.pool)
+
+            return holder[-1]
+        except SubversionException, e:
+            if e.apr_err not in [core.SVN_ERR_FS_NOT_FOUND]:
+                raise
+            else:
+                return self.HEAD
+
+    def list_dir(self, dir, revision=None):
+        """List the contents of a server-side directory.
+
+        Returns a dict-like object with one dict key per directory entry.
+
+        Args:
+          dir: the directory to list, no leading slash
+          rev: the revision at which to list the directory, defaults to HEAD
+        """
+        # TODO this should just not accept leading slashes like the docstring says
+        if dir and dir[-1] == '/':
+            dir = dir[:-1]
+        if revision is None:
+            revision = self.HEAD
+        r = ra.get_dir2(self.ra, dir, revision, core.SVN_DIRENT_KIND, self.pool)
+        folders, props, junk = r
+        return folders
+
+    def revisions(self, paths=None, start=0, stop=0,
+                  chunk_size=common.chunk_size):
+        """Load the history of this repo.
+
+        This is LAZY. It returns a generator, and fetches a small number
+        of revisions at a time.
+
+        The reason this is lazy is so that you can use the same repo object
+        to perform RA calls to get deltas.
+        """
+        if paths is None:
+            paths = ['']
+        if not stop:
+            stop = self.HEAD
+        while stop > start:
+            def callback(paths, revnum, author, date, message, pool):
+                r = common.Revision(revnum, author, message, date, paths,
+                                    strip_path=self.subdir)
+                revisions.append(r)
+            # we only access revisions in a FIFO manner
+            revisions = collections.deque()
+
+            try:
+                # TODO: using min(start + chunk_size, stop) may be preferable;
+                #       ra.get_log(), even with chunk_size set, takes a while
+                #       when converting the 65k+ rev. in LLVM.
+                ra.get_log(self.ra,
+                           paths,
+                           start+1,
+                           stop,
+                           chunk_size, #limit of how many log messages to load
+                           True, # don't need to know changed paths
+                           True, # stop on copies
+                           callback,
+                           self.pool)
+            except core.SubversionException, e:
+                if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
+                    msg = ('%s not found at revision %d!'
+                           % (self.subdir.rstrip('/'), stop))
+                    raise common.SubversionConnectionException(msg)
+                elif e.apr_err == core.SVN_ERR_FS_NO_SUCH_REVISION:
+                    raise common.SubversionConnectionException(e.message)
+                else:
+                    raise
+
+            while len(revisions) > 1:
+                yield revisions.popleft()
+
+            if len(revisions) == 0:
+                # exit the loop; there is no history for the path.
+                break
+            else:
+                r = revisions.popleft()
+                start = r.revnum
+                yield r
+            self.init_ra_and_client()
+
+    def commit(self, paths, message, file_data, base_revision, addeddirs,
+               deleteddirs, properties, copies):
+        """Commits the appropriate targets from revision in editor's store.
+        """
+        self.init_ra_and_client()
+        commit_info = []
+        def commit_cb(_commit_info, pool):
+            commit_info.append(_commit_info)
+        editor, edit_baton = ra.get_commit_editor2(self.ra,
+                                                   message,
+                                                   commit_cb,
+                                                   None,
+                                                   False,
+                                                   self.pool)
+        checksum = []
+        # internal dir batons can fall out of scope and get GCed before svn is
+        # done with them. This prevents that (credit to gvn for the idea).
+        batons = [edit_baton, ]
+        def driver_cb(parent, path, pool):
+            if not parent:
+                bat = editor.open_root(edit_baton, base_revision, self.pool)
+                batons.append(bat)
+                return bat
+            if path in deleteddirs:
+                bat = editor.delete_entry(path, base_revision, parent, pool)
+                batons.append(bat)
+                return bat
+            if path not in file_data:
+                if path in addeddirs:
+                    bat = editor.add_directory(path, parent, None, -1, pool)
+                else:
+                    bat = editor.open_directory(path, parent, base_revision, pool)
+                batons.append(bat)
+                props = properties.get(path, {})
+                if 'svn:externals' in props:
+                    value = props['svn:externals']
+                    editor.change_dir_prop(bat, 'svn:externals', value, pool)
+                return bat
+            base_text, new_text, action = file_data[path]
+            compute_delta = True
+            if action == 'modify':
+                baton = editor.open_file(path, parent, base_revision, pool)
+            elif action == 'add':
+                frompath, fromrev = copies.get(path, (None, -1))
+                if frompath:
+                    frompath = self.path2url(frompath)
+                baton = editor.add_file(path, parent, frompath, fromrev, pool)
+            elif action == 'delete':
+                baton = editor.delete_entry(path, base_revision, parent, pool)
+                compute_delta = False
+
+            if path in properties:
+                if properties[path].get('svn:special', None):
+                    new_text = 'link %s' % new_text
+                for p, v in properties[path].iteritems():
+                    editor.change_file_prop(baton, p, v)
+
+            if compute_delta:
+                handler, wh_baton = editor.apply_textdelta(baton, None,
+                                                           self.pool)
+
+                txdelta_stream = delta.svn_txdelta(
+                    cStringIO.StringIO(base_text), cStringIO.StringIO(new_text),
+                    self.pool)
+                delta.svn_txdelta_send_txstream(txdelta_stream, handler,
+                                                wh_baton, pool)
+
+                # TODO pass md5(new_text) instead of None
+                editor.close_file(baton, None, pool)
+
+        delta.path_driver(editor, edit_baton, base_revision, paths, driver_cb,
+                          self.pool)
+        editor.close_edit(edit_baton, self.pool)
+
+    def get_replay(self, revision, editor, oldest_rev_i_have=0):
+        # this method has a tendency to chew through RAM if you don't re-init
+        self.init_ra_and_client()
+        e_ptr, e_baton = delta.make_editor(editor)
+        try:
+            ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr,
+                      e_baton, self.pool)
+        except SubversionException, e: #pragma: no cover
+            # can I depend on this number being constant?
+            if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or
+                e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE):
+                msg = ('This Subversion server is older than 1.4.0, and '
+                       'cannot satisfy replay requests.')
+                raise common.SubversionRepoCanNotReplay(msg)
+            else:
+                raise
+
+    def get_revision(self, revision, editor):
+        ''' feed the contents of the given revision to the given editor '''
+
+        e_ptr, e_baton = delta.make_editor(editor)
+
+        reporter, reporter_baton = ra.do_update(self.ra, revision, "", True,
+                                                e_ptr, e_baton)
+
+        reporter.set_path(reporter_baton, "", revision, True, None)
+        reporter.finish_report(reporter_baton)
+
+    def get_unified_diff(self, path, revision, other_path=None, other_rev=None,
+                         deleted=True, ignore_type=False):
+        """Gets a unidiff of path at revision against revision-1.
+        """
+        if not self.hasdiff3:
+            raise common.SubversionRepoCanNotDiff()
+        # works around an svn server keeping too many open files (observed
+        # in an svnserve from the 1.2 era)
+        self.init_ra_and_client()
+
+        url = self.path2url(path)
+        url2 = url
+        url2 = (other_path and self.path2url(other_path) or url)
+        if other_rev is None:
+            other_rev = revision - 1
+        old_cwd = os.getcwd()
+        tmpdir = tempfile.mkdtemp('svnwrap_temp')
+        out, err = None, None
+        try:
+            # hot tip: the swig bridge doesn't like StringIO for these bad boys
+            out_path = os.path.join(tmpdir, 'diffout')
+            error_path = os.path.join(tmpdir, 'differr')
+            out = open(out_path, 'w')
+            err = open(error_path, 'w')
+            try:
+                client.diff3([], url2, optrev(other_rev), url, optrev(revision),
+                             True, True, deleted, ignore_type, 'UTF-8', out, err,
+                             self.client_context, self.pool)
+            except SubversionException, e:
+                # "Can't write to stream: The handle is invalid."
+                # This error happens systematically under Windows, possibly
+                # related to file handles being non-write shareable by default.
+                if e.apr_err != 720006:
+                    raise
+                self.hasdiff3 = False
+                raise common.SubversionRepoCanNotDiff()
+            out.close()
+            err.close()
+            out, err = None, None
+            assert len(open(error_path).read()) == 0
+            diff = open(out_path).read()
+            return diff
+        finally:
+            if out: out.close()
+            if err: err.close()
+            shutil.rmtree(tmpdir)
+            os.chdir(old_cwd)
+
+    def get_file(self, path, revision):
+        """Return content and mode of file at given path and revision.
+
+        "link " prefix is dropped from symlink content. Mode is 'x' if
+        file is executable, 'l' if a symlink, the empty string
+        otherwise. If the file does not exist at this revision, raise
+        IOError.
+        """
+        assert not path.startswith('/')
+        mode = ''
+        try:
+            out = cStringIO.StringIO()
+            info = ra.get_file(self.ra, path, revision, out)
+            data = out.getvalue()
+            out.close()
+            if isinstance(info, list):
+                info = info[-1]
+            mode = ("svn:executable" in info) and 'x' or ''
+            mode = ("svn:special" in info) and 'l' or mode
+        except SubversionException, e:
+            notfound = (core.SVN_ERR_FS_NOT_FOUND,
+                        core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
+            if e.args[1] in notfound: # File not found
+                raise IOError(errno.ENOENT, e.args[0])
+            raise
+        if mode  == 'l':
+            linkprefix = "link "
+            if data.startswith(linkprefix):
+                data = data[len(linkprefix):]
+        return data, mode
+
+    def list_props(self, path, revision):
+        """Return a mapping of property names to values, raise IOError if
+        specified path does not exist.
+        """
+        self.init_ra_and_client()
+        rev = optrev(revision)
+        rpath = self.path2url(path)
+        try:
+            pl = client.proplist2(rpath, rev, rev, False,
+                                  self.client_context, self.pool)
+        except SubversionException, e:
+            # Specified path does not exist at this revision
+            if e.apr_err == core.SVN_ERR_NODE_UNKNOWN_KIND:
+                raise IOError(errno.ENOENT, e.args[0])
+            raise
+        if not pl:
+            return {}
+        return pl[0][1]
+
+    def list_files(self, dirpath, revision):
+        """List the content of a directory at a given revision, recursively.
+
+        Yield tuples (path, kind) where 'path' is the entry path relatively to
+        'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a
+        directory. Raise IOError if the directory cannot be found at given
+        revision.
+        """
+        rpath = self.path2url(dirpath)
+        pool = core.Pool()
+        rev = optrev(revision)
+        try:
+            entries = client.ls(rpath, rev, True, self.client_context, pool)
+        except SubversionException, e:
+            if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
+                raise IOError(errno.ENOENT,
+                              '%s cannot be found at r%d' % (dirpath, revision))
+            raise
+        for path, e in entries.iteritems():
+            kind = _svntypes.get(e.kind)
+            yield path, kind
+
+    def checkpath(self, path, revision):
+        """Return the entry type at the given revision, 'f', 'd' or None
+        if the entry does not exist.
+        """
+        kind = ra.check_path(self.ra, path.strip('/'), revision)
+        return _svntypes.get(kind)
+
+    def path2url(self, path):
+        """Build svn URL for path, URL-escaping path.
+        """
+        if not path or path == '.':
+            return self.svn_url
+        assert path[0] != '/', path
+        return '/'.join((self.svn_url,
+                         urllib.quote(path).rstrip('/'),
+                         ))
diff --git a/opt/hgsubversion/hgsubversion/util.py b/opt/hgsubversion/hgsubversion/util.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/util.py
@@ -0,0 +1,283 @@
+import re
+import os
+import urllib
+
+from mercurial import cmdutil
+from mercurial import error
+from mercurial import hg
+from mercurial import node
+from mercurial import util as hgutil
+
+try:
+    from mercurial import revset
+except ImportError:
+    pass
+
+ignoredfiles = set(['.hgtags', '.hgsvnexternals', '.hgsub', '.hgsubstate'])
+
+b_re = re.compile(r'^\+\+\+ b\/([^\n]*)', re.MULTILINE)
+a_re = re.compile(r'^--- a\/([^\n]*)', re.MULTILINE)
+devnull_re = re.compile(r'^([-+]{3}) /dev/null', re.MULTILINE)
+header_re = re.compile(r'^diff --git .* b\/(.*)', re.MULTILINE)
+newfile_devnull_re = re.compile(r'^--- /dev/null\n\+\+\+ b/([^\n]*)',
+                                re.MULTILINE)
+
+
+def formatrev(rev):
+    if rev == -1:
+        return '\t(working copy)'
+    return '\t(revision %d)' % rev
+
+
+def filterdiff(diff, oldrev, newrev):
+    diff = newfile_devnull_re.sub(r'--- \1\t(revision 0)' '\n'
+                                  r'+++ \1\t(working copy)',
+                                  diff)
+    oldrev = formatrev(oldrev)
+    newrev = formatrev(newrev)
+    diff = a_re.sub(r'--- \1'+ oldrev, diff)
+    diff = b_re.sub(r'+++ \1' + newrev, diff)
+    diff = devnull_re.sub(r'\1 /dev/null\t(working copy)', diff)
+    diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff)
+    return diff
+
+
+def parentrev(ui, repo, meta, hashes):
+    """Find the svn parent revision of the repo's dirstate.
+    """
+    workingctx = repo.parents()[0]
+    outrev = outgoing_revisions(repo, hashes, workingctx.node())
+    if outrev:
+        workingctx = repo[outrev[-1]].parents()[0]
+    return workingctx
+
+
+def islocalrepo(url):
+    path = str(url) # convert once up front
+    if path.startswith('file:///'):
+        prefixlen = len('file://')
+    elif path.startswith('file:/'):
+        prefixlen = len('file:')
+    else:
+        return False
+    if '#' in path.split('/')[-1]: # strip off #anchor
+        path = path[:path.rfind('#')]
+    path = url[prefixlen:]
+    path = urllib.url2pathname(path).replace(os.sep, '/')
+    while '/' in path:
+        if reduce(lambda x,y: x and y,
+                  map(lambda p: os.path.exists(os.path.join(path, p)),
+                      ('hooks', 'format', 'db', ))):
+            return True
+        path = path.rsplit('/', 1)[0]
+    return False
+
+
+def version(ui):
+    """Return version information if available."""
+    try:
+        import __version__
+        return __version__.version
+    except ImportError:
+        try:
+            dn = os.path.dirname
+            repo = hg.repository(ui, dn(dn(__file__)))
+            ver = repo.dirstate.parents()[0]
+            return node.hex(ver)[:12]
+        except:
+            return 'unknown'
+
+
+def normalize_url(url):
+    if not url:
+        return url
+    if url.startswith('svn+http://') or url.startswith('svn+https://'):
+        url = url[4:]
+    url, revs, checkout = parseurl(url)
+    url = url.rstrip('/')
+    if checkout:
+        url = '%s#%s' % (url, checkout)
+    return url
+
+# TODO remove when we drop 1.3 support
+def progress(ui, *args, **kwargs):
+    if getattr(ui, 'progress', False):
+        return ui.progress(*args, **kwargs)
+
+# TODO remove when we drop 1.5 support
+remoteui = getattr(cmdutil, 'remoteui', getattr(hg, 'remoteui', False))
+if not remoteui:
+    raise ImportError('Failed to import remoteui')
+
+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
+    return svn_url, heads, checkout
+
+
+class PrefixMatch(object):
+    def __init__(self, prefix):
+        self.p = prefix
+
+    def files(self):
+        return []
+
+    def __call__(self, fn):
+        return fn.startswith(self.p)
+
+def outgoing_revisions(repo, reverse_map, sourcerev):
+    """Given a repo and an hg_editor, determines outgoing revisions for the
+    current working copy state.
+    """
+    outgoing_rev_hashes = []
+    if sourcerev in reverse_map:
+        return
+    sourcerev = repo[sourcerev]
+    while (not sourcerev.node() in reverse_map
+           and sourcerev.node() != node.nullid):
+        outgoing_rev_hashes.append(sourcerev.node())
+        sourcerev = sourcerev.parents()
+        if len(sourcerev) != 1:
+            raise hgutil.Abort("Sorry, can't find svn parent of a merge revision.")
+        sourcerev = sourcerev[0]
+    if sourcerev.node() != node.nullid:
+        return outgoing_rev_hashes
+
+def outgoing_common_and_heads(repo, reverse_map, sourcerev):
+    """Given a repo and an hg_editor, determines outgoing revisions for the
+    current working copy state. Returns a tuple (common, heads) like
+    discovery.findcommonoutgoing does.
+    """
+    if sourcerev in reverse_map:
+        return ([sourcerev], [sourcerev]) # nothing outgoing
+    sourcecx = repo[sourcerev]
+    while (not sourcecx.node() in reverse_map
+           and sourcecx.node() != node.nullid):
+        ps = sourcecx.parents()
+        if len(ps) != 1:
+            raise hgutil.Abort("Sorry, can't find svn parent of a merge revision.")
+        sourcecx = ps[0]
+    if sourcecx.node() != node.nullid:
+        return ([sourcecx.node()], [sourcerev])
+    return ([sourcerev], [sourcerev]) # nothing outgoing
+
+def default_commit_msg(ui):
+    return ui.config('hgsubversion', 'defaultmessage', '')
+
+def describe_commit(ui, h, b):
+    ui.note(' committed to "%s" as %s\n' % ((b or 'default'), node.short(h)))
+
+
+def swap_out_encoding(new_encoding="UTF-8"):
+    from mercurial import encoding
+    old = encoding.encoding
+    encoding.encoding = new_encoding
+    return old
+
+
+def issamefile(parentctx, childctx, f):
+    """Return True if f exists and is the same in childctx and parentctx"""
+    if f not in parentctx or f not in childctx:
+        return False
+    if parentctx == childctx:
+        return True
+    if parentctx.rev() > childctx.rev():
+        parentctx, childctx = childctx, parentctx
+
+    def selfandancestors(selfctx):
+        yield selfctx
+        for ctx in selfctx.ancestors():
+            yield ctx
+
+    for pctx in selfandancestors(childctx):
+        if pctx.rev() <= parentctx.rev():
+            return True
+        if f in pctx.files():
+            return False
+    # parentctx is not an ancestor of childctx, files are unrelated
+    return False
+
+def _templatehelper(ctx, kw):
+    '''
+    Helper function for displaying information about converted changesets.
+    '''
+    convertinfo = ctx.extra().get('convert_revision', '')
+
+    if not convertinfo or not convertinfo.startswith('svn:'):
+        return ''
+
+    if kw == 'svnuuid':
+        return convertinfo[4:40]
+    elif kw == 'svnpath':
+        return convertinfo[40:].rsplit('@', 1)[0]
+    elif kw == 'svnrev':
+        return convertinfo[40:].rsplit('@', 1)[-1]
+    else:
+        raise hgutil.Abort('unrecognized hgsubversion keyword %s' % kw)
+
+def svnrevkw(**args):
+    """:svnrev: String. Converted subversion revision number."""
+    return _templatehelper(args['ctx'], 'svnrev')
+
+def svnpathkw(**args):
+    """:svnpath: String. Converted subversion revision project path."""
+    return _templatehelper(args['ctx'], 'svnpath')
+
+def svnuuidkw(**args):
+    """:svnuuid: String. Converted subversion revision repository identifier."""
+    return _templatehelper(args['ctx'], 'svnuuid')
+
+templatekeywords = {
+    'svnrev': svnrevkw,
+    'svnpath': svnpathkw,
+    'svnuuid': svnuuidkw,
+}
+
+def revset_fromsvn(repo, subset, x):
+    '''``fromsvn()``
+    Select changesets that originate from Subversion.
+    '''
+    args = revset.getargs(x, 0, 0, "fromsvn takes no arguments")
+
+    def matches(r):
+        convertinfo = repo[r].extra().get('convert_revision', '')
+        return convertinfo[:4] == 'svn:'
+
+    return [r for r in subset if matches(r)]
+
+def revset_svnrev(repo, subset, x):
+    '''``svnrev(number)``
+    Select changesets that originate in the given Subversion revision.
+    '''
+    args = revset.getargs(x, 1, 1, "svnrev takes one argument")
+
+    rev = revset.getstring(args[0],
+                           "the argument to svnrev() must be a number")
+    try:
+        rev = int(rev)
+    except ValueError:
+        raise error.ParseError("the argument to svnrev() must be a number")
+
+    def matches(r):
+        convertinfo = repo[r].extra().get('convert_revision', '')
+        if convertinfo[:4] != 'svn:':
+            return False
+        return int(convertinfo[40:].rsplit('@', 1)[-1]) == rev
+
+    return [r for r in subset if matches(r)]
+
+revsets = {
+    'fromsvn': revset_fromsvn,
+    'svnrev': revset_svnrev,
+}
diff --git a/opt/hgsubversion/hgsubversion/wrappers.py b/opt/hgsubversion/hgsubversion/wrappers.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/hgsubversion/wrappers.py
@@ -0,0 +1,520 @@
+from hgext import rebase as hgrebase
+
+from mercurial import cmdutil
+from mercurial import patch
+from mercurial import hg
+from mercurial import util as hgutil
+from mercurial import node
+from mercurial import i18n
+from mercurial import extensions
+
+import replay
+import pushmod
+import stupid as stupidmod
+import svnwrap
+import svnrepo
+import util
+
+try:
+    from mercurial import scmutil
+    revpair = scmutil.revpair
+except ImportError:
+    revpair = cmdutil.revpair
+
+pullfuns = {
+    True: replay.convert_rev,
+    False: stupidmod.convert_rev,
+}
+
+revmeta = [
+    ('revision', 'revnum'),
+    ('user', 'author'),
+    ('date', 'date'),
+    ('message', 'message'),
+]
+
+
+def version(orig, ui, *args, **opts):
+    svn = opts.pop('svn', None)
+    orig(ui, *args, **opts)
+    if svn:
+        svnversion, bindings = svnwrap.version()
+        ui.status('\n')
+        ui.status('hgsubversion: %s\n' % util.version(ui))
+        ui.status('Subversion: %s\n' % svnversion)
+        ui.status('bindings: %s\n' % bindings)
+
+
+def parents(orig, ui, repo, *args, **opts):
+    """show Mercurial & Subversion parents of the working dir or revision
+    """
+    if not opts.get('svn', False):
+        return orig(ui, repo, *args, **opts)
+    meta = repo.svnmeta()
+    hashes = meta.revmap.hashes()
+    ha = util.parentrev(ui, repo, meta, hashes)
+    if ha.node() == node.nullid:
+        raise hgutil.Abort('No parent svn revision!')
+    displayer = cmdutil.show_changeset(ui, repo, opts, buffered=False)
+    displayer.show(ha)
+    return 0
+
+
+def incoming(orig, ui, repo, origsource='default', **opts):
+    """show incoming revisions from Subversion
+    """
+
+    source, revs, checkout = util.parseurl(ui.expandpath(origsource))
+    other = hg.repository(ui, source)
+    if 'subversion' not in other.capabilities:
+        return orig(ui, repo, origsource, **opts)
+
+    svn = other.svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+
+    ui.status('incoming changes from %s\n' % other.svnurl)
+    for r in svn.revisions(start=meta.revmap.youngest):
+        ui.status('\n')
+        for label, attr in revmeta:
+            l1 = label + ':'
+            val = str(getattr(r, attr)).strip()
+            if not ui.verbose:
+                val = val.split('\n')[0]
+            ui.status('%s%s\n' % (l1.ljust(13), val))
+
+
+def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
+    assert other.capable('subversion')
+    # split off #rev; TODO implement --revision/#rev support
+    svn = other.svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+    parent = repo.parents()[0].node()
+    hashes = meta.revmap.hashes()
+    return util.outgoing_common_and_heads(repo, hashes, parent)
+
+
+def findoutgoing(repo, dest=None, heads=None, force=False):
+    """show changesets not found in the Subversion repository
+    """
+    assert dest.capable('subversion')
+    # split off #rev; TODO implement --revision/#rev support
+    #svnurl, revs, checkout = util.parseurl(dest.svnurl, heads)
+    svn = dest.svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+    parent = repo.parents()[0].node()
+    hashes = meta.revmap.hashes()
+    return util.outgoing_revisions(repo, hashes, parent)
+
+
+def diff(orig, ui, repo, *args, **opts):
+    """show a diff of the most recent revision against its parent from svn
+    """
+    if not opts.get('svn', False) or opts.get('change', None):
+        return orig(ui, repo, *args, **opts)
+    meta = repo.svnmeta()
+    hashes = meta.revmap.hashes()
+    if not opts.get('rev', None):
+        parent = repo.parents()[0]
+        o_r = util.outgoing_revisions(repo, hashes, parent.node())
+        if o_r:
+            parent = repo[o_r[-1]].parents()[0]
+        opts['rev'] = ['%s:.' % node.hex(parent.node()), ]
+    node1, node2 = revpair(repo, opts['rev'])
+    baserev, _junk = hashes.get(node1, (-1, 'junk'))
+    newrev, _junk = hashes.get(node2, (-1, 'junk'))
+    it = patch.diff(repo, node1, node2,
+                    opts=patch.diffopts(ui, opts={'git': True,
+                                                  'show_function': False,
+                                                  'ignore_all_space': False,
+                                                  'ignore_space_change': False,
+                                                  'ignore_blank_lines': False,
+                                                  'unified': True,
+                                                  'text': False,
+                                                  }))
+    ui.write(util.filterdiff(''.join(it), baserev, newrev))
+
+def push(repo, dest, force, revs):
+    """push revisions starting at a specified head back to Subversion.
+    """
+    assert not revs, 'designated revisions for push remains unimplemented.'
+    if hasattr(cmdutil, 'bail_if_changed'):
+        cmdutil.bail_if_changed(repo)
+    else:
+        # Since 1.9 (d68ddccf276b)
+        cmdutil.bailifchanged(repo)
+    checkpush = getattr(repo, 'checkpush', None)
+    if checkpush:
+        checkpush(force, revs)
+    ui = repo.ui
+    old_encoding = util.swap_out_encoding()
+    # TODO: implement --rev/#rev support
+    # TODO: do credentials specified in the URL still work?
+    svnurl = repo.ui.expandpath(dest.svnurl)
+    svn = dest.svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+
+    # Strategy:
+    # 1. Find all outgoing commits from this head
+    if len(repo.parents()) != 1:
+        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]
+    ui.status('searching for changes\n')
+    hashes = meta.revmap.hashes()
+    outgoing = util.outgoing_revisions(repo, hashes, workingrev.node())
+    if not (outgoing and len(outgoing)):
+        ui.status('no changes found\n')
+        return 1 # so we get a sane exit status, see hg's commands.push
+    while outgoing:
+
+        # 2. Commit oldest revision that needs to be pushed
+        oldest = outgoing.pop(-1)
+        old_ctx = repo[oldest]
+        old_pars = old_ctx.parents()
+        if len(old_pars) != 1:
+            ui.status('Found a branch merge, this needs discussion and '
+                      'implementation.\n')
+            return 0 # results in nonzero exit status, see hg's commands.py
+        # We will commit to svn against this node's parent rev. Any file-level
+        # conflicts here will result in an error reported by svn.
+        base_ctx = old_pars[0]
+        base_revision = hashes[base_ctx.node()][0]
+        svnbranch = base_ctx.branch()
+        # Find most recent svn commit we have on this branch.
+        # This node will become the nearest known ancestor of the pushed rev.
+        oldtipctx = base_ctx
+        old_children = oldtipctx.descendants()
+        seen = set(c.node() for c in old_children)
+        samebranchchildren = [c for c in old_children if c.branch() == svnbranch
+                              and c.node() in hashes]
+        if samebranchchildren:
+            # The following relies on descendants being sorted by rev.
+            oldtipctx = samebranchchildren[-1]
+        # All set, so commit now.
+        try:
+            pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn)
+        except pushmod.NoFilesException:
+            ui.warn("Could not push revision %s because it had no changes in svn.\n" %
+                     old_ctx)
+            return 1
+
+        # 3. Fetch revisions from svn
+        # TODO: this probably should pass in the source explicitly - rev too?
+        r = repo.pull(dest, force=force)
+        assert not r or r == 0
+
+        # 4. Find the new head of the target branch
+        # We expect to get our own new commit back, but we might also get other
+        # commits that happened since our last pull, or even right after our own
+        # commit (race).
+        for c in oldtipctx.descendants():
+            if c.node() not in seen and c.branch() == svnbranch:
+                newtipctx = c
+
+        # 5. Rebase all children of the currently-pushing rev to the new head
+        heads = repo.heads(old_ctx.node())
+        for needs_transplant in heads:
+            def extrafn(ctx, extra):
+                if ctx.node() == oldest:
+                    return
+                extra['branch'] = ctx.branch()
+            # TODO: can we avoid calling our own rebase wrapper here?
+            rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn,
+                   svnsourcerev=needs_transplant)
+            # Reload the repo after the rebase. Do not reuse contexts across this.
+            newtip = newtipctx.node()
+            repo = hg.repository(ui, meta.path)
+            newtipctx = repo[newtip]
+            # Rewrite the node ids in outgoing to their rebased versions.
+            rebasemap = dict()
+            for child in newtipctx.descendants():
+                rebasesrc = child.extra().get('rebase_source')
+                if rebasesrc:
+                    rebasemap[node.bin(rebasesrc)] = child.node()
+            outgoing = [rebasemap.get(n) or n for n in outgoing]
+        # TODO: stop constantly creating the SVNMeta instances.
+        meta = repo.svnmeta(svn.uuid, svn.subdir)
+        hashes = meta.revmap.hashes()
+    util.swap_out_encoding(old_encoding)
+    return 1 # so we get a sane exit status, see hg's commands.push
+
+
+def pull(repo, source, heads=[], force=False):
+    """pull new revisions from Subversion"""
+    assert source.capable('subversion')
+    svn_url = source.svnurl
+
+    # Split off #rev
+    svn_url, heads, checkout = util.parseurl(svn_url, heads)
+    old_encoding = util.swap_out_encoding()
+
+    try:
+        stopat_rev = int(checkout or 0)
+    except ValueError:
+        raise hgutil.Abort('unrecognised Subversion revision %s: '
+                           'only numbers work.' % checkout)
+
+    have_replay = not repo.ui.configbool('hgsubversion', 'stupid')
+    if not have_replay:
+        repo.ui.note('fetching stupidly...\n')
+
+    svn = source.svn
+    meta = repo.svnmeta(svn.uuid, svn.subdir)
+
+    layout = repo.ui.config('hgsubversion', 'layout', 'auto')
+    if layout == 'auto':
+        rootlist = svn.list_dir('', revision=(stopat_rev or None))
+        if sum(map(lambda x: x in rootlist, ('branches', 'tags', 'trunk'))):
+            layout = 'standard'
+        else:
+            layout = 'single'
+        repo.ui.setconfig('hgsubversion', 'layout', layout)
+        repo.ui.note('using %s layout\n' % layout)
+
+    branch = repo.ui.config('hgsubversion', 'branch')
+    if branch:
+        if layout != 'single':
+            msg = ('branch cannot be specified for Subversion clones using '
+                   'standard directory layout')
+            raise hgutil.Abort(msg)
+
+        meta.branchmap['default'] = branch
+
+    ui = repo.ui
+    start = meta.revmap.youngest
+    origrevcount = len(meta.revmap)
+
+    if start <= 0:
+        # we are initializing a new repository
+        start = repo.ui.config('hgsubversion', 'startrev', 0)
+        if isinstance(start, str) and start.upper() == 'HEAD':
+            start = svn.last_changed_rev
+        else:
+            start = int(start)
+
+        if start > 0:
+            if layout == 'standard':
+                raise hgutil.Abort('non-zero start revisions are only '
+                                   'supported for single-directory clones.')
+            ui.note('starting at revision %d; any prior will be ignored\n'
+                    % start)
+            # fetch all revisions *including* the one specified...
+            start -= 1
+
+        # anything less than zero makes no sense
+        if start < 0:
+            start = 0
+
+    oldrevisions = len(meta.revmap)
+    if stopat_rev:
+        total = stopat_rev - start
+    else:
+        total = svn.HEAD - start
+    try:
+        try:
+            # start converting revisions
+            firstrun = True
+            for r in svn.revisions(start=start, stop=stopat_rev):
+                if (r.author is None and
+                    r.message == 'This is an empty revision for padding.'):
+                    continue
+                tbdelta = meta.update_branch_tag_map_for_rev(r)
+                # got a 502? Try more than once!
+                tries = 0
+                converted = False
+                while not converted:
+                    try:
+                        msg = ''
+                        if r.message:
+                            msg = r.message.strip()
+                        if not msg:
+                            msg = util.default_commit_msg(ui)
+                        else:
+                            msg = [s.strip() for s in msg.splitlines() if s][0]
+                        if getattr(ui, 'termwidth', False):
+                            w = ui.termwidth()
+                        else:
+                            w = hgutil.termwidth()
+                        bits = (r.revnum, r.author, msg)
+                        ui.status(('[r%d] %s: %s' % bits)[:w] + '\n')
+                        util.progress(ui, 'pull', r.revnum - start, total=total)
+
+                        meta.save_tbdelta(tbdelta)
+                        close = pullfuns[have_replay](ui, meta, svn, r, tbdelta,
+                                                      firstrun)
+                        meta.committags(r, close)
+                        for branch, parent in close.iteritems():
+                            if parent in (None, node.nullid):
+                                continue
+                            meta.delbranch(branch, parent, r)
+
+                        meta.save()
+                        converted = True
+                        firstrun = False
+
+                    except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover
+                        ui.status('%s\n' % e.message)
+                        stupidmod.print_your_svn_is_old_message(ui)
+                        have_replay = False
+                    except svnwrap.SubversionException, e: #pragma: no cover
+                        if (e.args[1] == svnwrap.ERR_RA_DAV_REQUEST_FAILED
+                            and '502' in str(e)
+                            and tries < 3):
+                            tries += 1
+                            ui.status('Got a 502, retrying (%s)\n' % tries)
+                        else:
+                            ui.traceback()
+                            raise hgutil.Abort(*e.args)
+        except KeyboardInterrupt:
+            pass
+    finally:
+        util.progress(ui, 'pull', None, total=total)
+        util.swap_out_encoding(old_encoding)
+
+    revisions = len(meta.revmap) - oldrevisions
+
+    if revisions == 0:
+        ui.status(i18n._("no changes found\n"))
+        return 0
+    else:
+        ui.status("pulled %d revisions\n" % revisions)
+
+def rebase(orig, ui, repo, **opts):
+    """rebase current unpushed revisions onto the Subversion head
+
+    This moves a line of development from making its own head to the top of
+    Subversion development, linearizing the changes. In order to make sure you
+    rebase on top of the current top of Subversion work, you should probably run
+    'hg svn pull' before running this.
+
+    Also looks for svnextrafn and svnsourcerev in **opts.
+    """
+    if not opts.get('svn', False):
+        return orig(ui, repo, **opts)
+    def extrafn2(ctx, extra):
+        """defined here so we can add things easily.
+        """
+        extra['branch'] = ctx.branch()
+    extrafn = opts.get('svnextrafn', extrafn2)
+    sourcerev = opts.get('svnsourcerev', repo.parents()[0].node())
+    meta = repo.svnmeta()
+    hashes = meta.revmap.hashes()
+    o_r = util.outgoing_revisions(repo, hashes, sourcerev=sourcerev)
+    if not o_r:
+        ui.status('Nothing to rebase!\n')
+        return 0
+    if len(repo[sourcerev].children()):
+        ui.status('Refusing to rebase non-head commit like a coward\n')
+        return 0
+    parent_rev = repo[o_r[-1]].parents()[0]
+    target_rev = parent_rev
+    p_n = parent_rev.node()
+    exhausted_choices = False
+    while target_rev.children() and not exhausted_choices:
+        for c in target_rev.children():
+            exhausted_choices = True
+            n = c.node()
+            if (n in hashes and hashes[n][1] == hashes[p_n][1]):
+                target_rev = c
+                exhausted_choices = False
+                break
+    if parent_rev == target_rev:
+        ui.status('Already up to date!\n')
+        return 0
+    return orig(ui, repo, dest=node.hex(target_rev.node()),
+                base=node.hex(sourcerev),
+                extrafn=extrafn)
+
+
+optionmap = {
+    'tagpaths': ('hgsubversion', 'tagpaths'),
+    'authors': ('hgsubversion', 'authormap'),
+    'filemap': ('hgsubversion', 'filemap'),
+    'branchmap': ('hgsubversion', 'branchmap'),
+    'tagmap': ('hgsubversion', 'tagmap'),
+    'stupid': ('hgsubversion', 'stupid'),
+    'defaulthost': ('hgsubversion', 'defaulthost'),
+    'defaultauthors': ('hgsubversion', 'defaultauthors'),
+    'usebranchnames': ('hgsubversion', 'usebranchnames'),
+    'layout': ('hgsubversion', 'layout'),
+    'startrev': ('hgsubversion', 'startrev'),
+}
+
+dontretain = { 'hgsubversion': set(['authormap', 'filemap', 'layout', ]) }
+
+def clone(orig, ui, source, dest=None, **opts):
+    """
+    Some of the options listed below only apply to Subversion
+    %(target)s. See 'hg help %(extension)s' for more information on
+    them as well as other ways of customising the conversion process.
+    """
+
+    data = {}
+    def hgclonewrapper(orig, ui, *args, **opts):
+        if getattr(hg, 'peer', None):
+            # Since 1.9 (d976542986d2)
+            origsource = args[1]
+        else:
+            origsource = args[0]
+
+        if isinstance(origsource, str):
+            source, branch, checkout = util.parseurl(ui.expandpath(origsource),
+                                         opts.get('branch'))
+            srcrepo = hg.repository(ui, source)
+        else:
+            srcrepo = origsource
+
+        if srcrepo.capable('subversion'):
+            branches = opts.pop('branch', None)
+            if branches:
+                data['branches'] = branches
+                ui.setconfig('hgsubversion', 'branch', branches[-1])
+
+        data['srcrepo'], data['dstrepo'] = orig(ui, *args, **opts)
+
+    for opt, (section, name) in optionmap.iteritems():
+        if opt in opts and opts[opt]:
+            ui.setconfig(section, name, str(opts.pop(opt)))
+
+    # calling hg.clone directoly to get the repository instances it returns,
+    # breaks in subtle ways, so we double-wrap
+    orighgclone = extensions.wrapfunction(hg, 'clone', hgclonewrapper)
+    orig(ui, source, dest, **opts)
+    hg.clone = orighgclone
+
+    # do this again; the ui instance isn't shared between the wrappers
+    if data.get('branches'):
+        ui.setconfig('hgsubversion', 'branch', data['branches'][-1])
+
+    dstrepo = data.get('dstrepo')
+    srcrepo = data.get('srcrepo')
+
+    if dstrepo.local() and srcrepo.capable('subversion'):
+        fd = dstrepo.opener("hgrc", "a", text=True)
+        for section in set(s for s, v in optionmap.itervalues()):
+            config = dict(ui.configitems(section))
+            for name in dontretain[section]:
+                config.pop(name, None)
+
+            if config:
+                fd.write('\n[%s]\n' % section)
+                map(fd.write, ('%s = %s\n' % p for p in config.iteritems()))
+
+
+def generic(orig, ui, repo, *args, **opts):
+    """
+    Subversion %(target)s can be used for %(command)s. See 'hg help
+    %(extension)s' for more on the conversion process.
+    """
+
+    branch = opts.get('branch', None)
+    if branch:
+        ui.setconfig('hgsubversion', 'branch', branch[-1])
+
+    for opt, (section, name) in optionmap.iteritems():
+        if opt in opts and opts[opt]:
+            if isinstance(repo, str):
+                ui.setconfig(section, name, opts.pop(opt))
+            else:
+                repo.ui.setconfig(section, name, opts.pop(opt))
+    return orig(ui, repo, *args, **opts)
diff --git a/opt/hgsubversion/notes/hgsubversion.png b/opt/hgsubversion/notes/hgsubversion.png
new file mode 100644
index 0000000000000000000000000000000000000000..34bd80af419ac4a16868d583974febd7bda102fb
GIT binary patch
literal 88113
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

diff --git a/opt/hgsubversion/notes/hgsubversion.svg b/opt/hgsubversion/notes/hgsubversion.svg
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/notes/hgsubversion.svg
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   sodipodi:docbase="G:\MyDocs\temp\svnlogo"
+   sodipodi:docname="hgsubversion.svg"
+   inkscape:version="0.46"
+   sodipodi:version="0.32"
+   version="1.0"
+   x="0.00000000"
+   y="0.00000000"
+   width="1024"
+   height="1024"
+   viewBox="0 0 157.009 94.082"
+   id="svg2"
+   xml:space="preserve"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/Users/danchr/Downloads/hgsubversion.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90"><metadata
+   id="metadata24"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+   inkscape:current-layer="Layer_1"
+   inkscape:window-y="0"
+   inkscape:window-x="144"
+   inkscape:cy="536.26439"
+   inkscape:cx="460.62218"
+   inkscape:zoom="0.55664062"
+   id="base"
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1.0"
+   inkscape:pageopacity="0.0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1152"
+   inkscape:window-height="785"
+   showgrid="false"
+   showguides="false"><inkscape:grid
+     type="xygrid"
+     id="grid5424"
+     visible="true"
+     enabled="true" /></sodipodi:namedview>
+   <defs
+   id="defs84"><linearGradient
+   id="linearGradient5650"><stop
+     style="stop-color:#526482;stop-opacity:1;"
+     offset="0"
+     id="stop5652" /><stop
+     id="stop5660"
+     offset="0.75"
+     style="stop-color:#526482;stop-opacity:0.74452555;" /><stop
+     style="stop-color:#526482;stop-opacity:0;"
+     offset="1"
+     id="stop5654" /></linearGradient><inkscape:perspective
+   sodipodi:type="inkscape:persp3d"
+   inkscape:vp_x="0 : 47.041 : 1"
+   inkscape:vp_y="0 : 1000 : 0"
+   inkscape:vp_z="157.009 : 47.041 : 1"
+   inkscape:persp3d-origin="78.504501 : 31.360667 : 1"
+   id="perspective5232" />
+
+		<filter
+   inkscape:collect="always"
+   id="filter5646"><feGaussianBlur
+     inkscape:collect="always"
+     stdDeviation="1.2513068"
+     id="feGaussianBlur5648" /></filter><filter
+   inkscape:collect="always"
+   id="filter5686"><feGaussianBlur
+     inkscape:collect="always"
+     stdDeviation="0.41579211"
+     id="feGaussianBlur5688" /></filter><filter
+   inkscape:collect="always"
+   id="filter5710"><feGaussianBlur
+     inkscape:collect="always"
+     stdDeviation="0.55954644"
+     id="feGaussianBlur5712" /></filter></defs>
+
+		<g
+   id="g46">
+				<g
+   id="Layer_1">
+					
+					
+					
+					
+					
+					
+					
+					
+					
+					
+					
+					
+					
+					
+				<path
+   id="path49"
+   style="fill:#809cc9"
+   d="M 156.07994,-30.712905 L 0.9276555,-30.712905 L 0.9276555,104.78678 L 156.08134,104.78678 L 156.08134,-30.712905 L 156.07994,-30.712905 z" /><g
+   id="g5907"
+   transform="translate(-0.3822713,0)"><path
+     d="M 10.394451,115.96731 C 10.394451,116.58957 10.23494,117.1486 9.914463,117.64439 C 9.593997,118.14019 9.1255086,118.5282 8.5075566,118.80987 C 7.8910476,119.09011 7.1595656,119.23094 6.3159966,119.23094 C 5.3014166,119.23094 4.4650276,119.03837 3.8068486,118.6561 C 3.3383506,118.38162 2.9575246,118.01229 2.6657946,117.55098 C 2.3726296,117.08967 2.2260526,116.64131 2.2260526,116.20587 C 2.2260526,115.95294 2.3137136,115.73738 2.4904706,115.55631 C 2.6643606,115.37667 2.8885426,115.28613 3.1601506,115.28613 C 3.3800306,115.28613 3.5654106,115.35656 3.7191776,115.49595 C 3.8729526,115.63535 4.0008506,115.84516 4.1086356,116.12252 C 4.2394106,116.45018 4.3802436,116.72323 4.5340096,116.94309 C 4.6849076,117.16297 4.8990336,117.34404 5.1763866,117.48775 C 5.4508716,117.63146 5.8144526,117.70188 6.2657036,117.70188 C 6.8836466,117.70188 7.3851916,117.55673 7.7717726,117.26932 C 8.1583436,116.9819 8.3509126,116.62119 8.3509126,116.1915 C 8.3509126,115.84947 8.2474396,115.57068 8.0376256,115.35799 C 7.8292546,115.14674 7.5605146,114.98291 7.2299816,114.87081 C 6.8994576,114.75873 6.4582726,114.63944 5.9049916,114.51298 C 5.1648956,114.3391 4.5469436,114.13646 4.0468426,113.90366 C 3.5481656,113.67372 3.1529706,113.35612 2.8612406,112.95662 C 2.5680756,112.5571 2.4214886,112.05844 2.4214886,111.46492 C 2.4214886,110.89727 2.5752646,110.39429 2.8842316,109.95454 C 3.1932076,109.51335 3.6387046,109.17564 4.2235996,108.93852 C 4.8084946,108.70284 5.4954196,108.585 6.2872606,108.585 C 6.9167036,108.585 7.4627966,108.66404 7.9240956,108.81924 C 8.3839696,108.97588 8.7676736,109.1857 9.0708946,109.44438 C 9.3755596,109.70593 9.596874,109.97754 9.737706,110.26351 C 9.878538,110.54806 9.947521,110.8283 9.947521,111.09846 C 9.947521,111.34708 9.859859,111.56983 9.68597,111.76958 C 9.510646,111.96934 9.2936426,112.0685 9.0320916,112.0685 C 8.7949756,112.0685 8.6138976,112.00814 8.4917446,111.88886 C 8.3681586,111.76958 8.2345146,111.57414 8.0908056,111.30253 C 7.9025476,110.91308 7.6783556,110.61129 7.4153706,110.39286 C 7.1523866,110.17442 6.7313146,110.06663 6.1507306,110.06663 C 5.6132606,110.06663 5.1778296,110.18448 4.8487306,110.42016 C 4.5196416,110.65727 4.3529406,110.94183 4.3529406,111.27379 C 4.3529406,111.48073 4.4089796,111.65893 4.5210756,111.80839 C 4.6331716,111.95784 4.7883716,112.08575 4.9838176,112.19497 C 5.1792636,112.30418 5.3790216,112.38753 5.5816466,112.44788 C 5.7842726,112.50969 6.1148056,112.59735 6.5789816,112.71519 C 7.1566976,112.85171 7.6812336,113.00117 8.1497206,113.165 C 8.6182086,113.32882 9.0177236,113.52714 9.3453796,113.76138 C 9.67447,113.99563 9.931709,114.29167 10.115655,114.64951 C 10.299601,115.00735 10.394451,115.44853 10.394451,115.96731 z"
+     style="fill:#809cc9"
+     id="path5859" /><path
+     d="M 18.660578,114.79897 L 18.660578,109.84963 C 18.660578,109.42857 18.755428,109.11241 18.94512,108.9026 C 19.134821,108.69278 19.384871,108.58643 19.693847,108.58643 C 20.018626,108.58643 20.274431,108.69134 20.464122,108.9026 C 20.653823,109.11385 20.748664,109.43 20.748664,109.85251 L 20.748664,114.92399 C 20.748664,115.50026 20.813335,115.98169 20.941233,116.3697 C 21.06914,116.75771 21.2962,117.05806 21.623856,117.27075 C 21.950068,117.48344 22.407065,117.59122 22.994838,117.59122 C 23.805349,117.59122 24.378753,117.37422 24.713588,116.94022 C 25.048433,116.50623 25.218011,115.84804 25.218011,114.9671 L 25.218011,109.85251 C 25.218011,109.42569 25.311418,109.10666 25.498241,108.89829 C 25.686498,108.68991 25.936549,108.58643 26.249837,108.58643 C 26.563124,108.58643 26.81892,108.68991 27.012932,108.89829 C 27.206935,109.1081 27.304663,109.42426 27.304663,109.85107 L 27.304663,114.80615 C 27.304663,115.61235 27.225624,116.28348 27.06898,116.82238 C 26.912336,117.36129 26.616295,117.83552 26.17942,118.24366 C 25.805774,118.58568 25.371777,118.83573 24.877421,118.99238 C 24.380187,119.14902 23.802481,119.2295 23.138547,119.2295 C 22.348149,119.2295 21.669838,119.14183 21.09932,118.97227 C 20.530227,118.80124 20.063173,118.53682 19.703904,118.17899 C 19.344635,117.82115 19.080207,117.36272 18.912072,116.80226 C 18.743928,116.24467 18.660578,115.57643 18.660578,114.79897 z"
+     style="fill:#809cc9"
+     id="path5861" /><path
+     d="M 40.413773,119.04986 L 37.266549,119.04986 C 36.81243,119.04986 36.487651,118.94783 36.293639,118.74376 C 36.098193,118.5397 36.001909,118.21635 36.001909,117.7723 L 36.001909,110.04077 C 36.001909,109.58809 36.101071,109.26187 36.300828,109.06211 C 36.499142,108.86235 36.821052,108.76463 37.266549,108.76463 L 40.604908,108.76463 C 41.096387,108.76463 41.523204,108.79481 41.883907,108.85373 C 42.24462,108.91266 42.567964,109.0305 42.853949,109.20151 C 43.096811,109.34665 43.310937,109.5306 43.49776,109.75191 C 43.684584,109.97323 43.82685,110.21897 43.926012,110.48771 C 44.02373,110.75787 44.074033,111.04098 44.074033,111.3399 C 44.074033,112.36885 43.560988,113.12044 42.532039,113.59756 C 43.881464,114.02868 44.558332,114.86795 44.558332,116.1139 C 44.558332,116.69017 44.410311,117.21039 44.115703,117.67026 C 43.821104,118.13156 43.424467,118.47359 42.924365,118.69347 C 42.609644,118.82424 42.248931,118.91477 41.840803,118.96795 C 41.432665,119.02399 40.956988,119.04986 40.413773,119.04986 z M 38.088561,110.31525 L 38.088561,112.9868 L 39.99989,112.9868 C 40.51868,112.9868 40.921063,112.9365 41.204171,112.83734 C 41.487279,112.73818 41.704273,112.55136 41.855171,112.27256 C 41.971569,112.07568 42.030494,111.85438 42.030494,111.61007 C 42.030494,111.0884 41.845114,110.74207 41.474345,110.56961 C 41.105009,110.3986 40.540237,110.31237 39.781453,110.31237 L 38.088561,110.31237 L 38.088561,110.31525 z M 40.260007,114.46987 L 38.088561,114.46987 L 38.088561,117.50069 L 40.330423,117.50069 C 41.740198,117.50069 42.445811,116.98764 42.445811,115.963 C 42.445811,115.43991 42.263299,115.05907 41.898285,114.82339 C 41.531827,114.58771 40.985734,114.46987 40.260007,114.46987 z"
+     style="fill:#809cc9"
+     id="path5863" /><path
+     d="M 54.344895,109.9531 L 56.670107,116.84968 L 59.003952,109.90425 C 59.126076,109.53922 59.215209,109.28486 59.276975,109.14403 C 59.337334,109.00175 59.436496,108.87385 59.577365,108.75745 C 59.718141,108.64105 59.909342,108.58643 60.152185,108.58643 C 60.328952,108.58643 60.49419,108.62955 60.646494,108.71864 C 60.797392,108.80775 60.916705,108.92702 61.002933,109.07361 C 61.090566,109.22163 61.13368,109.36965 61.13368,109.52054 C 61.13368,109.62257 61.11934,109.73467 61.092066,109.85251 C 61.063292,109.97323 61.028801,110.08962 60.987093,110.2046 C 60.945479,110.31813 60.902365,110.4374 60.860657,110.55812 L 58.37449,117.28512 C 58.285357,117.54236 58.196317,117.7881 58.107184,118.01947 C 58.018051,118.25084 57.916059,118.45491 57.798218,118.63023 C 57.68181,118.80556 57.52661,118.95071 57.332598,119.06279 C 57.138595,119.17346 56.901469,119.23094 56.622682,119.23094 C 56.342442,119.23094 56.105326,119.17489 55.911323,119.06567 C 55.715877,118.95645 55.560667,118.80987 55.441392,118.63023 C 55.322117,118.4506 55.218643,118.24509 55.129548,118.01516 C 55.040443,117.78666 54.952781,117.54236 54.862252,117.28512 L 52.417764,110.61417 C 52.376084,110.49345 52.331536,110.37418 52.286989,110.25634 C 52.243875,110.13993 52.205072,110.01346 52.176327,109.87694 C 52.146156,109.74186 52.130345,109.62688 52.130345,109.53348 C 52.130345,109.29492 52.225195,109.07792 52.417764,108.88104 C 52.608889,108.68416 52.850327,108.58643 53.13918,108.58643 C 53.494138,108.58643 53.745632,108.69422 53.89221,108.91266 C 54.038796,109.13109 54.191129,109.47599 54.344895,109.9531 z"
+     style="fill:#999999"
+     id="path5865" /><path
+     d="M 75.962957,110.36124 L 71.26805,110.36124 L 71.26805,112.89914 L 75.590772,112.89914 C 75.908408,112.89914 76.14694,112.96955 76.303556,113.11182 C 76.458769,113.25553 76.537869,113.44235 76.537869,113.6766 C 76.537869,113.91084 76.460269,114.10197 76.306461,114.24856 C 76.152657,114.39514 75.914126,114.46987 75.590772,114.46987 L 71.26805,114.46987 L 71.26805,117.41158 L 76.123977,117.41158 C 76.451639,117.41158 76.697389,117.48631 76.865539,117.63577 C 77.030769,117.78666 77.114099,117.98642 77.114099,118.23647 C 77.114099,118.4779 77.030769,118.67478 76.865539,118.82424 C 76.698799,118.9737 76.451639,119.04986 76.123977,119.04986 L 70.460416,119.04986 C 70.006315,119.04986 69.67865,118.94926 69.478827,118.74664 C 69.279097,118.54545 69.179936,118.2221 69.179936,117.7723 L 69.179936,110.04077 C 69.179936,109.74186 69.224455,109.49755 69.313589,109.30785 C 69.402722,109.11816 69.542092,108.98019 69.731792,108.89398 C 69.921493,108.80775 70.164336,108.76463 70.460416,108.76463 L 75.962957,108.76463 C 76.294933,108.76463 76.542179,108.83793 76.703109,108.98307 C 76.865539,109.12966 76.945949,109.31935 76.945949,109.55647 C 76.945949,109.7979 76.865539,109.99191 76.703109,110.13705 C 76.542179,110.28795 76.294933,110.36124 75.962957,110.36124 z"
+     style="fill:#999999"
+     id="path5867" /><path
+     d="M 88.025919,114.64951 L 87.297299,114.64951 L 87.297299,117.95912 C 87.297299,118.39455 87.202449,118.71358 87.008429,118.92053 C 86.817329,119.12747 86.565859,119.23094 86.256849,119.23094 C 85.924869,119.23094 85.667689,119.12315 85.483709,118.90759 C 85.301219,118.69203 85.210679,118.37587 85.210679,117.95912 L 85.210679,110.04364 C 85.210679,109.59527 85.311249,109.26905 85.512479,109.06642 C 85.713609,108.86523 86.038369,108.76607 86.488259,108.76607 L 89.875409,108.76607 C 90.342539,108.76607 90.743399,108.78619 91.075379,108.82499 C 91.405949,108.8638 91.706249,108.94427 91.973549,109.06354 C 92.295499,109.20007 92.581459,109.39408 92.828609,109.647 C 93.077269,109.89993 93.265559,110.1931 93.393399,110.5265 C 93.522739,110.86134 93.587409,111.21487 93.587409,111.58995 C 93.587409,112.35736 93.371849,112.96955 92.939299,113.42798 C 92.506759,113.88641 91.850019,114.21264 90.971909,114.40376 C 91.341279,114.60064 91.693409,114.89094 92.029599,115.2732 C 92.365889,115.65834 92.666279,116.06647 92.932079,116.50048 C 93.196579,116.93304 93.402029,117.32392 93.548609,117.6717 C 93.698099,118.02091 93.769989,118.26091 93.769989,118.39168 C 93.769989,118.5282 93.726879,118.66185 93.640649,118.7955 C 93.552919,118.92915 93.435109,119.03406 93.285619,119.11165 C 93.136219,119.18926 92.963759,119.22806 92.768349,119.22806 C 92.534029,119.22806 92.337209,119.17201 92.179089,119.06279 C 92.019569,118.95358 91.883009,118.81274 91.769509,118.64461 C 91.654599,118.47647 91.499389,118.22785 91.303879,117.89876 L 90.468969,116.50766 C 90.170079,115.99749 89.901279,115.60948 89.665649,115.34075 C 89.429929,115.07345 89.189989,114.89237 88.947059,114.79322 C 88.704209,114.69406 88.395199,114.64951 88.025919,114.64951 z M 89.218669,110.31525 L 87.298699,110.31525 L 87.298699,113.16643 L 89.162629,113.16643 C 89.662749,113.16643 90.083859,113.12188 90.424449,113.03422 C 90.764959,112.94656 91.026549,112.79854 91.206219,112.58873 C 91.385799,112.37891 91.476339,112.08861 91.476339,111.72072 C 91.476339,111.4333 91.403039,111.17894 91.259359,110.9605 C 91.115679,110.74207 90.914449,110.5768 90.657179,110.46758 C 90.412929,110.36699 89.934359,110.31525 89.218669,110.31525 z"
+     style="fill:#999999"
+     id="path5869" /><path
+     d="M 109.84376,115.96731 C 109.84376,116.58957 109.68424,117.1486 109.36379,117.64439 C 109.04334,118.14019 108.57481,118.5282 107.95828,118.80987 C 107.34035,119.09011 106.61032,119.23094 105.76388,119.23094 C 104.75071,119.23094 103.91439,119.03837 103.25616,118.6561 C 102.78771,118.38162 102.40681,118.01229 102.11514,117.55098 C 101.82197,117.08967 101.67538,116.64131 101.67538,116.20587 C 101.67538,115.95294 101.76451,115.73738 101.93837,115.55631 C 102.11514,115.37667 102.33783,115.28613 102.60945,115.28613 C 102.82933,115.28613 103.01612,115.35656 103.16852,115.49595 C 103.32083,115.63535 103.45017,115.84516 103.55795,116.12252 C 103.6887,116.45018 103.82957,116.72323 103.98187,116.94309 C 104.13427,117.16297 104.34975,117.34404 104.62427,117.48775 C 104.9016,117.63146 105.26376,117.70188 105.71355,117.70188 C 106.33298,117.70188 106.8331,117.55673 107.22113,117.26932 C 107.60765,116.9819 107.80026,116.62119 107.80026,116.1915 C 107.80026,115.84947 107.69678,115.57068 107.48693,115.35799 C 107.27858,115.14674 107.00987,114.98291 106.6793,114.87081 C 106.35023,114.75873 105.90906,114.63944 105.3557,114.51298 C 104.61565,114.3391 103.99771,114.13646 103.499,113.90366 C 102.99897,113.67372 102.60373,113.35612 102.31196,112.95662 C 102.01879,112.5571 101.8723,112.05844 101.8723,111.46492 C 101.8723,110.89727 102.02601,110.39429 102.33502,109.95454 C 102.64394,109.51335 103.08942,109.17564 103.67436,108.93852 C 104.25921,108.70284 104.94762,108.585 105.73661,108.585 C 106.36747,108.585 106.91352,108.66404 107.37484,108.81924 C 107.83475,108.97588 108.21846,109.1857 108.52166,109.44438 C 108.82627,109.70593 109.04765,109.97754 109.18843,110.26351 C 109.3293,110.54806 109.39828,110.8283 109.39828,111.09846 C 109.39828,111.34708 109.31065,111.56983 109.13529,111.76958 C 108.96142,111.96934 108.74436,112.0685 108.48286,112.0685 C 108.24574,112.0685 108.06606,112.00814 107.94253,111.88886 C 107.81891,111.76958 107.68385,111.57414 107.54157,111.30253 C 107.35328,110.91308 107.12909,110.61129 106.86609,110.39286 C 106.6031,110.17442 106.18208,110.06663 105.60145,110.06663 C 105.06262,110.06663 104.62858,110.18448 104.29951,110.42016 C 103.96894,110.65727 103.8037,110.94183 103.8037,111.27379 C 103.8037,111.48073 103.85975,111.65893 103.97184,111.80839 C 104.08394,111.95784 104.23915,112.08575 104.43597,112.19497 C 104.63289,112.30274 104.83122,112.38753 105.03244,112.44788 C 105.23358,112.50969 105.56556,112.59735 106.02978,112.71519 C 106.60741,112.85171 107.13199,113.00117 107.60043,113.165 C 108.06897,113.32882 108.46852,113.52714 108.79759,113.76138 C 109.12666,113.99563 109.38103,114.29167 109.56642,114.64951 C 109.75181,115.00878 109.84376,115.44853 109.84376,115.96731 z"
+     style="fill:#999999"
+     id="path5871" /><path
+     d="M 118.17747,117.95912 L 118.17747,109.84963 C 118.17747,109.42857 118.27232,109.11241 118.46483,108.9026 C 118.65744,108.69278 118.906,108.58643 119.21211,108.58643 C 119.52543,108.58643 119.7798,108.68991 119.97382,108.89829 C 120.16923,109.1081 120.26549,109.42282 120.26549,109.84963 L 120.26549,117.95912 C 120.26549,118.38593 120.16923,118.70353 119.97382,118.91334 C 119.7784,119.12315 119.52543,119.23094 119.21211,119.23094 C 118.91181,119.23094 118.66316,119.12459 118.46914,118.91046 C 118.27373,118.69634 118.17747,118.38018 118.17747,117.95912 z"
+     style="fill:#999999"
+     id="path5873" /><path
+     d="M 133.57585,108.58643 C 134.64076,108.58643 135.55468,108.802 136.31779,109.23457 C 137.08091,109.66856 137.66004,110.28364 138.05238,111.08121 C 138.44321,111.88023 138.64013,112.81579 138.64013,113.8936 C 138.64013,114.68831 138.53234,115.4126 138.31678,116.0636 C 138.10261,116.71316 137.78076,117.27794 137.35103,117.75505 C 136.91998,118.23216 136.39249,118.59862 135.76594,118.85155 C 135.13938,119.10304 134.42229,119.23094 133.61606,119.23094 C 132.81133,119.23094 132.09273,119.10016 131.45896,118.84149 C 130.82378,118.58137 130.29208,118.21491 129.86816,117.74212 C 129.44423,117.26932 129.12229,116.70023 128.90531,116.03629 C 128.68834,115.37093 128.57905,114.65238 128.57905,113.88067 C 128.57905,113.08883 128.69115,112.3631 128.91825,111.70348 C 129.14525,111.04242 129.47291,110.48052 129.90405,110.01778 C 130.33378,109.55503 130.85546,109.20007 131.47339,108.95433 C 132.09133,108.70859 132.79118,108.58643 133.57585,108.58643 z M 136.52905,113.88067 C 136.52905,113.12332 136.40833,112.46657 136.16399,111.91186 C 135.92255,111.35714 135.57624,110.93607 135.12504,110.65296 C 134.67525,110.36842 134.15639,110.22616 133.57435,110.22616 C 133.15905,110.22616 132.77534,110.30519 132.4219,110.46184 C 132.07118,110.61848 131.76657,110.84984 131.5122,111.1502 C 131.25783,111.45055 131.0581,111.83569 130.91001,112.30418 C 130.76202,112.77267 130.69013,113.29721 130.69013,113.8821 C 130.69013,114.46843 130.76342,115.00016 130.91001,115.47583 C 131.0581,115.95007 131.26504,116.34384 131.53375,116.65711 C 131.80247,116.96896 132.10998,117.2032 132.4578,117.35841 C 132.80552,117.51218 133.18783,117.59122 133.60312,117.59122 C 134.13483,117.59122 134.62351,117.45613 135.0704,117.18883 C 135.51738,116.92154 135.87082,116.50623 136.13381,115.9472 C 136.3983,115.38817 136.52905,114.69693 136.52905,113.88067 z"
+     style="fill:#999999"
+     id="path5875" /><path
+     d="M 149.61662,109.83526 L 153.63755,115.90696 L 153.63755,109.77921 C 153.63755,109.38258 153.72237,109.08367 153.89342,108.88391 C 154.06297,108.68416 154.29147,108.58643 154.58174,108.58643 C 154.88063,108.58643 155.11485,108.68416 155.28881,108.88391 C 155.45976,109.08367 155.54749,109.38114 155.54749,109.77921 L 155.54749,117.87433 C 155.54749,118.77825 155.17381,119.2295 154.42653,119.2295 C 154.23974,119.2295 154.07159,119.20076 153.9221,119.14759 C 153.7727,119.09297 153.63183,119.00819 153.50108,118.89035 C 153.37024,118.77394 153.24812,118.63599 153.13602,118.47934 C 153.02393,118.3227 152.91324,118.16174 152.80114,117.99791 L 148.87938,111.99808 L 148.87938,118.0324 C 148.87938,118.42617 148.78884,118.72365 148.60635,118.92484 C 148.42237,119.12747 148.18956,119.22806 147.9036,119.22806 C 147.60902,119.22806 147.37189,119.12603 147.19363,118.92196 C 147.01546,118.71789 146.92642,118.42186 146.92642,118.03385 L 146.92642,110.09394 C 146.92642,109.75766 146.96231,109.49324 147.03851,109.30067 C 147.12755,109.09086 147.27414,108.91697 147.48249,108.78476 C 147.68803,108.6511 147.91082,108.585 148.15075,108.585 C 148.33755,108.585 148.49857,108.61518 148.63222,108.67554 C 148.76588,108.73589 148.88228,108.8178 148.98285,108.92128 C 149.08342,109.02475 149.18689,109.15696 149.29327,109.32078 C 149.39534,109.48749 149.50303,109.6585 149.61662,109.83526 z"
+     style="fill:#999999"
+     id="path5877" /></g><path
+   id="path5879"
+   style="fill:none;stroke:#809cc9;stroke-width:1.43708742"
+   d="M 0.71854451,124.82695 L 156.29046,124.82695" /><path
+   d="M 23.128208,80.247192 C 24.989875,79.550002 26.968166,79.201759 28.868711,79.201759 C 30.6915,79.201759 31.855159,79.581523 32.514298,80.374035 C 33.910323,79.582244 35.810868,79.201759 37.284649,79.201759 C 41.706874,79.201759 41.861511,80.722255 41.861511,85.189103 L 41.861511,95.071417 C 41.861511,95.514885 41.939258,95.514885 39.728591,95.514885 L 39.728591,84.998475 C 39.728591,81.76765 39.650827,80.785312 37.130904,80.785312 C 36.006132,80.785312 34.842482,81.006674 33.601076,81.703894 L 33.601076,95.292757 C 33.562198,95.419629 33.484451,95.451107 33.214073,95.482635 C 33.174303,95.482635 33.136318,95.514156 33.096556,95.514156 L 31.467264,95.514156 L 31.467264,84.998475 C 31.467264,81.894434 31.467264,80.753747 28.830717,80.753747 C 27.705936,80.753747 26.581163,80.943617 25.262002,81.48251 L 25.262002,95.071417 C 25.262002,95.514885 25.339766,95.514885 23.129091,95.514885 L 23.129091,80.247192 M 52.287543,79.201759 C 48.990973,79.201759 45.345394,79.993514 45.345394,88.451434 C 45.345394,94.976801 48.020828,95.989984 51.706151,95.989984 C 54.536212,95.989984 56.980155,95.198238 56.980155,94.817724 C 56.980155,94.342705 56.902399,93.613972 56.708893,93.201959 C 55.506365,93.899149 53.799318,94.278876 51.899665,94.278876 C 49.301103,94.278876 47.556061,93.645485 47.5163,88.735853 C 49.223356,88.735853 53.567826,88.704281 56.786649,88.165491 C 57.057911,87.120096 57.173652,85.599578 57.173652,84.269704 C 57.173652,81.102011 55.660993,79.201759 52.287543,79.201759 M 52.093162,80.785312 C 54.420462,80.785312 55.040741,82.083599 55.07961,84.61799 C 55.07961,85.282872 55.040741,86.074656 54.924107,86.834889 C 52.75231,87.27843 49.066953,87.27843 47.515416,87.27843 C 47.788445,81.514001 50.114862,80.785312 52.093162,80.785312 M 60.548852,80.595427 C 62.370767,79.581523 64.03982,79.201759 65.745984,79.201759 C 67.64652,79.201759 68.538918,79.645301 68.538918,80.184179 C 68.538918,80.500841 68.42141,81.039711 68.227904,81.35641 C 67.684515,81.102724 67.065137,80.88134 66.055223,80.88134 C 64.814709,80.88134 63.651042,81.165781 62.72065,81.862964 L 62.72065,95.072174 C 62.72065,95.515672 62.759536,95.515672 60.548852,95.515672 L 60.548852,80.595427 M 80.129509,80.247192 C 80.129509,79.835172 78.5011,79.201759 76.871808,79.201759 C 73.728974,79.201759 69.88989,80.120356 69.88989,87.944085 C 69.88989,95.355822 72.218074,96.021476 76.289541,96.021476 C 78.305828,96.021476 80.129509,95.102938 80.129509,94.596339 C 80.129509,94.311125 80.052637,93.867598 79.857364,93.487143 C 79.042722,93.993713 77.802209,94.469482 76.483931,94.469482 C 73.691872,94.469482 72.062571,93.804563 72.062571,88.039413 C 72.062571,81.672373 74.738005,80.785312 77.065305,80.785312 C 78.383583,80.785312 79.120478,81.102011 79.857364,81.514001 C 80.052637,81.13426 80.129509,80.595427 80.129509,80.247192 M 94.986626,94.976801 C 93.279578,95.705513 91.030908,95.990698 89.091486,95.990698 C 84.126746,95.990698 83.389851,94.217341 83.389851,90.004134 L 83.389851,80.089571 C 83.389851,79.678323 83.351857,79.678323 85.562541,79.678323 L 85.562541,90.194799 C 85.562541,93.362537 86.105046,94.43952 89.013721,94.43952 C 90.138511,94.43952 91.690047,94.249627 92.853697,93.679265 L 92.853697,80.090328 C 92.853697,79.679051 92.77595,79.679051 94.986626,79.679051 L 94.986626,94.976801 M 99.409735,80.595427 C 101.23163,79.581523 102.8998,79.201759 104.60597,79.201759 C 106.5065,79.201759 107.3989,79.645301 107.3989,80.184179 C 107.3989,80.500841 107.28227,81.039711 107.08876,81.35641 C 106.54537,81.102724 105.92513,80.88134 104.9161,80.88134 C 103.67557,80.88134 102.51192,81.165781 101.58065,81.862964 L 101.58065,95.072174 C 101.58065,95.515672 101.6204,95.515672 99.409735,95.515672 L 99.409735,80.595427 M 112.37865,79.676837 C 110.67162,79.676837 110.16799,79.676837 110.16799,80.532362 L 110.16799,95.514885 C 112.33979,95.514885 112.37865,95.514885 112.37865,95.071417 L 112.37865,79.676837 M 110.09023,74.102249 C 110.09023,75.147682 110.47812,75.527445 111.253,75.558937 C 112.14541,75.558937 112.57216,75.020854 112.57216,74.070015 C 112.61103,73.088368 112.30002,72.644848 111.4085,72.644848 C 110.55589,72.644848 110.1291,73.183689 110.09023,74.102249 M 116.05782,80.595427 C 116.05782,80.247192 116.13557,80.056586 116.29108,79.962 C 117.02797,79.613765 119.85979,79.201759 123.07862,79.201759 C 125.52254,79.201759 127.0732,80.184179 127.0732,83.034445 L 127.0732,85.093024 C 127.0732,90.731353 126.87969,94.944574 126.87969,94.944574 C 125.98732,95.355822 124.32001,95.989984 121.60394,95.989984 C 118.81189,96.021476 116.01982,95.800077 116.01982,91.111874 C 116.01982,86.645768 118.88964,85.948593 121.75946,85.948593 C 122.84535,85.948593 124.12563,86.04387 124.97915,86.329062 C 124.97915,86.329062 124.97915,84.269704 124.97915,83.382687 C 124.97915,81.324131 123.73863,80.880597 122.26397,80.880597 C 120.28654,80.880597 117.53336,81.197303 116.33083,81.672373 C 116.09759,81.324131 116.05782,80.785312 116.05782,80.595427 M 124.97826,87.628114 C 124.24138,87.405972 123.19436,87.311423 122.45746,87.311423 C 120.24678,87.311423 118.22962,87.691186 118.22962,91.175652 C 118.22962,94.43796 119.89779,94.470247 121.87518,94.470247 C 123.11659,94.470247 124.35801,94.185084 124.78389,93.805328 C 124.78476,93.804563 124.97826,90.003377 124.97826,87.628114 M 133.20865,95.071417 C 133.20865,95.514885 133.24753,95.514885 131.07484,95.514885 L 131.07484,73.088368 C 131.07484,72.232821 131.5016,72.232821 133.20865,72.232821 L 133.20865,95.071417"
+   style="fill:#010101;stroke-width:2.02999997;stroke-miterlimit:4;stroke-dasharray:none"
+   id="text2611" /><g
+   transform="matrix(0.9797274,0,0,0.832158,170.37768,-31.752119)"
+   id="g4503"><path
+     d="M -45.749655,92.691592 C -25.709638,59.370739 -49.98206,5.3291313 -94.363693,10.819389 C -134.46337,15.776665 -135.10949,57.983708 -99.76917,68.010455 C -69.186498,76.695132 -93.451029,96.093536 -92.742037,109.01138 C -92.030055,121.92728 -66.155038,126.61324 -45.749655,92.691592 z"
+     style="fill:#1b1a1b"
+     id="path2339" /><circle
+     cx="33.728001"
+     cy="85.363998"
+     r="15.414"
+     transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)"
+     style="fill:#1b1a1b"
+     id="circle2341"
+     sodipodi:cx="33.728001"
+     sodipodi:cy="85.363998"
+     sodipodi:rx="15.414"
+     sodipodi:ry="15.414" /><path
+     d="M -140.06215,48.935849 C -146.31997,49.541603 -150.90082,55.100456 -150.29507,61.358275 C -149.68817,67.620461 -144.12955,72.20487 -137.87064,71.59883 C -131.61373,70.985148 -127.02904,65.427621 -127.63726,59.169282 C -128.24543,52.915596 -133.80324,48.329809 -140.06215,48.935849 z"
+     style="fill:#1b1a1b"
+     id="path2343" /><path
+     d="M -44.99294,91.339709 C -24.951831,58.018571 -49.224253,3.976963 -93.605885,9.4672202 C -133.70556,14.424496 -134.35249,56.632918 -99.012168,66.659664 C -68.429497,75.344341 -92.694028,94.742745 -91.984749,107.66168 C -91.271961,120.5762 -65.398322,125.26135 -44.99294,91.339709 z"
+     style="fill:#bfbfbf"
+     id="path2561" /><path
+     d="M -86.84228,112.75985 C -88.056751,110.79004 -86.19955,108.60176 -84.290569,108.76815 C -81.251858,109.03428 -74.635637,108.73252 -69.415044,105.77341 C -56.372412,98.379694 -36.300952,62.803704 -46.395841,40.365295 C -50.915249,30.320886 -53.115898,27.444964 -57.770162,22.531645 C -58.719625,21.529587 -58.174556,21.584053 -57.531623,21.923221 C -55.014762,23.244092 -50.592026,28.36035 -46.055478,36.687677 C -38.390628,50.757116 -38.788117,67.483141 -41.638835,77.975343 C -43.624548,85.27439 -50.464117,101.78644 -60.480639,108.92577 C -70.5197,116.0815 -82.266433,120.18559 -86.84228,112.75985 z"
+     style="fill:#000000"
+     id="path2563" /><path
+     d="M -95.930347,66.591355 C -102.76341,64.562985 -111.57238,61.738267 -116.66758,55.073789 C -120.42371,50.15984 -122.3305,44.796759 -122.81745,41.755703 C -122.99069,40.670602 -123.13785,39.765332 -122.82526,39.515509 C -122.68064,39.399486 -120.02045,45.412302 -116.04367,50.451645 C -112.06769,55.492366 -106.51047,58.440379 -101.88092,59.511496 C -97.763206,60.46345 -89.233623,62.555175 -86.347769,65.013729 C -83.380949,67.540918 -83.133309,73.00119 -84.131664,73.617197 C -85.138469,74.236583 -87.180025,69.187603 -95.930347,66.591355 z"
+     style="fill:#000000"
+     id="path2565" /><path
+     d="M -81.840812,113.72311 C -81.972699,115.28707 -80.176315,115.59377 -77.75828,115.23141 C -74.658947,114.76654 -72.037923,114.41754 -68.470623,112.62971 C -63.63582,110.20674 -58.742752,106.74072 -55.159223,102.06476 C -44.467444,88.115271 -40.681354,71.610444 -41.264404,69.236185 C -41.459242,71.196944 -44.040349,81.489071 -49.943268,90.767882 C -57.52457,102.68631 -63.022197,109.03464 -75.701416,112.1124 C -79.230011,112.96964 -81.668137,111.66432 -81.840812,113.72311 z"
+     style="fill:#ffffff"
+     id="path2567" /><path
+     d="M -109.96233,59.479354 C -108.51822,60.704238 -105.55938,62.336389 -99.737455,64.245644 C -92.705873,66.551032 -89.282274,68.550326 -87.848506,69.508429 C -86.329222,70.525809 -85.366279,72.795951 -85.27115,70.779631 C -85.17194,68.761076 -86.416123,67.025373 -89.192166,66.104839 C -91.070345,65.481234 -94.229847,63.996111 -97.258539,63.398373 C -99.204694,63.014221 -102.37098,62.251845 -105.08636,61.420426 C -106.57454,60.963046 -108.09089,60.161888 -109.96233,59.479354 z"
+     style="fill:#ffffff"
+     id="path2569" /><circle
+     cx="34.681"
+     cy="84.375"
+     r="15.414"
+     transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)"
+     style="fill:#bfbfbf"
+     id="circle2577"
+     sodipodi:cx="34.681"
+     sodipodi:cy="84.375"
+     sodipodi:rx="15.414"
+     sodipodi:ry="15.414" /><path
+     d="M -128.68413,108.37945 C -115.15301,120.91784 -94.786007,103.69471 -103.75445,88.482597 C -104.76154,86.774656 -106.06907,85.474351 -105.63906,86.782721 C -102.77288,95.529828 -105.42141,102.44941 -110.3632,106.01451 C -115.20857,109.5112 -121.86847,110.09622 -127.20028,107.33186 C -128.76601,106.5203 -129.41538,107.70291 -128.68413,108.37945 z"
+     style="fill:#000000"
+     id="path2579" /><path
+     d="M -118.06686,110.95477 C -116.34413,110.59244 -106.32442,107.99742 -103.97055,99.756195 C -103.23743,97.186709 -103.1058,97.702893 -103.31295,99.095232 C -104.37035,106.20143 -111.08741,111.44338 -116.80312,111.63773 C -117.963,111.75704 -119.48484,111.25131 -118.06686,110.95477 z"
+     style="fill:#ffffff"
+     id="path2585" /><path
+     d="M -139.30435,47.583681 C -145.56216,48.189435 -150.14301,53.748288 -149.53726,60.006106 C -148.93065,66.2672 -143.37174,70.852702 -137.11392,70.246948 C -130.85592,69.632979 -126.27151,64.074361 -126.88083,57.816308 C -127.48791,51.562336 -133.04544,46.977641 -139.30435,47.583681 z"
+     style="fill:#bfbfbf"
+     id="path2589" /><path
+     d="M -144.46878,67.571208 C -144.39939,68.375508 -143.29781,69.408789 -141.56718,69.883196 C -140.08038,70.290771 -136.24758,71.332594 -131.32372,68.224839 C -126.39986,65.117084 -125.8321,56.804464 -128.07041,54.35955 C -128.76326,53.121154 -129.66426,52.21957 -128.94737,54.195974 C -127.13695,59.186468 -130.65487,63.854586 -133.68917,66.0162 C -136.72238,68.177528 -140.56932,67.154692 -142.14014,66.675779 C -143.71095,66.196867 -144.53929,66.740369 -144.46878,67.571208 z"
+     style="fill:#000000"
+     id="path2591" /><path
+     d="M -138.11472,68.687851 C -137.66344,68.281557 -135.37889,68.447629 -133.31622,67.338341 C -131.25464,66.229338 -128.80419,63.798254 -128.36692,60.343756 C -128.10933,58.315237 -128.03197,58.824631 -127.92942,59.929403 C -128.24939,65.67243 -133.53086,68.844638 -136.55132,69.263202 C -137.36636,69.376239 -138.8007,69.307247 -138.11472,68.687851 z"
+     style="fill:#ffffff"
+     id="path2597" /><path
+     d="M -47.767489,69.693822 C -39.234739,45.099506 -57.090457,7.9576459 -93.212919,12.425552 C -125.85191,16.461012 -126.37823,50.814524 -97.613495,58.976486 C -65.031338,63.908526 -84.650966,88.487524 -87.434101,100.88229 C -89.929232,111.99304 -61.102889,113.82164 -47.767489,69.693822 z"
+     style="fill:#999999"
+     id="path2561_1_" /><path
+     d="M -70.093288,88.904346 C -78.920045,87.812046 -91.622267,107.74061 -79.645446,105.40671 C -67.670523,103.07448 -91.622267,107.74061 -79.645446,105.40671 C -73.888849,104.55302 -69.119803,102.52058 -64.850547,97.64761 C -59.283982,91.295233 -50.968477,77.5735 -48.563483,68.707586 C -46.537563,61.232354 -47.555881,49.650767 -49.644305,60.532553 C -51.786232,71.700167 -61.266532,89.996647 -70.093288,88.904346 z"
+     style="fill:#f3f3f3"
+     id="path2571" /><path
+     d="M -129.3854,104.84502 C -127.34184,104.87935 -126.10573,105.16706 -124.03635,106.61908 C -119.94568,108.31891 -112.42648,107.24179 -108.9543,102.67081 C -105.48212,98.099823 -105.36811,91.801741 -106.69103,87.996073 C -109.92728,78.682039 -123.67593,78.846722 -129.81795,86.579362 C -136.46216,95.2146 -131.42897,104.81069 -129.3854,104.84502 z"
+     style="fill:#999999"
+     id="path2581" /><path
+     d="M -147.63565,61.683628 C -147.22833,62.966318 -146.18754,64.837882 -143.9897,65.149887 C -141.05481,65.566524 -140.45479,66.892551 -136.9892,66.204631 C -133.52361,65.516711 -130.89674,62.676625 -129.84557,59.535064 C -128.64212,55.188187 -130.44406,52.944024 -133.15599,50.940416 C -135.86791,48.936808 -141.83359,49.152263 -145.3938,52.39768 C -147.92393,54.702631 -148.62733,58.560726 -147.63565,61.683628 z"
+     style="fill:#999999"
+     id="path2593_2_" /><path
+     d="M -136.11009,64.55822 C -133.44721,63.861113 -129.92545,60.232613 -131.67381,57.462279 C -133.83086,54.048798 -139.84051,56.970651 -140.04374,60.77103 C -140.24777,64.572786 -138.93238,65.297057 -136.11009,64.55822 z"
+     style="fill:#f3f3f3"
+     id="path256" /><path
+     d="M -116.11512,105.50904 C -113.8431,104.91425 -106.88259,102.0818 -108.18994,91.962983 C -108.85161,86.83742 -111.64725,98.324328 -116.82409,100.04237 C -124.66721,102.64507 -123.78607,107.51719 -116.11512,105.50904 z"
+     style="fill:#f3f3f3"
+     id="path258" /></g></g>
+			</g>
+	</svg>
\ No newline at end of file
diff --git a/opt/hgsubversion/notes/metadata.txt b/opt/hgsubversion/notes/metadata.txt
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/notes/metadata.txt
@@ -0,0 +1,66 @@
+Branches
+--------
+In order to handle branches that are not immediate children of /branches/, the
+following information must be stored in the revmap:
+
+revision path
+
+Where path is the actual relative path of the branch in svn. An example, with
+the previous format for clarification:
+New                          | Old
+3 <hash> trunk               | 3 <hash>
+4 <hash> branches/foo        | 4 <hash> foo
+
+Tags
+----
+Note that if a tag is committed to, we can handle that case by making a branch
+and then marking it as deleted. The revmap line would look something like this:
+10 <hash> tags/the_tag
+And the commit would be done on the hg branch 'modified-tag/the_tag'. Note that 
+if this was 'tags/releases/1.0.0', then the branch would be 
+'modified-tag/releases/1.0.0'.
+
+Detecting Closing of Branches
+-----------------------------
+Subversion users typically remove branches when done with them. This means that
+if a commit performs a delete operation on the '' path inside a branch, we can
+be sure that the branch no longer exists. The branch should then be marked as
+inactive.
+
+Closing Branches
+----------------
+As of this writing, Mercurial marks branches as inactive by merging them so they
+have no active heads. In order to mark a branch as closed, the active head on
+the branch will be used as the first parent of the new changeset, and the second
+changeset will be either the active head on the hg branch 'closed-branches' or
+be the nullrev. The commit to mark the branch as inactive will happen on the
+'closed-branches' branch in Mercurial.
+
+Recovery
+--------
+hgsubversion stores several pieces of essential metadata in .hg/svn/. In order
+to rebuild this data, the key 'convert_revision' should be stored in the extra
+dictionary of the converted revision. The key should contain data in the format:
+'svn:<uuid>/abs/path@<rev>' where <uuid> is the repo UUID, /abs/path is the
+absolute path to the location the edits were made in Subversion (that is, if it
+was a trunk commit on /foo/trunk, then /foo/trunk is what gets stored here, even
+though the project root does not equal the repo root), and <rev> is the revision
+number of the change in Subversion. This key (and its contents) have been
+chosen to be compatible with the convert extension so that repos originally
+converted to hg using convert can be maintained using hgsubversion if desired.
+
+Tags (tag_info) can be reconstructed by listing the tags directory and then
+running log on each tag to determine its parent changeset. 
+
+The last revision converted (last_rev) can be converted simply by using the
+highest revision number encountered while rebuilding the revision map.
+
+The legacy tag_locations file does not need to be replaced - it will be
+obsoleted as part of the long-term branch refactor.
+
+The url will have to be provided by the user. The uuid can be re-requested from 
+the repository.
+
+branch_info can be rebuilt during the rebuild of the revision map by recording
+the revisions of all active heads of server-side branches. branch_info contains
+a map from branch: (parent_branch, parent_branch_rev, branch_created_rev)
diff --git a/opt/hgsubversion/setup.py b/opt/hgsubversion/setup.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/setup.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import re
+import subprocess
+import sys
+import time
+if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
+    raise SystemExit("Mercurial requires python 2.4 or later.")
+
+try:
+    from distutils.command.build_py import build_py_2to3 as build_py
+except ImportError:
+    from distutils.command.build_py import build_py
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+def runcmd(cmd, env):
+    shell = os.name == 'nt'
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=shell,
+                         stderr=subprocess.PIPE, env=env)
+    out, err = p.communicate()
+    # If root is executing setup.py, but the repository is owned by
+    # another user (as in "sudo python setup.py install") we will get
+    # trust warnings since the .hg/hgrc file is untrusted. That is
+    # fine, we don't want to load it anyway.
+    err = [e for e in err.splitlines()
+           if not e.startswith('Not trusting file')]
+    if err:
+        return ''
+    return out
+
+
+version = ''
+
+if os.path.isdir('.hg'):
+    # Execute hg out of this directory with a custom environment which
+    # includes the pure Python modules in mercurial/pure. We also take
+    # care to not use any hgrc files and do no localization.
+    env = {'HGRCPATH': '',
+           'LANGUAGE': 'C'}
+    for copyenv in ('LD_LIBRARY_PATH', 'PYTHONPATH', 'PATH'):
+        if copyenv in os.environ:
+            env[copyenv] = os.environ[copyenv]
+    if 'SystemRoot' in os.environ:
+        # Copy SystemRoot into the custom environment for Python 2.6
+        # under Windows. Otherwise, the subprocess will fail with
+        # error 0xc0150004. See: http://bugs.python.org/issue3440
+        env['SystemRoot'] = os.environ['SystemRoot']
+    cmd = ['hg', 'id', '-i', '-t']
+    l = runcmd(cmd, env).split()
+    while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
+        l.pop()
+    if len(l) > 1: # tag found
+        version = l[-1]
+        if l[0].endswith('+'): # propagate the dirty status to the tag
+            version += '+'
+    elif len(l) == 1: # no tag found
+        cmd = ['hg', 'parents', '--template',
+               '{latesttag}+{latesttagdistance}-']
+        version = runcmd(cmd, env) + l[0]
+    if not version:
+        version = runcmd(['hg', 'parents', '--template' '{node|short}\n'],
+                         env)
+        if version:
+            version = version.split()[0]
+    if version.endswith('+'):
+        version += time.strftime('%Y%m%d')
+elif os.path.exists('.hg_archival.txt'):
+    kw = dict([t.strip() for t in l.split(':', 1)]
+              for l in open('.hg_archival.txt'))
+    if 'tag' in kw:
+        version =  kw['tag']
+    elif 'latesttag' in kw:
+        version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
+    else:
+        version = kw.get('node', '')[:12]
+
+verfile = os.path.join("hgsubversion", "__version__.py")
+if version:
+    f = open(verfile, "w")
+    f.write('# this file is autogenerated by setup.py\n')
+    f.write('version = "%s"\n' % version)
+    f.close()
+
+if os.path.exists(verfile):
+    # scrape the version out with a regex because setuptools
+    # needlessly swaps out file() for some non-object thing
+    # and breaks importing hgsubversion entirely
+    mat = re.findall('.*"(.*)"', open(verfile).read())
+    version = mat[0]
+if not version:
+    version = 'unknown'
+
+requires = []
+try:
+    import mercurial
+except ImportError:
+    requires.append('mercurial')
+
+# If the Subversion SWIG bindings aren't present, require Subvertpy
+try:
+    from hgsubversion.svnwrap import svn_swig_wrapper
+except ImportError:
+    requires.append('subvertpy>=0.7.4')
+
+setup(
+    name = 'hgsubversion',
+    version = version,
+    url = 'http://bitbucket.org/durin42/hgsubversion',
+    license = 'GNU GPL',
+    author = 'Augie Fackler, others',
+    author_email = 'durin42@gmail.com',
+    description = ('hgsubversion is a Mercurial extension for working with '
+                   'Subversion repositories.'),
+    long_description = open(os.path.join(os.path.dirname(__file__),
+                                         'README')).read(),
+    keywords = 'mercurial',
+    packages = ('hgsubversion', 'hgsubversion.svnwrap'),
+    package_data = { 'hgsubversion': ['help/subversion.rst'] },
+    platforms = 'any',
+    install_requires=requires,
+    classifiers = [
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Intended Audience :: Developers',
+        'Topic :: Software Development :: Version Control',
+        'Development Status :: 4 - Beta',
+        'Programming Language :: Python',
+        'Operating System :: OS Independent',
+    ],
+    cmdclass = {'build_py': build_py},
+)
diff --git a/opt/hgsubversion/tests/comprehensive/test_stupid_pull.py b/opt/hgsubversion/tests/comprehensive/test_stupid_pull.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/comprehensive/test_stupid_pull.py
@@ -0,0 +1,58 @@
+import os
+import pickle
+import sys
+import unittest
+
+from mercurial import hg
+from mercurial import ui
+
+# wrapped in a try/except because of weirdness in how
+# run.py works as compared to nose.
+try:
+    import test_util
+except ImportError:
+    sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+    import test_util
+
+from hgsubversion import wrappers
+
+
+def _do_case(self, name, layout):
+    subdir = test_util.subdir.get(name, '')
+    self._load_fixture_and_fetch(name, subdir=subdir, stupid=False, layout=layout)
+    assert len(self.repo) > 0, 'Repo had no changes, maybe you need to add a subdir entry in test_util?'
+    wc2_path = self.wc_path + '_stupid'
+    u = ui.ui()
+    checkout_path = self.repo_path
+    if subdir:
+        checkout_path += '/' + subdir
+    u.setconfig('hgsubversion', 'stupid', '1')
+    u.setconfig('hgsubversion', 'layout', layout)
+    test_util.hgclone(u, test_util.fileurl(checkout_path), wc2_path, update=False)
+    if layout == 'single':
+        self.assertEqual(len(self.repo.heads()), 1)
+    self.repo2 = hg.repository(ui.ui(), wc2_path)
+    self.assertEqual(self.repo.heads(), self.repo2.heads())
+
+
+def buildmethod(case, name, layout):
+    m = lambda self: self._do_case(case, layout)
+    m.__name__ = name
+    m.__doc__ = 'Test stupid produces same as real on %s. (%s)' % (case, layout)
+    return m
+
+attrs = {'_do_case': _do_case,
+         }
+for case in (f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')):
+    name = 'test_' + case[:-len('.svndump')]
+    attrs[name] = buildmethod(case, name, 'auto')
+    name += '_single'
+    attrs[name] = buildmethod(case, name, 'single')
+
+StupidPullTests = type('StupidPullTests', (test_util.TestBase, ), attrs)
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(StupidPullTests),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/comprehensive/test_verify.py b/opt/hgsubversion/tests/comprehensive/test_verify.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/comprehensive/test_verify.py
@@ -0,0 +1,55 @@
+import os
+import pickle
+import sys
+import unittest
+
+# wrapped in a try/except because of weirdness in how
+# run.py works as compared to nose.
+try:
+    import test_util
+except ImportError:
+    sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
+    import test_util
+
+from mercurial import hg
+from mercurial import ui
+
+from hgsubversion import svncommands
+
+def _do_case(self, name, stupid, layout):
+    subdir = test_util.subdir.get(name, '')
+    repo = self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid, layout=layout)
+    assert len(self.repo) > 0
+    for i in repo:
+        ctx = repo[i]
+        self.assertEqual(svncommands.verify(repo.ui, repo, rev=ctx.node()), 0)
+
+def buildmethod(case, name, stupid, layout):
+    m = lambda self: self._do_case(case, stupid, layout)
+    m.__name__ = name
+    bits = case, stupid and 'stupid' or 'real', layout
+    m.__doc__ = 'Test verify on %s with %s replay. (%s)' % bits
+    return m
+
+attrs = {'_do_case': _do_case}
+fixtures = [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')]
+for case in fixtures:
+    # this fixture results in an empty repository, don't use it
+    if case == 'project_root_not_repo_root.svndump':
+        continue
+    bname = 'test_' + case[:-len('.svndump')]
+    attrs[bname] = buildmethod(case, bname, False, 'standard')
+    name = bname + '_stupid'
+    attrs[name] = buildmethod(case, name, True, 'standard')
+    name = bname + '_single'
+    attrs[name] = buildmethod(case, name, False, 'single')
+    # Disabled because the "stupid and real are the same" tests
+    # verify this plus even more.
+    # name = bname + '_single_stupid'
+    # attrs[name] = buildmethod(case, name, True, 'single')
+
+VerifyTests = type('VerifyTests', (test_util.TestBase,), attrs)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(VerifyTests)]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/fixtures/author-map-test.txt b/opt/hgsubversion/tests/fixtures/author-map-test.txt
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/author-map-test.txt
@@ -0,0 +1,397 @@
+alpha01 = Alpha <alpha@alpha.com>
+alpha02 = Alpha <alpha@alpha.com>
+alpha03 = Alpha <alpha@alpha.com>
+alpha04 = Alpha <alpha@alpha.com>
+alpha05 = Alpha <alpha@alpha.com>
+alpha06 = Alpha <alpha@alpha.com>
+alpha07 = Alpha <alpha@alpha.com>
+alpha08 = Alpha <alpha@alpha.com>
+alpha09 = Alpha <alpha@alpha.com>
+alpha10 = Alpha <alpha@alpha.com>
+alpha11 = Alpha <alpha@alpha.com>
+alpha12 = Alpha <alpha@alpha.com>
+alpha13 = Alpha <alpha@alpha.com>
+alpha14 = Alpha <alpha@alpha.com>
+alpha15 = Alpha <alpha@alpha.com>
+alpha16 = Alpha <alpha@alpha.com>
+alpha17 = Alpha <alpha@alpha.com>
+alpha18 = Alpha <alpha@alpha.com>
+alpha19 = Alpha <alpha@alpha.com>
+alpha20 = Alpha <alpha@alpha.com>
+alpha21 = Alpha <alpha@alpha.com>
+alpha22 = Alph5 <alpha@alpha.com>
+alpha23 = Alpha <alpha@alpha.com>
+alpha24 = Alpha <alpha@alpha.com>
+alpha25 = Alpha <alpha@alpha.com>
+alpha26 = Alpha <alpha@alpha.com>
+alpha27 = Alpha <alpha@alpha.com>
+alpha28 = Alpha <alpha@alpha.com>
+alpha29 = Alpha <alpha@alpha.com>
+alpha30 = Alpha <alpha@alpha.com>
+alpha31 = Alpha <alpha@alpha.com>
+alpha32 = Alpha <alpha@alpha.com>
+alpha33 = Alpha <alpha@alpha.com>
+alpha34 = Alpha <alpha@alpha.com>
+alpha35 = Alpha <alpha@alpha.com>
+alpha36 = Alpha <alpha@alpha.com>
+alpha37 = Alph6 <alpha@alpha.com>
+alpha38 = Alpha <alpha@alpha.com>
+alpha39 = Alpha <alpha@alpha.com>
+alpha40 = Alpha <alpha@alpha.com>
+alpha41 = Alpha <alpha@alpha.com>
+alpha42 = Alpha <alpha@alpha.com>
+alpha43 = Alpha <alpha@alpha.com>
+alpha44 = Alph8 <alpha@alpha.com>
+alpha45 = Alpha <alpha@alpha.com>
+alpha46 = Alpha <alpha@alpha.com>
+alpha47 = Alpha <alpha@alpha.com>
+alpha48 = Alpha <alpha@alpha.com>
+alpha49 = Alpha <alpha@alpha.com>
+alpha50 = Alpha <alpha@alpha.com>
+alpha51 = Alpha <alpha@alpha.com>
+alpha52 = Alpha <alpha@alpha.com>
+alpha53 = Alpha <alpha@alpha.com>
+alpha54 = Alpha <alpha@alpha.com>
+alpha55 = Alpha <alpha@alpha.com>
+alpha56 = Alpha <alpha@alpha.com>
+alpha57 = Alph6 <alpha@alpha.com>
+alpha58 = Alpha <alpha@alpha.com>
+alpha59 = Alpha <alpha@alpha.com>
+alpha60 = Alpha <alpha@alpha.com>
+alpha61 = Alpha <alpha@alpha.com>
+alpha62 = Alpha <alpha@alpha.com>
+alpha63 = Alpha <alpha@alpha.com>
+alpha64 = Alpha <alpha@alpha.com>
+alpha65 = Alpha <alpha@alpha.com>
+alpha66 = Alpha <alpha@alpha.com>
+alpha67 = Alph6 <alpha@alpha.com>
+alpha68 = Alpha <alpha@alpha.com>
+alpha69 = Alpha <alpha@alpha.com>
+alpha70 = Alpha <alpha@alpha.com>
+alpha71 = Alpha <alpha@alpha.com>
+alpha72 = Alpha <alpha@alpha.com>
+alpha73 = Alpha <alpha@alpha.com>
+alpha74 = Alpha <alpha@alpha.com>
+alpha75 = Alpha <alpha@alpha.com>
+alpha76 = Alpha <alpha@alpha.com>
+alpha77 = Alph6 <alpha@alpha.com>
+alpha78 = Alpha <alpha@alpha.com>
+alpha79 = Alpha <alpha@alpha.com>
+alpha80 = Alpha <alpha@alpha.com>
+alpha81 = Alpha <alpha@alpha.com>
+alpha82 = Alpha <alpha@alpha.com>
+alpha83 = Alpha <alpha@alpha.com>
+alpha84 = Alpha <alpha@alpha.com>
+alpha85 = Alpha <alpha@alpha.com>
+alpha86 = Alpha <alpha@alpha.com>
+alpha87 = Alpha <alpha@alpha.com>
+alpha88 = Alpha <alpha@alpha.com>
+alpha89 = Alpha <alpha@alpha.com>
+alpha90 = Alpha <alpha@alpha.com>
+alpha91 = Alpha <alpha@alpha.com>
+alpha92 = Alpha <alpha@alpha.com>
+alpha93 = Alpha <alpha@alpha.com>
+alpha94 = Alpha <alpha@alpha.com>
+alpha95 = Alpha <alpha@alpha.com>
+alpha96 = Alpha <alpha@alpha.com>
+alpha97 = Alpha <alpha@alpha.com>
+alpha98 = Alpha <alpha@alpha.com>
+alpha99 = Alpha <alpha@alpha.com>
+alpha101 = Alpha <alpha@alpha.com>
+alpha102 = Alpha <alpha@alpha.com>
+alpha103 = Alpha <alpha@alpha.com>
+alpha104 = Alpha <alpha@alpha.com>
+alpha105 = Alpha <alpha@alpha.com>
+alpha106 = Alpha <alpha@alpha.com>
+alpha107 = Alpha <alpha@alpha.com>
+alpha108 = Alpha <alpha@alpha.com>
+alpha109 = Alpha <alpha@alpha.com>
+alpha110 = Alpha <alpha@alpha.com>
+alpha111 = Alpha <alpha@alpha.com>
+alpha112 = Alpha <alpha@alpha.com>
+alpha113 = Alpha <alpha@alpha.com>
+alpha114 = Alpha <alpha@alpha.com>
+alpha115 = Alpha <alpha@alpha.com>
+alpha116 = Alpha <alpha@alpha.com>
+alpha117 = Alpha <alpha@alpha.com>
+alpha118 = Alpha <alpha@alpha.com>
+alpha119 = Alpha <alpha@alpha.com>
+alpha120 = Alpha <alpha@alpha.com>
+alpha121 = Alpha <alpha@alpha.com>
+alpha122 = Alph5 <alpha@alpha.com>
+alpha123 = Alpha <alpha@alpha.com>
+alpha124 = Alpha <alpha@alpha.com>
+alpha125 = Alpha <alpha@alpha.com>
+alpha126 = Alpha <alpha@alpha.com>
+alpha127 = Alpha <alpha@alpha.com>
+alpha128 = Alpha <alpha@alpha.com>
+alpha129 = Alpha <alpha@alpha.com>
+alpha130 = Alpha <alpha@alpha.com>
+alpha131 = Alpha <alpha@alpha.com>
+alpha132 = Alpha <alpha@alpha.com>
+alpha133 = Alpha <alpha@alpha.com>
+alpha134 = Alpha <alpha@alpha.com>
+alpha135 = Alpha <alpha@alpha.com>
+alpha136 = Alpha <alpha@alpha.com>
+alpha137 = Alph6 <alpha@alpha.com>
+alpha138 = Alpha <alpha@alpha.com>
+alpha139 = Alpha <alpha@alpha.com>
+alpha140 = Alpha <alpha@alpha.com>
+alpha141 = Alpha <alpha@alpha.com>
+alpha142 = Alpha <alpha@alpha.com>
+alpha143 = Alpha <alpha@alpha.com>
+alpha144 = Alph8 <alpha@alpha.com>
+alpha145 = Alpha <alpha@alpha.com>
+alpha146 = Alpha <alpha@alpha.com>
+alpha147 = Alpha <alpha@alpha.com>
+alpha148 = Alpha <alpha@alpha.com>
+alpha149 = Alpha <alpha@alpha.com>
+alpha150 = Alpha <alpha@alpha.com>
+alpha151 = Alpha <alpha@alpha.com>
+alpha152 = Alpha <alpha@alpha.com>
+alpha153 = Alpha <alpha@alpha.com>
+alpha154 = Alpha <alpha@alpha.com>
+alpha155 = Alpha <alpha@alpha.com>
+alpha156 = Alpha <alpha@alpha.com>
+alpha157 = Alph6 <alpha@alpha.com>
+alpha158 = Alpha <alpha@alpha.com>
+alpha159 = Alpha <alpha@alpha.com>
+alpha160 = Alpha <alpha@alpha.com>
+alpha161 = Alpha <alpha@alpha.com>
+alpha162 = Alpha <alpha@alpha.com>
+alpha163 = Alpha <alpha@alpha.com>
+alpha164 = Alpha <alpha@alpha.com>
+alpha165 = Alpha <alpha@alpha.com>
+alpha166 = Alpha <alpha@alpha.com>
+alpha167 = Alph6 <alpha@alpha.com>
+alpha168 = Alpha <alpha@alpha.com>
+alpha169 = Alpha <alpha@alpha.com>
+alpha170 = Alpha <alpha@alpha.com>
+alpha171 = Alpha <alpha@alpha.com>
+alpha172 = Alpha <alpha@alpha.com>
+alpha173 = Alpha <alpha@alpha.com>
+alpha174 = Alpha <alpha@alpha.com>
+alpha175 = Alpha <alpha@alpha.com>
+alpha176 = Alpha <alpha@alpha.com>
+alpha177 = Alph6 <alpha@alpha.com>
+alpha178 = Alpha <alpha@alpha.com>
+alpha179 = Alpha <alpha@alpha.com>
+alpha180 = Alpha <alpha@alpha.com>
+alpha181 = Alpha <alpha@alpha.com>
+alpha182 = Alpha <alpha@alpha.com>
+alpha183 = Alpha <alpha@alpha.com>
+alpha184 = Alpha <alpha@alpha.com>
+alpha185 = Alpha <alpha@alpha.com>
+alpha186 = Alpha <alpha@alpha.com>
+alpha187 = Alpha <alpha@alpha.com>
+alpha188 = Alpha <alpha@alpha.com>
+alpha189 = Alpha <alpha@alpha.com>
+alpha190 = Alpha <alpha@alpha.com>
+alpha191 = Alpha <alpha@alpha.com>
+alpha192 = Alpha <alpha@alpha.com>
+alpha193 = Alpha <alpha@alpha.com>
+alpha194 = Alpha <alpha@alpha.com>
+alpha195 = Alpha <alpha@alpha.com>
+alpha196 = Alpha <alpha@alpha.com>
+alpha197 = Alpha <alpha@alpha.com>
+alpha198 = Alpha <alpha@alpha.com>
+alpha199 = Alpha <alpha@alpha.com>
+alpha201 = Alpha <alpha@alpha.com>
+alpha202 = Alpha <alpha@alpha.com>
+alpha203 = Alpha <alpha@alpha.com>
+alpha204 = Alpha <alpha@alpha.com>
+alpha205 = Alpha <alpha@alpha.com>
+alpha206 = Alpha <alpha@alpha.com>
+alpha207 = Alpha <alpha@alpha.com>
+alpha208 = Alpha <alpha@alpha.com>
+alpha209 = Alpha <alpha@alpha.com>
+alpha210 = Alpha <alpha@alpha.com>
+alpha211 = Alpha <alpha@alpha.com>
+alpha212 = Alpha <alpha@alpha.com>
+alpha213 = Alpha <alpha@alpha.com>
+alpha214 = Alpha <alpha@alpha.com>
+alpha215 = Alpha <alpha@alpha.com>
+alpha216 = Alpha <alpha@alpha.com>
+alpha217 = Alpha <alpha@alpha.com>
+alpha218 = Alpha <alpha@alpha.com>
+alpha219 = Alpha <alpha@alpha.com>
+alpha220 = Alpha <alpha@alpha.com>
+alpha221 = Alpha <alpha@alpha.com>
+alpha222 = Alph5 <alpha@alpha.com>
+alpha223 = Alpha <alpha@alpha.com>
+alpha224 = Alpha <alpha@alpha.com>
+alpha225 = Alpha <alpha@alpha.com>
+alpha226 = Alpha <alpha@alpha.com>
+alpha227 = Alpha <alpha@alpha.com>
+alpha228 = Alpha <alpha@alpha.com>
+alpha229 = Alpha <alpha@alpha.com>
+alpha230 = Alpha <alpha@alpha.com>
+alpha231 = Alpha <alpha@alpha.com>
+alpha232 = Alpha <alpha@alpha.com>
+alpha233 = Alpha <alpha@alpha.com>
+alpha234 = Alpha <alpha@alpha.com>
+alpha235 = Alpha <alpha@alpha.com>
+alpha236 = Alpha <alpha@alpha.com>
+alpha237 = Alph6 <alpha@alpha.com>
+alpha238 = Alpha <alpha@alpha.com>
+alpha239 = Alpha <alpha@alpha.com>
+alpha240 = Alpha <alpha@alpha.com>
+alpha241 = Alpha <alpha@alpha.com>
+alpha242 = Alpha <alpha@alpha.com>
+alpha243 = Alpha <alpha@alpha.com>
+alpha244 = Alph8 <alpha@alpha.com>
+alpha245 = Alpha <alpha@alpha.com>
+alpha246 = Alpha <alpha@alpha.com>
+alpha247 = Alpha <alpha@alpha.com>
+alpha248 = Alpha <alpha@alpha.com>
+alpha249 = Alpha <alpha@alpha.com>
+alpha250 = Alpha <alpha@alpha.com>
+alpha251 = Alpha <alpha@alpha.com>
+alpha252 = Alpha <alpha@alpha.com>
+alpha253 = Alpha <alpha@alpha.com>
+alpha254 = Alpha <alpha@alpha.com>
+alpha255 = Alpha <alpha@alpha.com>
+alpha256 = Alpha <alpha@alpha.com>
+alpha257 = Alph6 <alpha@alpha.com>
+alpha258 = Alpha <alpha@alpha.com>
+alpha259 = Alpha <alpha@alpha.com>
+alpha260 = Alpha <alpha@alpha.com>
+alpha261 = Alpha <alpha@alpha.com>
+alpha262 = Alpha <alpha@alpha.com>
+alpha263 = Alpha <alpha@alpha.com>
+alpha264 = Alpha <alpha@alpha.com>
+alpha265 = Alpha <alpha@alpha.com>
+alpha266 = Alpha <alpha@alpha.com>
+alpha267 = Alph6 <alpha@alpha.com>
+alpha268 = Alpha <alpha@alpha.com>
+alpha269 = Alpha <alpha@alpha.com>
+alpha270 = Alpha <alpha@alpha.com>
+alpha271 = Alpha <alpha@alpha.com>
+alpha272 = Alpha <alpha@alpha.com>
+alpha273 = Alpha <alpha@alpha.com>
+alpha274 = Alpha <alpha@alpha.com>
+alpha275 = Alpha <alpha@alpha.com>
+alpha276 = Alpha <alpha@alpha.com>
+alpha277 = Alph6 <alpha@alpha.com>
+alpha278 = Alpha <alpha@alpha.com>
+alpha279 = Alpha <alpha@alpha.com>
+alpha280 = Alpha <alpha@alpha.com>
+alpha281 = Alpha <alpha@alpha.com>
+alpha282 = Alpha <alpha@alpha.com>
+alpha283 = Alpha <alpha@alpha.com>
+alpha284 = Alpha <alpha@alpha.com>
+alpha285 = Alpha <alpha@alpha.com>
+alpha286 = Alpha <alpha@alpha.com>
+alpha287 = Alpha <alpha@alpha.com>
+alpha288 = Alpha <alpha@alpha.com>
+alpha289 = Alpha <alpha@alpha.com>
+alpha290 = Alpha <alpha@alpha.com>
+alpha291 = Alpha <alpha@alpha.com>
+alpha292 = Alpha <alpha@alpha.com>
+alpha293 = Alpha <alpha@alpha.com>
+alpha294 = Alpha <alpha@alpha.com>
+alpha295 = Alpha <alpha@alpha.com>
+alpha296 = Alpha <alpha@alpha.com>
+alpha297 = Alpha <alpha@alpha.com>
+alpha298 = Alpha <alpha@alpha.com>
+alpha299 = Alpha <alpha@alpha.com>
+alpha301 = Alpha <alpha@alpha.com>
+alpha302 = Alpha <alpha@alpha.com>
+alpha303 = Alpha <alpha@alpha.com>
+alpha304 = Alpha <alpha@alpha.com>
+alpha305 = Alpha <alpha@alpha.com>
+alpha306 = Alpha <alpha@alpha.com>
+alpha307 = Alpha <alpha@alpha.com>
+alpha308 = Alpha <alpha@alpha.com>
+alpha309 = Alpha <alpha@alpha.com>
+alpha310 = Alpha <alpha@alpha.com>
+alpha311 = Alpha <alpha@alpha.com>
+alpha312 = Alpha <alpha@alpha.com>
+alpha313 = Alpha <alpha@alpha.com>
+alpha314 = Alpha <alpha@alpha.com>
+alpha315 = Alpha <alpha@alpha.com>
+alpha316 = Alpha <alpha@alpha.com>
+alpha317 = Alpha <alpha@alpha.com>
+alpha318 = Alpha <alpha@alpha.com>
+alpha319 = Alpha <alpha@alpha.com>
+alpha320 = Alpha <alpha@alpha.com>
+alpha321 = Alpha <alpha@alpha.com>
+alpha322 = Alph5 <alpha@alpha.com>
+alpha323 = Alpha <alpha@alpha.com>
+alpha324 = Alpha <alpha@alpha.com>
+alpha325 = Alpha <alpha@alpha.com>
+alpha326 = Alpha <alpha@alpha.com>
+alpha327 = Alpha <alpha@alpha.com>
+alpha328 = Alpha <alpha@alpha.com>
+alpha329 = Alpha <alpha@alpha.com>
+alpha330 = Alpha <alpha@alpha.com>
+alpha331 = Alpha <alpha@alpha.com>
+alpha332 = Alpha <alpha@alpha.com>
+alpha333 = Alpha <alpha@alpha.com>
+alpha334 = Alpha <alpha@alpha.com>
+alpha335 = Alpha <alpha@alpha.com>
+alpha336 = Alpha <alpha@alpha.com>
+alpha337 = Alph6 <alpha@alpha.com>
+alpha338 = Alpha <alpha@alpha.com>
+alpha339 = Alpha <alpha@alpha.com>
+alpha340 = Alpha <alpha@alpha.com>
+alpha341 = Alpha <alpha@alpha.com>
+alpha342 = Alpha <alpha@alpha.com>
+alpha343 = Alpha <alpha@alpha.com>
+alpha344 = Alph8 <alpha@alpha.com>
+alpha345 = Alpha <alpha@alpha.com>
+alpha346 = Alpha <alpha@alpha.com>
+alpha347 = Alpha <alpha@alpha.com>
+alpha348 = Alpha <alpha@alpha.com>
+alpha349 = Alpha <alpha@alpha.com>
+alpha350 = Alpha <alpha@alpha.com>
+alpha351 = Alpha <alpha@alpha.com>
+alpha352 = Alpha <alpha@alpha.com>
+alpha353 = Alpha <alpha@alpha.com>
+alpha354 = Alpha <alpha@alpha.com>
+alpha355 = Alpha <alpha@alpha.com>
+alpha356 = Alpha <alpha@alpha.com>
+alpha357 = Alph6 <alpha@alpha.com>
+alpha358 = Alpha <alpha@alpha.com>
+alpha359 = Alpha <alpha@alpha.com>
+alpha360 = Alpha <alpha@alpha.com>
+alpha361 = Alpha <alpha@alpha.com>
+alpha362 = Alpha <alpha@alpha.com>
+alpha363 = Alpha <alpha@alpha.com>
+alpha364 = Alpha <alpha@alpha.com>
+alpha365 = Alpha <alpha@alpha.com>
+alpha366 = Alpha <alpha@alpha.com>
+alpha367 = Alph6 <alpha@alpha.com>
+alpha368 = Alpha <alpha@alpha.com>
+alpha639 = Alpha <alpha@alpha.com>
+alpha370 = Alpha <alpha@alpha.com>
+alpha371 = Alpha <alpha@alpha.com>
+alpha372 = Alpha <alpha@alpha.com>
+alpha373 = Alpha <alpha@alpha.com>
+alpha374 = Alpha <alpha@alpha.com>
+alpha375 = Alpha <alpha@alpha.com>
+alpha376 = Alpha <alpha@alpha.com>
+alpha377 = Alph6 <alpha@alpha.com>
+alpha738 = Alpha <alpha@alpha.com>
+alpha379 = Alpha <alpha@alpha.com>
+alpha380 = Alpha <alpha@alpha.com>
+alpha381 = Alpha <alpha@alpha.com>
+alpha382 = Alpha <alpha@alpha.com>
+alpha383 = Alpha <alpha@alpha.com>
+alpha384 = Alpha <alpha@alpha.com>
+alpha385 = Alpha <alpha@alpha.com>
+alpha386 = Alpha <alpha@alpha.com>
+alpha387 = Alpha <alpha@alpha.com>
+alpha388 = Alpha <alpha@alpha.com>
+alpha389 = Alpha <alpha@alpha.com>
+alpha390 = Alpha <alpha@alpha.com>
+alpha91 = Alpha <alpha@alpha.com>
+alph3a92 = Alpha <alpha@alpha.com>
+alph3a93 = Alpha <alpha@alpha.com>
+alph3a94 = Alpha <alpha@alpha.com>
+alph3a95 = Alpha <alpha@alpha.com>
+alph3a96 = Alpha <alpha@alpha.com>
+alph3a97 = Alpha <alpha@alpha.com>
+alph3a98 = Alpha <alpha@alpha.com>
+alpha99 = Alpha <alpha@alpha.com>
+dsadsakdoa = dksadosakfa <fallo@eample.org>
diff --git a/opt/hgsubversion/tests/fixtures/basic_tag_tests.svndump b/opt/hgsubversion/tests/fixtures/basic_tag_tests.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/basic_tag_tests.svndump
@@ -0,0 +1,177 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+tagging r3
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:29.837825Z
+PROPS-END
+
+Node-path: tags/tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+tag from a tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:51.091356Z
+PROPS-END
+
+Node-path: tags/copied_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/tag_r3
+
+
diff --git a/opt/hgsubversion/tests/fixtures/binaryfiles.sh b/opt/hgsubversion/tests/fixtures/binaryfiles.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/binaryfiles.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Generate binaryfiles.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+# Add a regular binary file, and an unflagged one
+python -c "file('binary1', 'wb').write('a\0\0\nb\0b')"
+python -c "file('binary2', 'wb').write('b\0\0\nc\0d')"
+svn add binary1 binary2
+svn propset svn:mime-type application/octet-stream binary1
+svn propdel svn:mime-type binary2
+svn ci -m 'add binaries'
+# Update them
+python -c "file('binary1', 'wb').write('a\0\0\nc\0d')"
+python -c "file('binary2', 'wb').write('b\0\0\0\nd\0e')"
+svn ci -m 'change binaries'
+# Remove them
+svn rm binary1 binary2
+svn ci -m 'remove binaries'
+cd ../..
+
+svnadmin dump testrepo > ../binaryfiles.svndump
diff --git a/opt/hgsubversion/tests/fixtures/binaryfiles.svndump b/opt/hgsubversion/tests/fixtures/binaryfiles.svndump
new file mode 100644
index 0000000000000000000000000000000000000000..9317774582e19bb0031a149757920c9e853197ca
GIT binary patch
literal 1886
zc$}?NNp9OP6!n^?&;^)q9<*(`C<>%OrbTxgz8p2y0GT$Lp1!n#01hOlZtSHVDZLT8
zd%VWg8plo3<Ek4Pm+%&bb=|dP!4P_Q_;ytm!W$kmGgxxPu&|C}XEei=<-&rcwyF$5
zx3H~uFXDF7co@oppqruVap>9vZNe*Pm+7f2q(Wb&5c*yiwBEL5bP3R7!8Bro7)%*v
z_MYmJsFKMG%7qf*C%U=)adU^iUSG}d(s^D=#e{f=WoA7xuexO>rVQ0>or-?wenCk2
zl#=n-ZPHUW<Qp3G4g7IKJXW){jO%xrue%7icRLAb*tE~Dz;oTkvWWHY`shL$dvGxx
za73nt2-@eFhQm$`C#xxZ-MXQM^~vF=3ob~8KMdz`q<f+JAyxGXj!a2fbPvDN1lRB3
zCdzEtSjiknZv`c^@{(H{m?N4B2||ZSMrvFx<)hQ4ZUCp3ezYr)>-$w5+)mzQ7ZN1A
zP6N1R&#PN3kYD(Np4lfo=T?sP%r(<e6Cxz#PDDz%sXWX6d9*QlIo*Zc-<Z!9!XnO0
zjUSqto|&4SOiiNj<k}^iZ*BU52}X%M!`fb@9UroA+9++UARuJ4ArM|k!MqCqo-jK}
zB<JDYlKtE`K9XqWcHpS8Q5vs^4T=!1xba%)%DO5DVkQ#vKR=hhKyd*x?Tb^}AE4>B
bXKdf(AF<*}{zv<7QASun{_ww=m|Xu0)b>EF

diff --git a/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.sh b/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+mkdir al
+echo foo > al/foo
+svn add al
+svn ci -m 'add directory al to delete on the branch'
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn rm branches/dev_branch/al
+svn ci -m 'branch'
+cd branches/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+cd ../../../..
+svnadmin dump temp/repo > branch_create_with_dir_delete.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in branch_create_with_dir_delete.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.svndump b/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_create_with_dir_delete.svndump
@@ -0,0 +1,237 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 88f76347-3f0e-4a36-b1e1-8dec7ad11590
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-04-07T22:37:33.524401Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-07T22:37:34.076074Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-07T22:37:35.119223Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 140
+Content-length: 140
+
+K 7
+svn:log
+V 40
+add directory al to delete on the branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-07T22:37:36.075475Z
+PROPS-END
+
+Node-path: trunk/al
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/al/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 4
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-07T22:37:39.073788Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/dev_branch/al
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-07T22:37:40.093715Z
+PROPS-END
+
+Node-path: branches/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Content-length: 5
+
+narf
+
+
+Node-path: branches/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: branches/dev_branch/delta
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.sh b/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+echo foo > trunk/foo
+svn add trunk/foo
+svn ci -m 'add file'
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'branch'
+svn up
+svn rm branches
+svn ci -m 'delete branches dir'
+cd ..
+cd ..
+svnadmin dump temp/repo > branch_delete_parent_dir.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in branch_delete_parent_dir.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.svndump b/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_delete_parent_dir.svndump
@@ -0,0 +1,137 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 60132d6f-a460-4b38-8ae6-633264894f73
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-06-30T23:57:20.562207Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 101
+Content-length: 101
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 4
+maxb
+K 8
+svn:date
+V 27
+2009-06-30T23:57:21.078798Z
+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: 106
+Content-length: 106
+
+K 7
+svn:log
+V 8
+add file
+K 10
+svn:author
+V 4
+maxb
+K 8
+svn:date
+V 27
+2009-06-30T23:57:22.098826Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 3
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 4
+maxb
+K 8
+svn:date
+V 27
+2009-06-30T23:57:25.055724Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+delete branches dir
+K 10
+svn:author
+V 4
+maxb
+K 8
+svn:date
+V 27
+2009-06-30T23:57:27.072926Z
+PROPS-END
+
+Node-path: branches
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/branch_from_tag.svndump b/opt/hgsubversion/tests/fixtures/branch_from_tag.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_from_tag.svndump
@@ -0,0 +1,202 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+tagging r3
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:29.837825Z
+PROPS-END
+
+Node-path: tags/tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+tag from a tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:51.091356Z
+PROPS-END
+
+Node-path: tags/copied_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/tag_r3
+
+
+Revision-number: 6
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 25
+Make a branch from a tag.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T00:07:48.076984Z
+PROPS-END
+
+Node-path: branches/branch_from_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: tags/tag_r3
+
+
diff --git a/opt/hgsubversion/tests/fixtures/branch_prop_edit.sh b/opt/hgsubversion/tests/fixtures/branch_prop_edit.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_prop_edit.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk
+svn add *
+svn ci -m 'branches trunk'
+svn up
+
+cd trunk
+for a in alpha beta gamma ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Files.'
+cd ..
+svn up
+
+svn cp trunk branches/dev_branch
+svn ci -m 'make a branch'
+svn up
+
+cd branches/dev_branch
+echo epsilon > epsilon
+svn add epsilon
+svn ci -m 'Add a file on the branch.'
+svn up
+cd ../..
+
+cd branches/dev_branch
+svn ps 'svn:ignore' 'delta' .
+svn ci -m 'Commit bogus propchange.'
+svn up
+cd ../../..
+
+pwd
+svnadmin dump repo > ../branch_prop_edit.svndump
+cd ..
+echo 'Dump created in branch_prop_edit.svndump. You can probably delete temp.'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/branch_prop_edit.svndump b/opt/hgsubversion/tests/fixtures/branch_prop_edit.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_prop_edit.svndump
@@ -0,0 +1,191 @@
+SVN-fs-dump-format-version: 2
+
+UUID: ab14e60b-7e4b-473f-980a-26ace458a00f
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-12T00:18:13.309956Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branches trunk
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-12T00:18:14.083854Z
+PROPS-END
+
+Node-path: branches
+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: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+Files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-12T00:18:16.270290Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+make a branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-12T00:18:19.069446Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 25
+Add a file on the branch.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-12T00:18:21.088611Z
+PROPS-END
+
+Node-path: branches/dev_branch/epsilon
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: c40719840583e3f3e6744c02828d7cd9
+Content-length: 18
+
+PROPS-END
+epsilon
+
+
+Revision-number: 5
+Prop-content-length: 124
+Content-length: 124
+
+K 7
+svn:log
+V 24
+Commit bogus propchange.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-12T00:18:23.078263Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: change
+Prop-content-length: 37
+Content-length: 37
+
+K 10
+svn:ignore
+V 6
+delta
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.sh b/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'branch'
+cd branches/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+cd ../..
+svn up
+svn mv trunk branches/old_trunk
+svn ci -m 'move trunk to a branch'
+svn up
+svn mv branches/dev_branch trunk
+svn ci -m 'move dev to trunk'
+cd ..
+cd ..
+svnadmin dump temp/repo > branch_rename_to_trunk.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in branch_rename_to_trunk.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.svndump b/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branch_rename_to_trunk.svndump
@@ -0,0 +1,252 @@
+SVN-fs-dump-format-version: 2
+
+UUID: f6ca240b-44b4-4753-9ebe-569095e6ee32
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-10T21:37:23.126889Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:24.068831Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:25.115468Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:28.044281Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:29.085784Z
+PROPS-END
+
+Node-path: branches/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Content-length: 5
+
+narf
+
+
+Node-path: branches/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: branches/dev_branch/delta
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 22
+move trunk to a branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:32.056153Z
+PROPS-END
+
+Node-path: branches/old_trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: trunk
+
+
+Node-path: trunk
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 17
+move dev to trunk
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-12-10T21:37:35.046793Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-action: delete
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/dev_branch
+
+
diff --git a/opt/hgsubversion/tests/fixtures/branchmap.sh b/opt/hgsubversion/tests/fixtures/branchmap.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branchmap.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# inspired by Python r62868
+
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+export REPO=file://`pwd`/repo
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+
+echo a > trunk/a
+svn add trunk/a
+svn ci -m 'Add file.'
+svn up
+
+svn cp trunk branches/badname
+svn ci -m 'Branch to be renamed.'
+svn up
+
+svn cp trunk branches/feature
+svn ci -m 'Branch to be unnamed.'
+svn up
+
+cd ../..
+svnadmin dump temp/repo > branchmap.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in branchmap.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/branchmap.svndump b/opt/hgsubversion/tests/fixtures/branchmap.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/branchmap.svndump
@@ -0,0 +1,140 @@
+SVN-fs-dump-format-version: 2
+
+UUID: e45f170d-c19e-4c14-836b-556bb41c9429
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-02-23T17:55:37.753400Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 100
+Content-length: 100
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2010-02-23T17:55:38.066481Z
+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: 106
+Content-length: 106
+
+K 7
+svn:log
+V 9
+Add file.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2010-02-23T17:55:39.058424Z
+PROPS-END
+
+Node-path: 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
+
+
+Revision-number: 3
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 21
+Branch to be renamed.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2010-02-23T17:55:42.051658Z
+PROPS-END
+
+Node-path: branches/badname
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 21
+Branch to be unnamed.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2010-02-23T17:55:45.050455Z
+PROPS-END
+
+Node-path: branches/feature
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
diff --git a/opt/hgsubversion/tests/fixtures/commit-to-tag.sh b/opt/hgsubversion/tests/fixtures/commit-to-tag.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/commit-to-tag.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+REPOPATH="file://`pwd`/repo"
+svn co $REPOPATH wc
+cd wc
+mkdir -p branches/magic trunk tags
+svn add *
+svn ci -m 'btt'
+cd branches/magic
+for a in alpha beta gamma; do
+    echo $a > $a
+    svn add $a
+    svn ci -m "Add file $a"
+done
+cd ../..
+svn up
+svn cp $REPOPATH/branches/magic $REPOPATH/tags/will-edit -m 'Make tag to edit'
+svn up
+
+cd branches/magic
+for a in delta iota lambda; do
+    echo $a > $a
+    svn add $a
+    svn ci -m "Add file $a"
+done
+cd ../..
+
+cd tags/will-edit
+svn rm alpha
+svn ci -m 'removed alpha on a tag. Moves tag, implicit branch.'
+cd ../..
+
+cd branches/magic
+for a in omega; do
+    echo $a > $a
+    svn add $a
+    svn ci -m "Add file $a"
+done
+cd ../..
+svn up
+svn cp $REPOPATH/branches/magic $REPOPATH/tags/also-edit -m 'Make tag to edit'
+svn up
+
+echo not omega > branches/magic/omega
+echo not omega > tags/also-edit/omega
+svn ci -m 'edit both the tag and its source branch at the same time'
+
+echo more stupidity > tags/also-edit/omega
+svn ci -m 'Edit an edited tag.'
+
+svn cp $REPOPATH/tags/also-edit $REPOPATH/tags/did-edits -m 'Tag an edited tag'
+
+svn cp $REPOPATH/branches/magic $REPOPATH/branches/closeme -m 'Make extra branch for another bogus case'
+svn cp $REPOPATH/branches/closeme $REPOPATH/tags/edit-later -m 'Make tag to edit after branch closes'
+svn rm $REPOPATH/branches/closeme -m 'Close the branch'
+svn up
+echo boofar > tags/edit-later/delta
+svn ci -m 'Edit this tag after its parent closed'
+
+# try and revert will-edit to its original state
+svn up
+svn merge -r9:8 $REPOPATH .
+svn ci -m 'Revert revision 9.'
+
+# make a tag from a branch and edit it at the same time
+svn up
+svn cp branches/magic tags/edit-at-create
+echo alpha >> tags/edit-at-create/alpha
+svn ci -m 'make a tag from a branch and edit it at the same time'
+
+cd ../..
+svnadmin dump temp/repo > commit-to-tag.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in commit-to-tag.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/commit-to-tag.svndump b/opt/hgsubversion/tests/fixtures/commit-to-tag.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/commit-to-tag.svndump
@@ -0,0 +1,600 @@
+SVN-fs-dump-format-version: 2
+
+UUID: af82cc90-c2d2-43cd-b1aa-c8a78449440a
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-06-24T02:53:15.860217Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:17.530891Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/magic
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Add file alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:18.122469Z
+PROPS-END
+
+Node-path: branches/magic/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Revision-number: 3
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Add file beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:19.101676Z
+PROPS-END
+
+Node-path: branches/magic/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Add file gamma
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:20.081433Z
+PROPS-END
+
+Node-path: branches/magic/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 5
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Make tag to edit
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:22.103708Z
+PROPS-END
+
+Node-path: tags/will-edit
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: branches/magic
+
+
+Revision-number: 6
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Add file delta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:23.077083Z
+PROPS-END
+
+Node-path: branches/magic/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Revision-number: 7
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Add file iota
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:24.107494Z
+PROPS-END
+
+Node-path: branches/magic/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Revision-number: 8
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 15
+Add file lambda
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:25.092428Z
+PROPS-END
+
+Node-path: branches/magic/lambda
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: 8c8a4646591ee0d9a43d3149320ed577
+Content-length: 17
+
+PROPS-END
+lambda
+
+
+Revision-number: 9
+Prop-content-length: 151
+Content-length: 151
+
+K 7
+svn:log
+V 51
+removed alpha on a tag. Moves tag, implicit branch.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:26.443673Z
+PROPS-END
+
+Node-path: tags/will-edit/alpha
+Node-action: delete
+
+
+Revision-number: 10
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Add file omega
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:27.067556Z
+PROPS-END
+
+Node-path: branches/magic/omega
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 14723c69541ee556d75c581b787dc217
+Content-length: 16
+
+PROPS-END
+omega
+
+
+Revision-number: 11
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Make tag to edit
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:29.049217Z
+PROPS-END
+
+Node-path: tags/also-edit
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 10
+Node-copyfrom-path: branches/magic
+
+
+Revision-number: 12
+Prop-content-length: 156
+Content-length: 156
+
+K 7
+svn:log
+V 56
+edit both the tag and its source branch at the same time
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-24T02:53:30.063026Z
+PROPS-END
+
+Node-path: branches/magic/omega
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 9b26a47955b0778e131aae04743f2b8c
+Content-length: 10
+
+not omega
+
+
+Node-path: tags/also-edit/omega
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 9b26a47955b0778e131aae04743f2b8c
+Content-length: 10
+
+not omega
+
+
+Revision-number: 13
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 19
+Edit an edited tag.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-26T15:28:20.055574Z
+PROPS-END
+
+Node-path: tags/also-edit/omega
+Node-kind: file
+Node-action: change
+Text-content-length: 15
+Text-content-md5: a8d56f18cc28a34d6fe2cee5291ac1cc
+Content-length: 15
+
+more stupidity
+
+
+Revision-number: 14
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 17
+Tag an edited tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-26T15:57:06.047408Z
+PROPS-END
+
+Node-path: tags/did-edits
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 13
+Node-copyfrom-path: tags/also-edit
+
+
+Revision-number: 15
+Prop-content-length: 140
+Content-length: 140
+
+K 7
+svn:log
+V 40
+Make extra branch for another bogus case
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-26T19:26:28.086924Z
+PROPS-END
+
+Node-path: branches/closeme
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 14
+Node-copyfrom-path: branches/magic
+
+
+Revision-number: 16
+Prop-content-length: 136
+Content-length: 136
+
+K 7
+svn:log
+V 36
+Make tag to edit after branch closes
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-26T19:26:28.119751Z
+PROPS-END
+
+Node-path: tags/edit-later
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 15
+Node-copyfrom-path: branches/closeme
+
+
+Revision-number: 17
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Close the branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-29T00:12:57.367624Z
+PROPS-END
+
+Node-path: branches/closeme
+Node-action: delete
+
+
+Revision-number: 18
+Prop-content-length: 137
+Content-length: 137
+
+K 7
+svn:log
+V 37
+Edit this tag after its parent closed
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-26T19:26:29.059216Z
+PROPS-END
+
+Node-path: tags/edit-later/delta
+Node-kind: file
+Node-action: change
+Text-content-length: 7
+Text-content-md5: 5bbd00dab68c937673171d0b2e205c96
+Content-length: 7
+
+boofar
+
+
+Revision-number: 19
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 18
+Revert revision 9.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-06-29T00:13:01.537589Z
+PROPS-END
+
+Node-path: tags/will-edit/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 8
+Node-copyfrom-path: tags/will-edit/alpha
+Text-copy-source-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+
+Revision-number: 20
+Prop-content-length: 153
+Content-length: 153
+
+K 7
+svn:log
+V 53
+make a tag from a branch and edit it at the same time
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T13:31:13.153406Z
+PROPS-END
+
+Node-path: tags/edit-at-create
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 19
+Node-copyfrom-path: branches/magic
+
+
+Node-path: tags/edit-at-create/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 12
+Text-content-md5: 7b77a55a273801087b943dfbe257f4db
+Content-length: 12
+
+alpha
+alpha
diff --git a/opt/hgsubversion/tests/fixtures/copybeforeclose.sh b/opt/hgsubversion/tests/fixtures/copybeforeclose.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/copybeforeclose.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+cd trunk
+
+echo a > a
+svn add a
+svn ci -m 'Add file.'
+svn up
+
+cd ..
+svn cp trunk branches/test
+svn ci -m 'Branch.'
+svn up
+
+cd branches/test/
+svn mv a b
+svn ci -m 'Move on branch.'
+svn up
+
+cd ../../
+svn up
+svn rm branches/test
+svn ci -m 'Close branch.'
+
+cd ../..
+svnadmin dump temp/repo > copybeforeclose.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in copybeforeclose.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/copybeforeclose.svndump b/opt/hgsubversion/tests/fixtures/copybeforeclose.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/copybeforeclose.svndump
@@ -0,0 +1,166 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1e1f7d3f-4361-4205-84f8-c0d471d161d2
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-06-11T15:51:46.768965Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 100
+Content-length: 100
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-06-11T15:51:47.134555Z
+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: 106
+Content-length: 106
+
+K 7
+svn:log
+V 9
+Add file.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-06-11T15:51:48.129578Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 7
+Branch.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-06-11T15:51:51.120532Z
+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: 113
+Content-length: 113
+
+K 7
+svn:log
+V 15
+Move on branch.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-06-11T15:51:54.124503Z
+PROPS-END
+
+Node-path: branches/test/b
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/test/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+
+
+Node-path: branches/test/a
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 13
+Close branch.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-06-11T15:51:57.130547Z
+PROPS-END
+
+Node-path: branches/test
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/delentries.sh b/opt/hgsubversion/tests/fixtures/delentries.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/delentries.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Generate delentries.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+# Regular file deletion
+echo a > a
+# Another file starting like the deleted file
+echo aa > aa
+mkdir d1
+mkdir d1/d2
+mkdir d1/d2/d3
+echo c > d1/c
+# Test directory deletion
+echo d > d1/d2/c
+# Test subdirectory deletion
+echo e > d1/d2/d3/e
+echo f > d1/d2/d3/f
+# This file starts as the deleted directory, can be confusing
+echo d2prefix > d1/d2prefix
+svn add a aa d1
+svn ci -m "add entries"
+svn rm a d1/d2
+svn ci -m "remove entries"
+cd ../..
+
+svnadmin dump testrepo > ../delentries.svndump
diff --git a/opt/hgsubversion/tests/fixtures/delentries.svndump b/opt/hgsubversion/tests/fixtures/delentries.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/delentries.svndump
@@ -0,0 +1,196 @@
+SVN-fs-dump-format-version: 2
+
+UUID: c91f719a-92f6-401b-a65d-c6820897c57c
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-15T21:48:44.999817Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:48:45.636944Z
+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: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+add entries
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:48:46.249879Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/aa
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: d404401c8c6495b206fc35c95e55a6d5
+Content-length: 13
+
+PROPS-END
+aa
+
+
+Node-path: trunk/d1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d1/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: trunk/d1/d2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d1/d2/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Content-length: 12
+
+PROPS-END
+d
+
+
+Node-path: trunk/d1/d2/d3
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d1/d2/d3/e
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 9ffbf43126e33be52cd2bf7e01d627f9
+Content-length: 12
+
+PROPS-END
+e
+
+
+Node-path: trunk/d1/d2/d3/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 9a8ad92c50cae39aa2c5604fd0ab6d8c
+Content-length: 12
+
+PROPS-END
+f
+
+
+Node-path: trunk/d1/d2prefix
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 74409dc69996f46a4f59d3f7f39bf7c0
+Content-length: 19
+
+PROPS-END
+d2prefix
+
+
+Revision-number: 3
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+remove entries
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:48:47.188246Z
+PROPS-END
+
+Node-path: trunk/d1/d2
+Node-action: delete
+
+
+Node-path: trunk/a
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/empty-log-message.svndump b/opt/hgsubversion/tests/fixtures/empty-log-message.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/empty-log-message.svndump
@@ -0,0 +1,71 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 2abea918-74b5-4124-b744-a1f76c91de90
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-11-18T14:30:05.275138Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 100
+Content-length: 100
+
+K 7
+svn:log
+V 0
+
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-11-18T14:30:36.517160Z
+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
+
+
+Node-path: trunk/A
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.sh b/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+mkdir temp || exit 1
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+pushd wc
+mkdir -p project/trunk
+svn add project
+svn ci -m 'trunk'
+cd project/trunk
+echo a > a
+mkdir narf
+svn add a narf
+svn ci -m 'file and empty dir'
+popd
+svnadmin dump repo > ../empty_dir_in_trunk_not_repo_root.svndump
+echo 'dump in empty_dir_in_trunk_not_repo_root.svndump'
+echo 'you can probably delete temp now'
diff --git a/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.svndump b/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/empty_dir_in_trunk_not_repo_root.svndump
@@ -0,0 +1,89 @@
+SVN-fs-dump-format-version: 2
+
+UUID: c866b883-4c03-404b-8609-dade481701a6
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-04-15T03:39:30.544797Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 5
+trunk
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-15T03:39:31.069518Z
+PROPS-END
+
+Node-path: project
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 18
+file and empty dir
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-04-15T03:39:32.069497Z
+PROPS-END
+
+Node-path: project/trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: project/trunk/narf
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/emptyrepo.sh b/opt/hgsubversion/tests/fixtures/emptyrepo.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/emptyrepo.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Generate renames.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+# Create and remove a file, hgsubversion does not like
+# empty repositories
+echo a > a
+svn add a
+svn ci -m "add a"
+svn rm a
+svn ci -m "remove a"
+cd ../..
+
+svnadmin dump testrepo > ../emptyrepo.svndump
diff --git a/opt/hgsubversion/tests/fixtures/emptyrepo.svndump b/opt/hgsubversion/tests/fixtures/emptyrepo.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/emptyrepo.svndump
@@ -0,0 +1,102 @@
+SVN-fs-dump-format-version: 2
+
+UUID: d3a5b63a-6d97-4aaf-9707-be98248d5c9e
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-10T14:47:35.612188Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-10T14:47:35.655615Z
+PROPS-END
+
+Node-path: branches
+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: 106
+Content-length: 106
+
+K 7
+svn:log
+V 5
+add a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-10T14:47:36.172222Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+remove a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-10T14:47:37.171799Z
+PROPS-END
+
+Node-path: trunk/a
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/executable_file_empty_prop.svndump b/opt/hgsubversion/tests/fixtures/executable_file_empty_prop.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/executable_file_empty_prop.svndump
@@ -0,0 +1,93 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 60adb0cc-4d5c-4038-bbb4-90f4595cf81c
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-25T15:02:16.557895Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 15
+Basic structure
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-11-25T15:02:45.454954Z
+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: 103
+Content-length: 103
+
+K 7
+svn:log
+V 4
+blah
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-11-25T15:03:45.151223Z
+PROPS-END
+
+Node-path: trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 36
+Text-content-length: 4
+Text-content-md5: c157a79031e1c40f85931829bc5fc552
+Content-length: 40
+
+K 14
+svn:executable
+V 0
+
+PROPS-END
+bar
+
+
diff --git a/opt/hgsubversion/tests/fixtures/executebit.sh b/opt/hgsubversion/tests/fixtures/executebit.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/executebit.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Generate executebit.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+echo text > text1
+echo text > text2
+touch empty1
+touch empty2
+python -c "file('binary1', 'wb').write('a\x00b')"
+python -c "file('binary2', 'wb').write('a\x00b')"
+svn add text1 text2 binary1 binary2 empty1 empty2
+svn propset svn:mime-type application/octet-stream binary1 binary2
+svn propset svn:executable yes binary1 text1 empty1
+svn ci -m init
+# switch exec properties
+svn propdel svn:executable binary1 text1 empty1
+svn propset svn:executable yes binary2 text2 empty2
+svn ci -m changeexec
+cd ../..
+
+svnadmin dump testrepo > ../executebit.svndump
diff --git a/opt/hgsubversion/tests/fixtures/executebit.svndump b/opt/hgsubversion/tests/fixtures/executebit.svndump
new file mode 100644
index 0000000000000000000000000000000000000000..10c744428064d395ec8e3cf73263123b30af2a14
GIT binary patch
literal 3015
zc$~FXNspT_6z<u-f^#Kjykf(((?eA$jb=N{%{GsOPFO^oXr{lukX$ljh)|Xz&){!)
z=6im8nd5wevu)YnylzY5@ebNeQCDfi5PE+8b)QB`P%EtBSaTX<!L7r_8j3kEBft`3
zbdJyi?27(FTy0AWZ5k1@XzK>My7EwYyoPG&S7{_;^dl^x-;qX}U6p3W1A2*=LW~fN
zDZ@lOF_u!65;02>L6vw#i-$jpNBnbsKY&YzaVZr6a9`_?)gF0WFFRn$P*D{=YTEiQ
zINz;OvR^h^zpC5r4TYK#{+TwL#97WLWlZr;oac20xG_Bi-)^h7L*cEcvNXzy_V8$&
z-}k{}*@z+%3L@wjCp3qRZ6ycxl3&dcdv7lGB%TQ^q-G~QxwA!O+7Ei{O<t^FqB&Zk
zCwTWkoqsNund}ZY;gaMrVaht-it;?RDi(?pEjUSRV2#$jGu`3omBKr?tv7bv>!s*>
zzryz`Wl;k5AI;tfflSk^3uk)bceV52@y54c$|D!dH#_PCJJSi7+|Y@bq3i2~vhim(
zp2x#-$$;EU&`i55(K#WO5dn!NBuO;5TnnZkkjvv!jX(bLhDDv*LrQKa>T75O)P09>
z?RLlk=0Kgb#Br?>lWQ&%6LC(NlQJ<nwqZmGI;|bOfH7g$Zpl@IQHetOL74NgNyi2z
z9SbHML2hNLCG^H!oat)Dcp~}fIX5lS53EMREVw-+7e&yyd3%bb4m+yljYh_q`FV=|
dgp0*l@pcN6hu6Xj!gfMpUE6>OS#mS|{{hmkw1WTu

diff --git a/opt/hgsubversion/tests/fixtures/externals.sh b/opt/hgsubversion/tests/fixtures/externals.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/externals.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Generate externals.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+mkdir externals
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/externals
+mkdir project1
+echo a > project1/a
+svn add project1
+mkdir project2
+echo a > project2/b
+svn add project2
+svn ci -m "configure externals projects"
+cd ../trunk
+# Add an external reference
+echo a > a
+svn add a
+cat > externals <<EOF
+^/externals/project1 deps/project1
+EOF
+svn propset -F externals svn:externals .
+svn ci -m "set externals on ."
+# Add another one
+cat > externals <<EOF
+# A comment, then an empty line, then a blank line
+
+^/externals/project1 deps/project1
+    
+-r2 ^/externals/project2@2 deps/project2
+EOF
+svn propset -F externals svn:externals .
+svn ci -m "update externals on ."
+# Suppress an external and add one on a subdir
+cat > externals <<EOF
+-r2 ^/externals/project2@2 deps/project2
+EOF
+svn propset -F externals svn:externals .
+mkdir subdir
+mkdir subdir2
+svn add subdir subdir2
+cat > externals <<EOF
+^/externals/project1 deps/project1
+EOF
+svn propset -F externals svn:externals subdir subdir2
+svn ci -m "add on subdir"
+# Test branch with externals
+svn up
+cd ../branches
+svn copy ../trunk branch1
+svn propdel svn:externals branch1/subdir2
+svn ci -m 'externals in subtree'
+# Test branch with externals, removing on copy root
+svn copy ../trunk branch2
+svn propdel svn:externals branch2 branch2/subdir2
+svn ci -m 'externals in subtree, removed on root'
+cd ../trunk
+# Suppress the subdirectory
+svn rm --force subdir
+svn ci -m 'remove externals subdir'
+# Remove the property on subdir2
+svn propdel svn:externals subdir2
+svn ci -m 'remove externals subdir2'
+# Kill project2 externals, peg revision should preserve it
+cd ..
+svn up
+svn rm externals/project2
+svn ci -m 'remove externals project2'
+cd trunk
+echo a >> a
+svn ci -m 'change a'
+cd ../..
+
+svnadmin dump testrepo > ../externals.svndump
diff --git a/opt/hgsubversion/tests/fixtures/externals.svndump b/opt/hgsubversion/tests/fixtures/externals.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/externals.svndump
@@ -0,0 +1,435 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 2fcff1c7-6cef-40bf-9072-468ceec83032
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2011-02-25T14:33:01.299441Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:01.321997Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: externals
+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: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+configure externals projects
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:02.082464Z
+PROPS-END
+
+Node-path: externals/project1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: externals/project1/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: externals/project2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: externals/project2/b
+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
+
+
+Revision-number: 3
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+set externals on .
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:03.076026Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 70
+Content-length: 70
+
+K 13
+svn:externals
+V 35
+^/externals/project1 deps/project1
+
+PROPS-END
+
+
+Node-path: 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
+
+
+Revision-number: 4
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 21
+update externals on .
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:04.058124Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 169
+Content-length: 169
+
+K 13
+svn:externals
+V 133
+# A comment, then an empty line, then a blank line
+
+^/externals/project1 deps/project1
+    
+-r2 ^/externals/project2@2 deps/project2
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 13
+add on subdir
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:05.096545Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 76
+Content-length: 76
+
+K 13
+svn:externals
+V 41
+-r2 ^/externals/project2@2 deps/project2
+
+PROPS-END
+
+
+Node-path: trunk/subdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 70
+Content-length: 70
+
+K 13
+svn:externals
+V 35
+^/externals/project1 deps/project1
+
+PROPS-END
+
+
+Node-path: trunk/subdir2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 70
+Content-length: 70
+
+K 13
+svn:externals
+V 35
+^/externals/project1 deps/project1
+
+PROPS-END
+
+
+Revision-number: 6
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 20
+externals in subtree
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:08.059874Z
+PROPS-END
+
+Node-path: branches/branch1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/branch1/subdir2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 7
+Prop-content-length: 139
+Content-length: 139
+
+K 7
+svn:log
+V 37
+externals in subtree, removed on root
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:10.059295Z
+PROPS-END
+
+Node-path: branches/branch2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/branch2/subdir2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 23
+remove externals subdir
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:11.058317Z
+PROPS-END
+
+Node-path: trunk/subdir
+Node-action: delete
+
+
+Revision-number: 9
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 24
+remove externals subdir2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:12.058270Z
+PROPS-END
+
+Node-path: trunk/subdir2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 10
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 25
+remove externals project2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:14.058780Z
+PROPS-END
+
+Node-path: externals/project2
+Node-action: delete
+
+
+Revision-number: 11
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+change a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T14:33:15.045928Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
diff --git a/opt/hgsubversion/tests/fixtures/fetch_missing_files_subdir.svndump b/opt/hgsubversion/tests/fixtures/fetch_missing_files_subdir.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/fetch_missing_files_subdir.svndump
@@ -0,0 +1,184 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 90158077-d23c-442d-9351-5dd160359962
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-25T21:55:11.516061Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 12
+initial add.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-25T22:00:55.483808Z
+PROPS-END
+
+Node-path: foo
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: foo/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: foo/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: foo/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: foo/trunk/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 30
+commit bar in the wrong place.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-25T22:01:32.415795Z
+PROPS-END
+
+Node-path: foo/bar
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: foo/bar/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: foo/bar/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: foo/bar/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: foo/bar/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 26
+move bar to the sane place
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-25T22:02:24.410165Z
+PROPS-END
+
+Node-path: foo/trunk/bar
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: foo/bar
+
+
+Node-path: foo/bar
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/file_mixed_with_branches.svndump b/opt/hgsubversion/tests/fixtures/file_mixed_with_branches.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/file_mixed_with_branches.svndump
@@ -0,0 +1,119 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 29
+Adding a readme for branches.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-27T03:08:00.088667Z
+PROPS-END
+
+Node-path: branches/README
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 40
+Text-content-md5: 05e0cee6729d14caf6688d01b4bbfa0d
+Content-length: 50
+
+PROPS-END
+This is a readme on how to use branches
+
+
diff --git a/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.sh b/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+cd trunk
+mkdir narf
+cd narf
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+done
+cd ..
+svn add narf
+svn ci -m 'Add files.'
+cd ../../..
+svnadmin dump temp/repo > file_not_in_trunk_root.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in file_not_in_trunk_root.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.svndump b/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/file_not_in_trunk_root.svndump
@@ -0,0 +1,138 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 8904f924-d410-4c11-97ad-f853c35ee419
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-03-09T16:36:52.319733Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2009-03-09T16:36:53.075933Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2009-03-09T16:36:54.163467Z
+PROPS-END
+
+Node-path: trunk/narf
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/narf/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Text-content-sha1: d046cd9b7ffb7661e449683313d41f6fc33e3130
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/narf/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Text-content-sha1: 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/narf/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Text-content-sha1: 4bd6315d6d7824c4e376847ca7d116738ad2f29a
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/narf/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Text-content-sha1: 37f385b028bf2f93a4b497ca9ff44eea63945b7f
+Content-length: 16
+
+PROPS-END
+gamma
+
+
diff --git a/opt/hgsubversion/tests/fixtures/file_renamed_in_from_outside_btt.svndump b/opt/hgsubversion/tests/fixtures/file_renamed_in_from_outside_btt.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/file_renamed_in_from_outside_btt.svndump
@@ -0,0 +1,238 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 15
+Add third_party
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-27T03:16:13.831333Z
+PROPS-END
+
+Node-path: third_party
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: third_party/magic
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: third_party/magic/the_magic_software
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: bc82cb068ef102a1f4e296992e5979ef
+Content-length: 17
+
+PROPS-END
+magic!
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 31
+Copy magic in from third_party.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-27T03:16:32.737023Z
+PROPS-END
+
+Node-path: trunk/magic
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: third_party/magic
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 132
+Content-length: 132
+
+K 7
+svn:log
+V 32
+making a file I can rename over.
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-10-27T17:06:06.012783Z
+PROPS-END
+
+Node-path: third_party/LICENSE.file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 42b502668b588785b91cfea1a3fa834a
+Content-length: 22
+
+PROPS-END
+A joke file
+
+
+Revision-number: 6
+Prop-content-length: 149
+Content-length: 149
+
+K 7
+svn:log
+V 49
+Making a rename that might demonstrate a problem.
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-10-27T17:06:32.785437Z
+PROPS-END
+
+Node-path: third_party/LICENSE.file
+Node-action: delete
+
+
+Node-path: trunk/LICENSE.file
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: third_party/LICENSE.file
+Text-copy-source-md5: 42b502668b588785b91cfea1a3fa834a
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/filecase.sh b/opt/hgsubversion/tests/fixtures/filecase.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/filecase.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Generate filecase.svndump
+# WARNING: this script must be run on a case-sensitive file system
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+# Test files and directories differing in case only
+echo a > a
+echo A > A
+echo b > b
+mkdir d
+echo a > d/a
+mkdir D
+echo a > D/a
+mkdir e
+echo a > e/a
+mkdir f
+echo a > f/a
+echo F > F
+svn add a A b d D e f F
+svn ci -m 'add files'
+# Rename files and directories, changing only their case
+svn mv b B
+svn mv d/a d/A
+svn mv e E
+svn ci -m 'change case'
+cd ../..
+
+svnadmin dump testrepo > ../filecase.svndump
diff --git a/opt/hgsubversion/tests/fixtures/filecase.svndump b/opt/hgsubversion/tests/fixtures/filecase.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/filecase.svndump
@@ -0,0 +1,268 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 43703c40-3731-4eff-a4bd-16f771fbf860
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-07T13:00:57.919880Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-07T13:00:58.039069Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+add files
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-07T13:00:59.209541Z
+PROPS-END
+
+Node-path: trunk/A
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: bf072e9119077b4e76437a93986787ef
+Content-length: 12
+
+PROPS-END
+A
+
+
+Node-path: trunk/D
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/D/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/F
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: d2a33790e5bf28b33cdbf61722a06989
+Content-length: 12
+
+PROPS-END
+F
+
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/e
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/e/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/f
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/f/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+change case
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-07T13:01:03.159028Z
+PROPS-END
+
+Node-path: trunk/B
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/b
+Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk/E
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/e
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk/d/A
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/d/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk/d/a
+Node-action: delete
+
+
+Node-path: trunk/b
+Node-action: delete
+
+
+Node-path: trunk/e
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/ignores.svndump b/opt/hgsubversion/tests/fixtures/ignores.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/ignores.svndump
@@ -0,0 +1,132 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 8ad10f29-7a81-4fb6-a789-b0b0ade2ec54
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-02-24T20:04:33.977054Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Add a file.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-02-24T20:05:12.825468Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+ignore
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-02-24T20:05:49.334191Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 47
+Content-length: 47
+
+K 10
+svn:ignore
+V 15
+blah
+otherblah
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 124
+Content-length: 124
+
+K 7
+svn:log
+V 24
+another file and ignore.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-02-24T20:07:13.884019Z
+PROPS-END
+
+Node-path: trunk/baz
+Node-kind: dir
+Node-action: add
+Prop-content-length: 37
+Content-length: 37
+
+K 10
+svn:ignore
+V 6
+magic
+
+PROPS-END
+
+
+Node-path: trunk/baz/xyzzy
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 89d447eb9afaeb94e463615e8ded6479
+Text-content-sha1: 9b3802ecbad114267dd0d1a431e57b0bca95930a
+Content-length: 16
+
+PROPS-END
+xyzzy
+
+
diff --git a/opt/hgsubversion/tests/fixtures/many_special_cases.svndump b/opt/hgsubversion/tests/fixtures/many_special_cases.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/many_special_cases.svndump
@@ -0,0 +1,508 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Make a branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:35:39.984789Z
+PROPS-END
+
+Node-path: branches/the_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 23
+add delta on the branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:39:05.520779Z
+PROPS-END
+
+Node-path: branches/the_branch/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Revision-number: 6
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 19
+Add gamma on trunk.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:39:29.950892Z
+PROPS-END
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 7
+Prop-content-length: 192
+Content-length: 192
+
+K 7
+svn:log
+V 92
+Add a dir that is part of another file so I can delete it to test a case in the delete code.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T21:58:28.902105Z
+PROPS-END
+
+Node-path: trunk/gam
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/gam/bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: c157a79031e1c40f85931829bc5fc552
+Content-length: 14
+
+PROPS-END
+bar
+
+
+Node-path: trunk/gam/baz
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 258622b1688250cb619f3c9ccaefb7eb
+Content-length: 14
+
+PROPS-END
+baz
+
+
+Node-path: trunk/gam/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 8
+Prop-content-length: 150
+Content-length: 150
+
+K 7
+svn:log
+V 50
+delete gam to test that previously-mentioned case.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T21:58:43.789735Z
+PROPS-END
+
+Node-path: trunk/gam
+Node-action: delete
+
+
+Revision-number: 9
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Add a symlink
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:44:17.202691Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 10
+Text-content-md5: 9d4142e2eb58c20531a1ac599bb0f7fd
+Content-length: 43
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link alpha
+
+Revision-number: 10
+Prop-content-length: 149
+Content-length: 149
+
+K 7
+svn:log
+V 49
+redirect an existing symlink to a different file.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:45:46.927432Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: change
+Text-content-length: 9
+Text-content-md5: 404afec23f4019f7d784b2f78ff5503d
+Content-length: 9
+
+link beta
+
+Revision-number: 11
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 17
+remove a symlink.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:46:50.607212Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-action: delete
+
+
+Revision-number: 12
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 23
+add an executable file.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:47:47.863563Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 36
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 42
+
+K 14
+svn:executable
+V 1
+*
+PROPS-END
+delta
+
+
+Revision-number: 13
+Prop-content-length: 127
+Content-length: 127
+
+K 7
+svn:log
+V 27
+remove execute from a file.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:48:13.734895Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: change
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 14
+Prop-content-length: 124
+Content-length: 124
+
+K 7
+svn:log
+V 24
+add executable to a file
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T22:48:33.143919Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: change
+Prop-content-length: 36
+Content-length: 36
+
+K 14
+svn:executable
+V 1
+*
+PROPS-END
+
+
+Revision-number: 15
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 31
+Add epsilon as a copy of alpha.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:15:08.250550Z
+PROPS-END
+
+Node-path: trunk/epsilon
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Prop-content-length: 34
+Text-content-length: 14
+Text-content-md5: dc626a565ffebe5389e0109508e8ee31
+Content-length: 48
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+file: epsilon
+
+
diff --git a/opt/hgsubversion/tests/fixtures/mergeexternals.sh b/opt/hgsubversion/tests/fixtures/mergeexternals.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/mergeexternals.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Generate mergeexternals.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+mkdir d1
+echo a > d1/a
+mkdir d2
+echo b > d2/b
+mkdir -p common/ext
+echo c > common/ext/c
+svn add d1 d2 common
+svn ci -m addfiles
+svn up
+svn propset svn:externals '^/trunk/common/ext ext' d1
+svn propset svn:externals '^/trunk/common/ext ext' d2
+svn ci -m addexternals
+cd ..
+svn up
+svn cp trunk branches/branch
+cd branches
+svn ci -m addbranch
+cd branch
+mkdir d3
+echo d > d3/d
+svn add d3
+svn propset svn:externals '^/trunk/common/ext ext3' d3
+svn ci -m touchbranch
+cd ../../trunk
+svn merge '^/branches/branch'
+svn up
+svn ci -m 'merge'
+cd ../..
+
+svnadmin dump testrepo > ../mergeexternals.svndump
diff --git a/opt/hgsubversion/tests/fixtures/mergeexternals.svndump b/opt/hgsubversion/tests/fixtures/mergeexternals.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/mergeexternals.svndump
@@ -0,0 +1,306 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b402ceb9-6185-4dce-93a1-92de515c5c8b
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2011-02-25T13:54:38.654361Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:38.675100Z
+PROPS-END
+
+Node-path: branches
+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: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+addfiles
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:39.078800Z
+PROPS-END
+
+Node-path: trunk/common
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/common/ext
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/common/ext/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: trunk/d1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d1/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: trunk/d2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d2/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
+
+
+Revision-number: 3
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+addexternals
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:41.071346Z
+PROPS-END
+
+Node-path: trunk/d1
+Node-kind: dir
+Node-action: change
+Prop-content-length: 58
+Content-length: 58
+
+K 13
+svn:externals
+V 23
+^/trunk/common/ext ext
+
+PROPS-END
+
+
+Node-path: trunk/d2
+Node-kind: dir
+Node-action: change
+Prop-content-length: 58
+Content-length: 58
+
+K 13
+svn:externals
+V 23
+^/trunk/common/ext ext
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+addbranch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:44.043149Z
+PROPS-END
+
+Node-path: branches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+touchbranch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:45.080319Z
+PROPS-END
+
+Node-path: branches/branch/d3
+Node-kind: dir
+Node-action: add
+Prop-content-length: 59
+Content-length: 59
+
+K 13
+svn:externals
+V 24
+^/trunk/common/ext ext3
+
+PROPS-END
+
+
+Node-path: branches/branch/d3/d
+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
+
+
+Revision-number: 6
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 5
+merge
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2011-02-25T13:54:48.049151Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 55
+Content-length: 55
+
+K 13
+svn:mergeinfo
+V 20
+/branches/branch:4-5
+PROPS-END
+
+
+Node-path: trunk/d3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch/d3
+Prop-content-length: 59
+Content-length: 59
+
+K 13
+svn:externals
+V 24
+^/trunk/common/ext ext3
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.sh b/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+mkdir tags/versions
+mkdir tags/blah
+svn add *
+svn ci -m 'btt'
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'branch'
+cd branches/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+cd ../..
+svn up
+svn cp branches/dev_branch tags/some-tag
+svn ci -m 'Make a tag.'
+svn up
+echo foo > tags/some-tag/alpha
+svn ci -m 'edit that tag'
+cd ../..
+svnadmin dump temp/repo > most-recent-is-edit-tag.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in most-recent-is-edit-tag.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.svndump b/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/most-recent-is-edit-tag.svndump
@@ -0,0 +1,265 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 65efcde9-3b35-4f89-9c6b-23da1cf93d9b
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-07-19T03:04:55.657240Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:04:56.082834Z
+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: tags/blah
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags/versions
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:04:57.109656Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:05:00.048169Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:05:01.081946Z
+PROPS-END
+
+Node-path: branches/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Content-length: 5
+
+narf
+
+
+Node-path: branches/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: branches/dev_branch/delta
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Make a tag.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:05:04.056268Z
+PROPS-END
+
+Node-path: tags/some-tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: branches/dev_branch
+
+
+Revision-number: 6
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+edit that tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-07-19T03:05:06.057723Z
+PROPS-END
+
+Node-path: tags/some-tag/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 4
+
+foo
+
+
diff --git a/opt/hgsubversion/tests/fixtures/move_into_trunk.svndump b/opt/hgsubversion/tests/fixtures/move_into_trunk.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/move_into_trunk.svndump
@@ -0,0 +1,147 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 124c25c6-3b41-4c6b-a3a3-a0c9809dc9cb
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-08-14T19:08:00.090868Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 9
+anonymous
+K 8
+svn:date
+V 27
+2010-08-14T19:09:04.290181Z
+PROPS-END
+
+Node-path: A
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 16
+Text-content-md5: 98475036dc73d318982805bf4b16e8b2
+Text-content-sha1: d7dff2b1ef48b9c20c23d7b3a08b557957cec3c9
+Content-length: 26
+
+PROPS-END
+This is a file.
+
+
+Node-path: B
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 22
+Text-content-md5: d54ff73404ed6041a3bd66850b061bff
+Text-content-sha1: 4bdb40dfd6ec75cb730e678b5d7786e30170c5fb
+Content-length: 32
+
+PROPS-END
+This is another file.
+
+
+Revision-number: 2
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 10
+Add trunk.
+K 10
+svn:author
+V 9
+anonymous
+K 8
+svn:date
+V 27
+2010-08-14T19:09:23.652445Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 12
+Move a file.
+K 10
+svn:author
+V 9
+anonymous
+K 8
+svn:date
+V 27
+2010-08-14T19:09:33.945291Z
+PROPS-END
+
+Node-path: trunk/A
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: A
+Text-copy-source-md5: 98475036dc73d318982805bf4b16e8b2
+Text-copy-source-sha1: d7dff2b1ef48b9c20c23d7b3a08b557957cec3c9
+
+
+Node-path: A
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 18
+Move another file.
+K 10
+svn:author
+V 9
+anonymous
+K 8
+svn:date
+V 27
+2010-08-14T19:10:03.135714Z
+PROPS-END
+
+Node-path: trunk/B
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: B
+Text-copy-source-md5: d54ff73404ed6041a3bd66850b061bff
+Text-copy-source-sha1: 4bdb40dfd6ec75cb730e678b5d7786e30170c5fb
+
+
+Node-path: B
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/no-author.svndump b/opt/hgsubversion/tests/fixtures/no-author.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/no-author.svndump
@@ -0,0 +1,82 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 85
+Content-length: 85
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 82
+Content-length: 82
+
+K 7
+svn:log
+V 9
+Add alpha
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Text-content-sha1: f552a50b53177d35b29a4a0ab1cece918b5b5e9b
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
diff --git a/opt/hgsubversion/tests/fixtures/non_ascii_path_1.svndump b/opt/hgsubversion/tests/fixtures/non_ascii_path_1.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/non_ascii_path_1.svndump
@@ -0,0 +1,112 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a7b92ce5-70a8-4e75-99da-c3ca360ae490
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-08-16T13:08:38.494252Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 20
+Create project bøb.
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-08-16T13:10:12.395716Z
+PROPS-END
+
+Node-path: bøb
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/trunk/A
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 1
+Text-content-md5: 68b329da9893e34099c7d8ad5cb9c940
+Text-content-sha1: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
+Content-length: 11
+
+PROPS-END
+
+
+
+Revision-number: 2
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 11
+Add a file.
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-08-16T14:04:00.624391Z
+PROPS-END
+
+Node-path: bøb/trunk/B
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 1
+Text-content-md5: 68b329da9893e34099c7d8ad5cb9c940
+Text-content-sha1: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
+Content-length: 11
+
+PROPS-END
+
+
+
diff --git a/opt/hgsubversion/tests/fixtures/non_ascii_path_2.svndump b/opt/hgsubversion/tests/fixtures/non_ascii_path_2.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/non_ascii_path_2.svndump
@@ -0,0 +1,112 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a7b92ce5-70a8-4e75-99da-c3ca360ae490
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-08-16T13:08:38.494252Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 20
+Create project bøb.
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-08-16T13:10:12.395716Z
+PROPS-END
+
+Node-path: bøb
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: bøb/trunk/A
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 1
+Text-content-md5: 68b329da9893e34099c7d8ad5cb9c940
+Text-content-sha1: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
+Content-length: 11
+
+PROPS-END
+
+
+
+Revision-number: 2
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 11
+Add a file.
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-08-16T14:04:00.624391Z
+PROPS-END
+
+Node-path: bøb/trunk/B
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 1
+Text-content-md5: 68b329da9893e34099c7d8ad5cb9c940
+Text-content-sha1: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
+Content-length: 11
+
+PROPS-END
+
+
+
diff --git a/opt/hgsubversion/tests/fixtures/project_name_with_space.sh b/opt/hgsubversion/tests/fixtures/project_name_with_space.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/project_name_with_space.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir 'project name'
+cd 'project name'
+mkdir branches trunk tags
+cd ..
+svn add *
+svn ci -m 'btt'
+
+cd 'project name'/trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+
+mkdir al
+echo foo > al/foo
+svn add al
+svn ci -m 'add directory al'
+
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'branch'
+
+cd branches/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+
+cd ../../../../..
+svnadmin dump temp/repo > project_name_with_space.svndump
+
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in project_name_with_space.svndump'
+
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/project_name_with_space.svndump b/opt/hgsubversion/tests/fixtures/project_name_with_space.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/project_name_with_space.svndump
@@ -0,0 +1,249 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 5696e9c1-c844-4b75-be77-832074ca2506
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-12-24T18:41:45.385996Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-12-24T18:41:46.148218Z
+PROPS-END
+
+Node-path: project name
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project name/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project name/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project name/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-12-24T18:41:47.184399Z
+PROPS-END
+
+Node-path: project name/trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Text-content-sha1: d046cd9b7ffb7661e449683313d41f6fc33e3130
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: project name/trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Text-content-sha1: 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: project name/trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Text-content-sha1: 4bd6315d6d7824c4e376847ca7d116738ad2f29a
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: project name/trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Text-content-sha1: 37f385b028bf2f93a4b497ca9ff44eea63945b7f
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+add directory al
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-12-24T18:41:48.125685Z
+PROPS-END
+
+Node-path: project name/trunk/al
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project name/trunk/al/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Text-content-sha1: f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 4
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-12-24T18:41:51.073403Z
+PROPS-END
+
+Node-path: project name/branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: project name/trunk
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-12-24T18:41:52.164995Z
+PROPS-END
+
+Node-path: project name/branches/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Text-content-sha1: 89dc052326e95e1d1c5b08e2b8471d99112d1fa4
+Content-length: 5
+
+narf
+
+
+Node-path: project name/branches/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Text-content-sha1: 47e9aceee5149402971cda8590e9b912c1b1053e
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: project name/branches/dev_branch/delta
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/project_root_at_repo_root.svndump b/opt/hgsubversion/tests/fixtures/project_root_at_repo_root.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/project_root_at_repo_root.svndump
@@ -0,0 +1,352 @@
+SVN-fs-dump-format-version: 2
+
+UUID: e72213dc-9746-48d2-846a-a19c2c569b0d
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-09T14:46:06.470091Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:07.147162Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Initial Files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:08.147874Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
+Content-length: 25
+
+PROPS-END
+This is alpha.
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 14
+Text-content-md5: 952a13b33256f343982d0a8f0e0af277
+Content-length: 24
+
+PROPS-END
+This is beta.
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
+Content-length: 25
+
+PROPS-END
+This is delta.
+
+
+Revision-number: 3
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Tag rev 1.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:10.131807Z
+PROPS-END
+
+Node-path: tags/rev1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: tags/rev1/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: tags/rev1/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: tags/rev1/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Branch to crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:12.131174Z
+PROPS-END
+
+Node-path: branches/crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: branches/crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: branches/crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: branches/crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 5
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add gamma
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:13.143196Z
+PROPS-END
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 255b705986e84fbb13db9c5a832739ae
+Content-length: 25
+
+PROPS-END
+This is gamma.
+
+
+Revision-number: 6
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add omega
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:14.144244Z
+PROPS-END
+
+Node-path: branches/crazy/omega
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
+Content-length: 25
+
+PROPS-END
+This is omega.
+
+
+Revision-number: 7
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 21
+Branch to more_crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T14:46:16.135900Z
+PROPS-END
+
+Node-path: branches/more_crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: branches/more_crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: branches/more_crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: branches/more_crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Node-path: branches/more_crazy/gamma
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk/gamma
+Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
+
+
diff --git a/opt/hgsubversion/tests/fixtures/project_root_not_repo_root.svndump b/opt/hgsubversion/tests/fixtures/project_root_not_repo_root.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/project_root_not_repo_root.svndump
@@ -0,0 +1,361 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1916bd89-caf8-4f3d-96a7-7d5a7968f6ea
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-09T15:32:50.842663Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:51.151627Z
+PROPS-END
+
+Node-path: dummyproj
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/tags
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: dummyproj/trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+Initial Files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:52.149125Z
+PROPS-END
+
+Node-path: dummyproj/trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 8717538ba2f18a613eaa4892e8d178f7
+Content-length: 25
+
+PROPS-END
+This is alpha.
+
+
+Node-path: dummyproj/trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 14
+Text-content-md5: 952a13b33256f343982d0a8f0e0af277
+Content-length: 24
+
+PROPS-END
+This is beta.
+
+
+Node-path: dummyproj/trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: e48d0bca73f04e800e7bd9fb58d49a62
+Content-length: 25
+
+PROPS-END
+This is delta.
+
+
+Revision-number: 3
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Tag rev 1.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:54.131909Z
+PROPS-END
+
+Node-path: dummyproj/tags/rev1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/tags/rev1/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/tags/rev1/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/tags/rev1/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 16
+Branch to crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:56.132149Z
+PROPS-END
+
+Node-path: dummyproj/branches/crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/branches/crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/branches/crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/branches/crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Revision-number: 5
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add gamma
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:57.142842Z
+PROPS-END
+
+Node-path: dummyproj/trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 255b705986e84fbb13db9c5a832739ae
+Content-length: 25
+
+PROPS-END
+This is gamma.
+
+
+Revision-number: 6
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add omega
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:32:58.146324Z
+PROPS-END
+
+Node-path: dummyproj/branches/crazy/omega
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 15
+Text-content-md5: 22219e00c8dbb47ce790f0eb658e8014
+Content-length: 25
+
+PROPS-END
+This is omega.
+
+
+Revision-number: 7
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 21
+Branch to more_crazy.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-09T15:33:00.138992Z
+PROPS-END
+
+Node-path: dummyproj/branches/more_crazy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: dummyproj/trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: dummyproj/branches/more_crazy/alpha
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/alpha
+Text-copy-source-md5: 8717538ba2f18a613eaa4892e8d178f7
+
+
+Node-path: dummyproj/branches/more_crazy/beta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/beta
+Text-copy-source-md5: 952a13b33256f343982d0a8f0e0af277
+
+
+Node-path: dummyproj/branches/more_crazy/delta
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: dummyproj/trunk/delta
+Text-copy-source-md5: e48d0bca73f04e800e7bd9fb58d49a62
+
+
+Node-path: dummyproj/branches/more_crazy/gamma
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: dummyproj/trunk/gamma
+Text-copy-source-md5: 255b705986e84fbb13db9c5a832739ae
+
+
diff --git a/opt/hgsubversion/tests/fixtures/propset-branch.svndump b/opt/hgsubversion/tests/fixtures/propset-branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/propset-branch.svndump
@@ -0,0 +1,145 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 6506bfb9-9ce8-4b91-8357-4dc8bef2ecc6
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-07-10T11:55:08.483993Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 21
+create initial layout
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-07-10T11:55:57.419315Z
+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: 111
+Content-length: 111
+
+K 7
+svn:log
+V 10
+add a file
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-07-10T11:56:54.708286Z
+PROPS-END
+
+Node-path: trunk/a.txt
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 15
+create a branch
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-07-10T11:57:29.710978Z
+PROPS-END
+
+Node-path: branches/the-branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 14
+set a property
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-07-10T11:58:41.440627Z
+PROPS-END
+
+Node-path: branches/the-branch
+Node-kind: dir
+Node-action: change
+Prop-content-length: 42
+Content-length: 42
+
+K 12
+the-property
+V 9
+something
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/pushexternals.sh b/opt/hgsubversion/tests/fixtures/pushexternals.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/pushexternals.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Generate pushexternals.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir externals
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/externals
+mkdir project1
+echo a > project1/a
+svn add project1
+mkdir project2
+echo a > project2/b
+svn add project2
+svn ci -m "configure externals projects"
+cd ../trunk
+echo a > a
+# dir is used to set svn:externals on an already existing directory
+mkdir dir
+svn add a dir
+svn ci -m "add a and dir"
+svn rm a
+svn ci -m "remove a"
+cd ../..
+
+svnadmin dump testrepo > ../pushexternals.svndump
diff --git a/opt/hgsubversion/tests/fixtures/pushexternals.svndump b/opt/hgsubversion/tests/fixtures/pushexternals.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/pushexternals.svndump
@@ -0,0 +1,171 @@
+SVN-fs-dump-format-version: 2
+
+UUID: ce6cbbbe-6533-4ba7-91e1-cc165717826f
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-27T19:48:52.687312Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-27T19:48:52.751303Z
+PROPS-END
+
+Node-path: externals
+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: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+configure externals projects
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-27T19:48:53.230452Z
+PROPS-END
+
+Node-path: externals/project1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: externals/project1/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: externals/project2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: externals/project2/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 13
+add a and dir
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-27T19:48:54.187575Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+remove a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-27T19:48:55.175595Z
+PROPS-END
+
+Node-path: trunk/a
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/pushrenames.sh b/opt/hgsubversion/tests/fixtures/pushrenames.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/pushrenames.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Generate pushrenames.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+echo a > a
+echo b > b
+echo c > c
+echo d > d
+echo e > e
+svn add a b c d e
+svn ci -m "add files"
+cd ../..
+
+svnadmin dump testrepo > ../pushrenames.svndump
diff --git a/opt/hgsubversion/tests/fixtures/pushrenames.svndump b/opt/hgsubversion/tests/fixtures/pushrenames.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/pushrenames.svndump
@@ -0,0 +1,128 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 7b554dd8-6cf3-486c-abe4-86e652fb70b5
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-02T21:17:17.157936Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-02T21:17:17.217532Z
+PROPS-END
+
+Node-path: branches
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+add files
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-02T21:17:18.184945Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: trunk/d
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Content-length: 12
+
+PROPS-END
+d
+
+
+Node-path: trunk/e
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 9ffbf43126e33be52cd2bf7e01d627f9
+Content-length: 12
+
+PROPS-END
+e
+
+
diff --git a/opt/hgsubversion/tests/fixtures/remove_tag_test.svndump b/opt/hgsubversion/tests/fixtures/remove_tag_test.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/remove_tag_test.svndump
@@ -0,0 +1,228 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+tagging r3
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:29.837825Z
+PROPS-END
+
+Node-path: tags/tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+tag from a tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:51.091356Z
+PROPS-END
+
+Node-path: tags/copied_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/tag_r3
+
+
+Revision-number: 6
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 12
+rename a tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:20:23.431085Z
+PROPS-END
+
+Node-path: tags/other_tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: tags/copied_tag
+
+
+Node-path: tags/copied_tag
+Node-action: delete
+
+
+Revision-number: 7
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 20
+remove a tag as well
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:21:43.223062Z
+PROPS-END
+
+Node-path: tags/other_tag_r3
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.sh b/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+
+cd wc
+mkdir brances trunk tags
+svn add *
+svn ci -m 'btt'
+
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+
+cd ..
+svn up
+svn cp trunk brances/dev_branch
+svn ci -m 'branch'
+
+cd brances/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+
+cd ../..
+svn up
+svn mv brances branches
+svn ci -m 'move branches to branches'
+
+cd ..
+cd ..
+
+svnadmin dump temp/repo > rename_branch_parent_dir.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in rename_branch_parent_dir.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.svndump b/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/rename_branch_parent_dir.svndump
@@ -0,0 +1,223 @@
+SVN-fs-dump-format-version: 2
+
+UUID: dceec716-b592-46cb-b5fe-41acbbe32b85
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-02-06T17:32:02.297006Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T17:32:03.176895Z
+PROPS-END
+
+Node-path: brances
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T17:32:04.202558Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T17:32:07.137254Z
+PROPS-END
+
+Node-path: brances/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T17:32:08.190998Z
+PROPS-END
+
+Node-path: brances/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Content-length: 5
+
+narf
+
+
+Node-path: brances/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: brances/dev_branch/delta
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 125
+Content-length: 125
+
+K 7
+svn:log
+V 25
+move branches to branches
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2010-02-06T17:32:11.150006Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: brances
+
+
+Node-path: brances
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/rename_tag_test.sh b/opt/hgsubversion/tests/fixtures/rename_tag_test.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/rename_tag_test.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+export REPO=file://`pwd`/repo
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'Empty dirs.'
+
+echo 'file: alpha' > trunk/alpha
+svn add trunk/alpha
+svn ci -m 'Add alpha'
+svn up
+
+echo 'Data of beta' > trunk/beta
+svn add trunk/beta
+svn ci -m 'Add beta'
+svn up
+cd ..
+
+svn cp -m 'tagging r3' $REPO/trunk@3 $REPO/tags/tag_r3
+svn cp -m 'tag from a tag' $REPO/tags/tag_r3 $REPO/tags/copied_tag
+svn mv -m 'rename a tag' $REPO/tags/copied_tag $REPO/tags/other_tag_r3
+cd ..
+svnadmin dump temp/repo > rename_tag_test.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in renametagdir.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/rename_tag_test.svndump b/opt/hgsubversion/tests/fixtures/rename_tag_test.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/rename_tag_test.svndump
@@ -0,0 +1,200 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b94d0721-81cf-435e-ab14-c40b19ce1924
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-17T19:05:32.787484Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:33.116308Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:34.099166Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Text-content-sha1: f552a50b53177d35b29a4a0ab1cece918b5b5e9b
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:36.110949Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Text-content-sha1: 5d40e0a9ceda69f3d98d4851a6bee02c10a6e277
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 10
+tagging r3
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:38.066217Z
+PROPS-END
+
+Node-path: tags/tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+tag from a tag
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:38.116376Z
+PROPS-END
+
+Node-path: tags/copied_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/tag_r3
+
+
+Revision-number: 6
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+rename a tag
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-17T19:05:38.179177Z
+PROPS-END
+
+Node-path: tags/other_tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: tags/copied_tag
+
+
+Node-path: tags/copied_tag
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/renamedproject.sh b/opt/hgsubversion/tests/fixtures/renamedproject.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renamedproject.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Convert a project moving from a non-canonical to canonical
+# layout, exercizing the missing plaintext code paths. It also tests
+# branch creations where the branch source is not a canonical branch.
+#
+
+mkdir temp
+cd temp
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+
+mkdir project-orig
+cd project-orig
+echo a > a
+echo b > b
+echo c > c
+mkdir d
+echo a > d/a
+cd ..
+
+# Let's suppose it was actually branched in a previous life
+mkdir project-branch
+cd project-branch
+echo a > a
+echo b > b
+cd ..
+
+svn import project-orig $svnurl/project-orig -m "init project"
+svn import project-branch $svnurl/project-branch -m "init branch"
+
+svn mkdir $svnurl/project -m "create new project hierarchy"
+svn mv $svnurl/project-orig $svnurl/project/project -m "rename as project"
+svn mv $svnurl/project/project $svnurl/project/trunk -m "rename as project"
+
+svn mkdir $svnurl/project/branches -m "add branches root"
+svn mv $svnurl/project-branch $svnurl/project/misplaced -m "incorrect move of the branch"
+svn mv $svnurl/project/misplaced $svnurl/project/branches/branch -m "move of the branch"
+
+svn co $svnurl/project
+cd project
+echo a >> trunk/a
+svn ci -m "change a"
+echo a >> trunk/a
+echo b >> trunk/b
+svn rm trunk/c
+echo a >> trunk/d/a
+svn ci -m "change files in trunk"
+# Try the same thing with the branch
+echo a >> branches/branch/a
+svn rm branches/branch/b
+svn ci -m "change a in branch"
+cd ..
+
+# Add this to make test_rebuildmeta happy, needs something to convert
+svn import project-orig $svnurl/trunk -m "init fake trunk for rebuild_meta"
+
+svnadmin dump testrepo > ../renamedproject.svndump
diff --git a/opt/hgsubversion/tests/fixtures/renamedproject.svndump b/opt/hgsubversion/tests/fixtures/renamedproject.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renamedproject.svndump
@@ -0,0 +1,536 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 169a5fe1-3c9f-4eef-ad86-f932c54e53dc
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-05-01T17:53:40.957980Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.129010Z
+PROPS-END
+
+Node-path: project-orig
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-orig/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: project-orig/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: project-orig/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: project-orig/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-orig/d/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
+
+
+Revision-number: 2
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+init branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.193926Z
+PROPS-END
+
+Node-path: project-branch
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project-branch/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: project-branch/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
+
+
+Revision-number: 3
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+create new project hierarchy
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.236131Z
+PROPS-END
+
+Node-path: project
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 4
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+rename as project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.288164Z
+PROPS-END
+
+Node-path: project/project
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: project-orig
+
+
+Node-path: project-orig
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+rename as project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.335332Z
+PROPS-END
+
+Node-path: project/trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: project/project
+
+
+Node-path: project/project
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 17
+add branches root
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.377684Z
+PROPS-END
+
+Node-path: project/branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 7
+Prop-content-length: 130
+Content-length: 130
+
+K 7
+svn:log
+V 28
+incorrect move of the branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.426928Z
+PROPS-END
+
+Node-path: project/misplaced
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 6
+Node-copyfrom-path: project-branch
+
+
+Node-path: project-branch
+Node-action: delete
+
+
+Revision-number: 8
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+move of the branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:41.478644Z
+PROPS-END
+
+Node-path: project/branches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 7
+Node-copyfrom-path: project/misplaced
+
+
+Node-path: project/misplaced
+Node-action: delete
+
+
+Revision-number: 9
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+change a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:42.078428Z
+PROPS-END
+
+Node-path: project/trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Text-content-sha1: d7c8127a20a396cff08af086a1c695b0636f0c29
+Content-length: 4
+
+a
+a
+
+
+Revision-number: 10
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 21
+change files in trunk
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:43.109915Z
+PROPS-END
+
+Node-path: project/trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 6
+Text-content-md5: 7d4ebf8f298d22fc349a91725b00af1c
+Text-content-sha1: 92f31bc48f52339253fce6cad9f2f0c95b302f7e
+Content-length: 6
+
+a
+a
+a
+
+
+Node-path: project/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: project/trunk/d/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: project/trunk/c
+Node-action: delete
+
+
+Revision-number: 11
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+change a in branch
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:44.100553Z
+PROPS-END
+
+Node-path: project/branches/branch/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: project/branches/branch/b
+Node-action: delete
+
+
+Revision-number: 12
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 32
+init fake trunk for rebuild_meta
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2009-05-01T17:53:45.089483Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: 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: 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: trunk/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: trunk/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d/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
+
+
diff --git a/opt/hgsubversion/tests/fixtures/renames.sh b/opt/hgsubversion/tests/fixtures/renames.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renames.sh
@@ -0,0 +1,107 @@
+#!/bin/sh
+#
+# Generate renames.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+# Entries for regular tests
+echo a > a
+echo b > b
+mkdir -p da/db
+echo c > da/daf
+echo d > da/db/dbf
+# Entries to test delete + copy
+echo deleted > deletedfile
+mkdir deleteddir
+echo deleteddir > deleteddir/f
+# Entries to test copy before change
+echo changed > changed
+mkdir changeddir
+echo changed2 > changeddir/f
+# Entries unchanged in the rest of history
+echo unchanged > unchanged
+mkdir unchangeddir
+echo unchanged2 > unchangeddir/f
+# 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"
+# Remove files to be copied later
+svn rm deletedfile
+svn rm deleteddir
+# Update files to be copied before this change
+echo changed >> changed
+echo changed2 >> changeddir/f
+# Update one of the groupdir files
+echo a >> groupdir/a
+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"
+cd ../../trunk
+# Regular copy and rename
+svn cp a a1
+svn mv a a2
+# Copy and update of source and dest
+svn cp b b1
+echo b >> b
+echo c >> b1
+# 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
+echo c >> c1
+cd ../..
+svn ci -m "rename and copy a, b and da"
+cd trunk
+# Copy across branch
+svn cp ../branches/branch1/c c
+svn ci -m "copy b from branch1"
+# Copy deleted stuff from the past
+svn cp $svnurl/trunk/deletedfile@2 deletedfile
+svn cp $svnurl/trunk/deleteddir@2 deleteddir
+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
+# Harder, copy from the past before change and change it again
+# This confused the stupid diff path
+svn cp $svnurl/trunk/changed@2 changed3
+echo changed3 >> changed3
+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 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
+svn cp $svnurl/trunk/groupdir@2 groupdir2
+svn ci -m "copy groupdir from the past"
+cd ../..
+
+svnadmin dump testrepo > ../renames.svndump
diff --git a/opt/hgsubversion/tests/fixtures/renames.svndump b/opt/hgsubversion/tests/fixtures/renames.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renames.svndump
@@ -0,0 +1,787 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 113560bd-ec36-42a6-acef-e4688a33b129
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-05T22:48:38.139917Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:38.525864Z
+PROPS-END
+
+Node-path: branches
+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: 113
+Content-length: 113
+
+K 7
+svn:log
+V 11
+add a and b
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:39.313010Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/changed
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: ec1bebaea2c042beb68f7679ddd106a4
+Content-length: 18
+
+PROPS-END
+changed
+
+
+Node-path: trunk/changeddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/changeddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 9
+Text-content-md5: 2dfdfd8492a2c558ec838d69f73f5f6b
+Content-length: 19
+
+PROPS-END
+changed2
+
+
+Node-path: trunk/da
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/da/daf
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: trunk/da/db
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/da/db/dbf
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Content-length: 12
+
+PROPS-END
+d
+
+
+Node-path: trunk/deleteddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/deleteddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 11
+Text-content-md5: 49b72b575e26ecddb296dd59b24c3e67
+Content-length: 21
+
+PROPS-END
+deleteddir
+
+
+Node-path: trunk/deletedfile
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 4d742b2f247bec99b41a60acbebc149a
+Content-length: 18
+
+PROPS-END
+deleted
+
+
+Node-path: trunk/groupdir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/groupdir/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Node-path: trunk/groupdir/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/unchanged
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 10
+Text-content-md5: 85ae5b04dd0a666efad8633d142a4635
+Content-length: 20
+
+PROPS-END
+unchanged
+
+
+Node-path: trunk/unchangeddir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/unchangeddir/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 11
+Text-content-md5: a11092875079a002afb9ecef07f510e7
+Content-length: 21
+
+PROPS-END
+unchanged2
+
+
+Revision-number: 3
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 21
+delete files and dirs
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:40.224632Z
+PROPS-END
+
+Node-path: trunk/changed
+Node-kind: file
+Node-action: change
+Text-content-length: 16
+Text-content-md5: 1725f40a29aad369a39b0f96c82d50f9
+Content-length: 16
+
+changed
+changed
+
+
+Node-path: trunk/changeddir/f
+Node-kind: file
+Node-action: change
+Text-content-length: 18
+Text-content-md5: 984b8c4ab9193b7659b9f914897a949c
+Content-length: 18
+
+changed2
+changed2
+
+
+Node-path: trunk/groupdir/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0d227f1abf8c2932d342e9b99cc957eb
+Content-length: 4
+
+a
+a
+
+
+Node-path: trunk/deleteddir
+Node-action: delete
+
+
+Node-path: trunk/deletedfile
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+create branch1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:42.184704Z
+PROPS-END
+
+Node-path: branches/branch1
+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
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+
+
+Node-path: branches/branch1/b
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/b
+Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+
+
+Node-path: branches/branch1/changed
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/changed
+Text-copy-source-md5: 1725f40a29aad369a39b0f96c82d50f9
+
+
+Node-path: branches/branch1/changeddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changeddir
+
+
+Node-path: branches/branch1/changeddir/f
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/changeddir/f
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/changeddir/f
+Text-copy-source-md5: 984b8c4ab9193b7659b9f914897a949c
+
+
+
+
+Node-path: branches/branch1/da
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/da
+
+
+Node-path: branches/branch1/groupdir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/groupdir
+
+
+Node-path: branches/branch1/groupdir/a
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/groupdir/a
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/groupdir/a
+Text-copy-source-md5: 0d227f1abf8c2932d342e9b99cc957eb
+
+
+
+
+Node-path: branches/branch1/unchanged
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchanged
+Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+
+
+Node-path: branches/branch1/unchangeddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchangeddir
+
+
+Revision-number: 5
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 5
+add c
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:43.175723Z
+PROPS-END
+
+Node-path: branches/branch1/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Revision-number: 6
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 27
+rename and copy a, b and da
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:50.200094Z
+PROPS-END
+
+Node-path: branches/branch1/c1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/c
+Text-copy-source-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Prop-content-length: 34
+Text-content-length: 4
+Text-content-md5: 63fad9092ad37713ebe26b3193f89c41
+Content-length: 38
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+c
+c
+
+
+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
+
+
+Node-path: trunk/a2
+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
+
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 06ac26ed8b614fc0b141e4542aa067c2
+Content-length: 4
+
+b
+b
+
+
+Node-path: trunk/b1
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/b
+Text-copy-source-md5: 3b5d5c3712955042212316173ccf37be
+Prop-content-length: 34
+Text-content-length: 4
+Text-content-md5: 33cb6785d50937d8d307ebb66d6259a7
+Content-length: 38
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+b
+c
+
+
+Node-path: trunk/da1
+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
+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/a
+Node-action: delete
+
+
+Node-path: trunk/da
+Node-action: delete
+
+
+Revision-number: 7
+Prop-content-length: 121
+Content-length: 121
+
+K 7
+svn:log
+V 19
+copy b from branch1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:52.154125Z
+PROPS-END
+
+Node-path: trunk/c
+Node-kind: file
+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
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 24
+copy stuff from the past
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:55.160439Z
+PROPS-END
+
+Node-path: trunk/deleteddir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/deleteddir
+
+
+Node-path: trunk/deletedfile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/deletedfile
+Text-copy-source-md5: 4d742b2f247bec99b41a60acbebc149a
+
+
+Revision-number: 9
+Prop-content-length: 140
+Content-length: 140
+
+K 7
+svn:log
+V 38
+copy stuff from the past before change
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:48:59.163460Z
+PROPS-END
+
+Node-path: trunk/changed2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changed
+Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+
+
+Node-path: trunk/changed3
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changed
+Text-copy-source-md5: ec1bebaea2c042beb68f7679ddd106a4
+Text-content-length: 17
+Text-content-md5: 7d93e8c4d61c2a7b05c20b7d8bf11f83
+Content-length: 17
+
+changed
+changed3
+
+
+Node-path: trunk/changeddir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/changeddir
+
+
+Revision-number: 10
+Prop-content-length: 136
+Content-length: 136
+
+K 7
+svn:log
+V 34
+copy unchanged stuff from the past
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:49:02.160163Z
+PROPS-END
+
+Node-path: trunk/unchanged2
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchanged
+Text-copy-source-md5: 85ae5b04dd0a666efad8633d142a4635
+
+
+Node-path: trunk/unchangeddir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/unchangeddir
+
+
+Revision-number: 11
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 27
+copy groupdir from the past
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-05T22:49:04.157303Z
+PROPS-END
+
+Node-path: trunk/groupdir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/groupdir
+
+
diff --git a/opt/hgsubversion/tests/fixtures/renametagdir.sh b/opt/hgsubversion/tests/fixtures/renametagdir.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renametagdir.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# inspired by Python r62868
+
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+export REPO=file://`pwd`/repo
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+
+echo a > trunk/a
+svn add trunk/a
+svn ci -m 'Add file.'
+svn up
+
+svn cp trunk branches/test
+svn ci -m 'Branch.'
+svn up
+
+cd ..
+svn cp -m 'First tag.' $REPO/branches/test@3 $REPO/tags/test-0.1
+svn cp -m 'Weird tag.' $REPO/branches/test@3 $REPO/tags/test-0.1/test
+svn mv -m 'Fix tag pt 1.' $REPO/tags/test-0.1/test $REPO/tags/test-0.1-real
+svn rm -m 'Remove weird.' $REPO/tags/test-0.1
+svn mv -m 'Fix tag pt 2.' $REPO/tags/test-0.1-real $REPO/tags/test-0.1
+
+cd ..
+svnadmin dump temp/repo > renametagdir.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in renametagdir.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/renametagdir.svndump b/opt/hgsubversion/tests/fixtures/renametagdir.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/renametagdir.svndump
@@ -0,0 +1,245 @@
+SVN-fs-dump-format-version: 2
+
+UUID: b3e24de6-f874-406f-9561-e48db081ff5e
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-01-20T12:58:05.107637Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 104
+Content-length: 104
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:06.111903Z
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+Add file.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:07.088858Z
+PROPS-END
+
+Node-path: 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
+
+
+Revision-number: 3
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 7
+Branch.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:10.062719Z
+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: 112
+Content-length: 112
+
+K 7
+svn:log
+V 10
+First tag.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:12.060717Z
+PROPS-END
+
+Node-path: tags/test-0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/test
+
+
+Revision-number: 5
+Prop-content-length: 112
+Content-length: 112
+
+K 7
+svn:log
+V 10
+Weird tag.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:12.110400Z
+PROPS-END
+
+Node-path: tags/test-0.1/test
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/test
+
+
+Revision-number: 6
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 13
+Fix tag pt 1.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:12.169328Z
+PROPS-END
+
+Node-path: tags/test-0.1/test
+Node-action: delete
+
+
+Node-path: tags/test-0.1-real
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: tags/test-0.1/test
+
+
+Revision-number: 7
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 13
+Remove weird.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:12.212355Z
+PROPS-END
+
+Node-path: tags/test-0.1
+Node-action: delete
+
+
+Revision-number: 8
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 13
+Fix tag pt 2.
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-01-20T12:58:12.317421Z
+PROPS-END
+
+Node-path: tags/test-0.1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 7
+Node-copyfrom-path: tags/test-0.1-real
+
+
+Node-path: tags/test-0.1-real
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.sh b/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+RSVN="`pwd`/rsvn.py"
+export PATH=/bin:/usr/bin
+mkdir temp
+cd temp
+
+svnadmin create repo
+svn co file://`pwd`/repo wc
+
+cd wc
+mkdir trunk branches
+cd trunk
+echo a > a
+cd ..
+svn add *
+svn ci -m 'initial'
+
+svn up
+svn cp trunk branches/branch1
+svn ci -m 'branch1'
+svn up
+echo b > branches/branch1/b
+echo d > branches/branch1/d
+mkdir branches/branch1/dir
+echo e > branches/branch1/dir/e
+echo f > branches/branch1/f
+echo g > branches/branch1/g
+svn add branches/branch1/b branches/branch1/d branches/branch1/dir \
+    branches/branch1/f branches/branch1/g
+svn ci -m 'add b to branch1'
+svn cp trunk branches/branch2
+svn ci -m 'branch2'
+svn up
+echo c > branches/branch2/c
+mkdir branches/branch2/dir
+echo e2 > branches/branch2/dir/e
+echo f2 > branches/branch2/f
+svn add branches/branch2/c branches/branch2/dir branches/branch2/f
+svn ci -m 'add c to branch2'
+svn up
+
+# Clobber branch1 with branch2
+cd ..
+cat > clobber.rsvn <<EOF
+rdelete branches/branch1
+rcopy branches/branch2 branches/branch1
+rcopy branches/branch1/d branches/branch1/a
+rcopy branches/branch1/dir branches/branch1/dir
+rcopy branches/branch1/dir branches/branch1/dir2
+rcopy branches/branch1/f branches/branch1/f
+rcopy branches/branch1/g branches/branch1/g
+EOF
+
+python $RSVN --message=blah --username=evil `pwd`/repo < clobber.rsvn
+
+svnadmin dump repo > ../replace_branch_with_branch.svndump
diff --git a/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.svndump b/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/replace_branch_with_branch.svndump
@@ -0,0 +1,361 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 6c4a9db2-8b38-4988-bc82-df1f3bb0afcd
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-03-01T23:13:19.505202Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-03-01T23:13:20.066689Z
+PROPS-END
+
+Node-path: branches
+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
+
+
+Node-path: 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
+
+
+Revision-number: 2
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 7
+branch1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-03-01T23:13:23.056920Z
+PROPS-END
+
+Node-path: branches/branch1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 16
+add b to branch1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-03-01T23:13:25.099593Z
+PROPS-END
+
+Node-path: branches/branch1/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: branches/branch1/d
+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: branches/branch1/dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/branch1/dir/e
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 9ffbf43126e33be52cd2bf7e01d627f9
+Text-content-sha1: 094e3afb2fe8dfe82f63731cdcd3b999f4856cff
+Content-length: 12
+
+PROPS-END
+e
+
+
+Node-path: branches/branch1/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 9a8ad92c50cae39aa2c5604fd0ab6d8c
+Text-content-sha1: a9fcd54b25e7e863d72cd47c08af46e61b74b561
+Content-length: 12
+
+PROPS-END
+f
+
+
+Node-path: branches/branch1/g
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: f5302386464f953ed581edac03556e55
+Text-content-sha1: a5938ace3f424be1a26904781cdb06d55b614e6b
+Content-length: 12
+
+PROPS-END
+g
+
+
+Revision-number: 4
+Prop-content-length: 108
+Content-length: 108
+
+K 7
+svn:log
+V 7
+branch2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-03-01T23:13:27.051018Z
+PROPS-END
+
+Node-path: branches/branch2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 16
+add c to branch2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2010-03-01T23:13:29.108780Z
+PROPS-END
+
+Node-path: branches/branch2/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: branches/branch2/dir
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/branch2/dir/e
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: c18e1214a2500c0c3636092222f8a850
+Text-content-sha1: 4a078b075f259063b9a730521378f70ec3223238
+Content-length: 13
+
+PROPS-END
+e2
+
+
+Node-path: branches/branch2/f
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 3
+Text-content-md5: 575c5638d60271457e54ab7d07309502
+Text-content-sha1: 1c49a440c352f3473efa9512255033b94dc7def0
+Content-length: 13
+
+PROPS-END
+f2
+
+
+Revision-number: 6
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 4
+blah
+K 10
+svn:author
+V 4
+evil
+K 8
+svn:date
+V 27
+2010-03-01T23:13:31.520483Z
+PROPS-END
+
+Node-path: branches/branch1
+Node-kind: dir
+Node-action: delete
+
+Node-path: branches/branch1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch2
+
+
+
+
+Node-path: branches/branch1/a
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/a
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/d
+Text-copy-source-md5: e29311f6f1bf1af907f9ef9f44b8328b
+Text-copy-source-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff
+
+
+
+
+Node-path: branches/branch1/dir
+Node-kind: dir
+Node-action: delete
+
+Node-path: branches/branch1/dir
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/dir
+
+
+
+
+Node-path: branches/branch1/dir2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/dir
+
+
+Node-path: branches/branch1/f
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/branch1/f
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/f
+Text-copy-source-md5: 9a8ad92c50cae39aa2c5604fd0ab6d8c
+Text-copy-source-sha1: a9fcd54b25e7e863d72cd47c08af46e61b74b561
+
+
+
+
+Node-path: branches/branch1/g
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/branch1/g
+Text-copy-source-md5: f5302386464f953ed581edac03556e55
+Text-copy-source-sha1: a5938ace3f424be1a26904781cdb06d55b614e6b
+
+
diff --git a/opt/hgsubversion/tests/fixtures/replace_trunk.sh b/opt/hgsubversion/tests/fixtures/replace_trunk.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/replace_trunk.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+RSVN="`pwd`/rsvn.py"
+export PATH=/bin:/usr/bin
+mkdir temp
+cd temp
+
+svnadmin create repo
+svn co file://`pwd`/repo wc
+
+cd wc
+mkdir trunk branches
+cd trunk
+for a in alpha beta gamma ; do
+    echo $a > $a
+done
+cd ..
+svn add *
+svn ci -m 'initial'
+
+svn up
+svn cp trunk branches/test
+svn ci -m 'branch'
+
+svn up
+echo foo >> branches/test/alpha
+svn ci -m 'Mod.'
+
+cd ..
+echo rdelete trunk > tmp
+echo rcopy branches/test trunk >> tmp
+python $RSVN --message=blah --username=evil `pwd`/repo < tmp
+
+svnadmin dump repo > ../replace_trunk_with_branch.svndump
diff --git a/opt/hgsubversion/tests/fixtures/replace_trunk_with_branch.svndump b/opt/hgsubversion/tests/fixtures/replace_trunk_with_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/replace_trunk_with_branch.svndump
@@ -0,0 +1,171 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 5b65bade-98f3-4993-a01f-b7a6710da339
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-16T16:37:53.651472Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 7
+initial
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:54.139835Z
+PROPS-END
+
+Node-path: branches
+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
+
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:57.116844Z
+PROPS-END
+
+Node-path: branches/test
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 1
+Node-copyfrom-path: trunk
+
+
+Revision-number: 3
+Prop-content-length: 103
+Content-length: 103
+
+K 7
+svn:log
+V 4
+Mod.
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-12-16T16:37:59.118952Z
+PROPS-END
+
+Node-path: branches/test/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 10
+Text-content-md5: 54add8695a828f4e66224b1ecfd9c576
+Content-length: 10
+
+alpha
+foo
+
+
+Revision-number: 4
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 4
+blah
+K 10
+svn:author
+V 4
+evil
+K 8
+svn:date
+V 27
+2008-12-16T16:38:00.236345Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: delete
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/test
+
+
+
+
diff --git a/opt/hgsubversion/tests/fixtures/rsvn.py b/opt/hgsubversion/tests/fixtures/rsvn.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/rsvn.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python2
+
+# LICENSE
+#
+# Copyright (c) 2004, Francois Beausoleil
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   * Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#   * Neither the name of the Francois Beausoleil nor the names of its
+#     contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import getopt
+import sys
+import os
+import re
+import traceback
+from svn import core, repos, fs
+
+VERSION='$Id: rsvn.py 20 2005-09-23 15:08:08Z fbos $'
+RCOPY_RE    = re.compile('^\s*rcopy\s+(.+)\s+(.+)$')
+RMOVE_RE    = re.compile('^\s*rmove\s+(.+)\s+(.+)$')
+RMKDIR_RE   = re.compile('^\s*rmkdir\s+(.+)$')
+RDELETE_RE  = re.compile('^\s*rdelete\s+(.+)$')
+COMMENT_RE  = re.compile('(?:^\s*#)|(?:^\s*$)')
+
+def usage(error=None):
+  if error:
+    print 'Error: %s\n' % error
+  print 'USAGE: %s --message=MESSAGE repos_path [--username=USERNAME]' % (
+          os.path.basename(sys.argv[0]))
+  print ''
+  print '  --help, -h           print this usage message and exit with success'
+  print '  --version            print the version number'
+  print '  --username=USERNAME  Username to execute the commands under'
+  print '  --message=LOG_MSG    Log message to execute the commit with'
+  print ''
+  print 'Reads STDIN and parses the following commands, to execute them on the server, '
+  print 'all within the same transaction:'
+  print ''
+  print '  rcopy SRC DEST       Copy the HEAD revision of a file or folder'
+  print '  rmove SRC DEST       Copy + delete the HEAD revision of a file or folder'
+  print '  rdelete TARGET       Deletes something from the repository'
+  print '  rmkdir TARGET        Creates a new folder (must create parents first)'
+  print '  #                    Initiates a comment'
+  print ''
+#        12345678901234567890123456789012345678901234567890123456789012345678901234567890
+
+
+class Transaction:
+  """Represents a transaction in a Subversion repository
+  
+     Transactions are long-lived objects which exist in the repository,
+     and are used to build an intermediate representation of a new
+     revision.  Once the transaction is committed, the repository
+     bumps the revision number, and links the new transaction in the
+     Subversion filesystem."""
+
+  def __init__(self, repository, rev, username, message, pool, logger=None):
+    if logger:
+     self.logger = logger
+    else:
+     self.logger = sys.stdout
+    self.pool = pool
+    self.rev = rev
+
+    self.fsptr = repos.svn_repos_fs(repository)
+    self.rev_root = fs.revision_root(self.fsptr, self.rev,
+                    self.pool)
+    self.txnp = repos.svn_repos_fs_begin_txn_for_commit(
+            repository, self.rev, username, message, self.pool)
+    self.txn_root = fs.txn_root(self.txnp, self.pool)
+    self.log('Base revision %d\n' % rev)
+
+  def commit(self):
+    values = fs.commit_txn(self.txnp, self.pool)
+    return values[1]
+
+  def rollback(self):
+    fs.abort_txn(self.txnp, self.pool)
+
+  def copy(self, src, dest, subpool):
+    self.log('A  + %s\n' % dest)
+    fs.copy(self.rev_root, src, self.txn_root, dest, subpool)
+
+  def delete(self, entry, subpool):
+    self.log('D    %s\n' % entry)
+    fs.delete(self.txn_root, entry, subpool)
+
+  def mkdir(self, entry, subpool):
+    self.log('A    %s\n' % entry)
+    fs.make_dir(self.txn_root, entry, subpool)
+
+  def move(self, src, dest, subpool):
+    self.copy(src, dest, subpool)
+    self.delete(src, subpool)
+
+  def log(self, msg):
+    self.logger.write(msg)
+
+
+class Repository:
+  """Represents a Subversion repository, and allows common operations
+     on it."""
+
+  def __init__(self, repos_path, pool, logger=None):
+    if logger:
+      self.logger = logger
+    else:
+      self.logger = sys.stdout
+    self.pool = pool
+    assert self.pool
+
+    self.repo = repos.svn_repos_open(repos_path, self.pool)
+    self.fsptr = repos.svn_repos_fs(self.repo)
+
+  def get_youngest(self):
+    """Returns the youngest revision in the repository."""
+    return fs.youngest_rev(self.fsptr, self.pool)
+
+  def begin(self, username, log_msg):
+    """Initiate a new Transaction"""
+    return Transaction(self.repo, self.get_youngest(), username,
+            log_msg, self.pool, self.logger)
+
+  def close(self):
+    """Close the repository, aborting any uncommitted transactions"""
+    core.svn_pool_destroy(self.pool)
+    core.apr_terminate()
+
+  def subpool(self):
+    """Instantiates a new pool from the master pool"""
+    return core.svn_pool_create(self.pool)
+
+  def delete_pool(self, pool):
+    """Deletes the passed-in pool.  Returns None, to assign to pool in
+       caller."""
+    core.svn_pool_destroy(pool)
+    return None
+
+def rsvn(pool):
+  log_msg = None
+
+  try:
+    opts, args = getopt.getopt(sys.argv[1:], 'vh',
+                ["help", "username=", "message=", "version"])
+  except getopt.GetoptError, e:
+    sys.stderr.write(str(e) + '\n\n')
+    usage()
+    sys.exit(1)
+  
+  for opt, value in opts:
+    if opt == '--version':
+      print '%s version %s' % (os.path.basename(sys.argv[0]), VERSION)
+      sys.exit(0)
+    elif opt == '--help' or opt == '-h':
+      usage()
+      sys.exit(0)
+    elif opt == '--username':
+      username = value
+    elif opt == '--message':
+      log_msg = value
+  
+  if log_msg == None:
+    usage('Missing --message argument')
+    sys.exit(1)
+  
+  if len(args) != 1:
+    usage('Missing repository path argument')
+    sys.exit(1)
+  
+  repos_path = args[0]
+  print 'Accessing repository at [%s]' % repos_path
+
+  repository = Repository(repos_path, pool)
+  sub = repository.subpool()
+  
+  try:
+    txn = repository.begin(username, log_msg)
+  
+    # Read commands from STDIN
+    lineno = 0
+    for line in sys.stdin:
+      lineno += 1
+      
+      core.svn_pool_clear(sub)
+      try:
+        if COMMENT_RE.search(line):
+          continue
+    
+        match = RCOPY_RE.search(line)
+        if match:
+          src = match.group(1)
+          dest = match.group(2)
+          txn.copy(src, dest, sub)
+          continue
+    
+        match = RMOVE_RE.search(line)
+        if match:
+          src = match.group(1)
+          dest = match.group(2)
+          txn.move(src, dest, sub)
+          continue
+    
+        match = RMKDIR_RE.search(line)
+        if match:
+          entry = match.group(1)
+          txn.mkdir(entry, sub)
+          continue
+    
+        match = RDELETE_RE.search(line)
+        if match:
+          entry = match.group(1)
+          txn.delete(entry, sub)
+          continue
+  
+        raise NameError, ('Unknown command [%s] on line %d' %
+            (line, lineno))
+    
+      except:
+        sys.stderr.write(('Exception occured while processing line %d:\n' % 
+            lineno))
+        etype, value, tb = sys.exc_info()
+        traceback.print_exception(etype, value, tb, None, sys.stderr)
+        sys.stderr.write('\n')
+        txn.rollback()
+        sys.exit(1)
+  
+    new_rev = txn.commit()
+    print '\nCommitted revision %d.' % new_rev
+  
+  finally:
+    print '\nRepository closed.'
+
+def main():
+    core.run_app(rsvn)
+
+if __name__ == '__main__':
+    main()
diff --git a/opt/hgsubversion/tests/fixtures/siblingbranchfix.sh b/opt/hgsubversion/tests/fixtures/siblingbranchfix.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/siblingbranchfix.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+svn add *
+svn ci -m 'btt'
+cd trunk
+
+echo a > a
+svn add a
+svn ci -m 'Add file.'
+svn up
+
+cd ..
+svn cp trunk wrongbranch
+svn ci -m 'Branch to repo root dir.'
+svn up
+
+svn mv wrongbranch branches/wrongbranch
+svn ci -m 'Move branch to correct branches location'
+svn up
+
+cd ../..
+svnadmin dump temp/repo > siblingbranchfix.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in siblingbranchfix.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/siblingbranchfix.svndump b/opt/hgsubversion/tests/fixtures/siblingbranchfix.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/siblingbranchfix.svndump
@@ -0,0 +1,144 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 380991ed-036e-45db-832e-7a8ca661c4f0
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-09-19T07:20:51.801386Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 100
+Content-length: 100
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-09-19T07:20:52.057610Z
+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: 106
+Content-length: 106
+
+K 7
+svn:log
+V 9
+Add file.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-09-19T07:20:53.056417Z
+PROPS-END
+
+Node-path: 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
+
+
+Revision-number: 3
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 24
+Branch to repo root dir.
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-09-19T07:20:56.043593Z
+PROPS-END
+
+Node-path: wrongbranch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 138
+Content-length: 138
+
+K 7
+svn:log
+V 40
+Move branch to correct branches location
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-09-19T07:20:59.045512Z
+PROPS-END
+
+Node-path: branches/wrongbranch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: wrongbranch
+
+
+Node-path: wrongbranch
+Node-action: delete
+
+
diff --git a/opt/hgsubversion/tests/fixtures/simple_branch.sh b/opt/hgsubversion/tests/fixtures/simple_branch.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/simple_branch.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Generate simple_branch.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk branches tags
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import --username durin project-orig $svnurl -m "Empty dirs."
+
+svn co $svnurl project
+cd project
+echo 'file: alpha' > trunk/alpha
+svn add trunk/alpha
+svn ci --username durin -m 'Add alpha'
+echo 'Data of beta' > trunk/beta
+svn add trunk/beta
+svn ci --username durin -m 'Add beta'
+svn up
+svn cp trunk branches/the_branch
+svn ci --username durin -m 'Make a branch'
+cd ..
+
+svnadmin dump testrepo > ../simple_branch.svndump
diff --git a/opt/hgsubversion/tests/fixtures/simple_branch.svndump b/opt/hgsubversion/tests/fixtures/simple_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/simple_branch.svndump
@@ -0,0 +1,146 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 3cd547df-371e-4add-bccf-aba732a2baf5
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2010-02-02T21:54:11.642093Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2010-02-02T21:54:11.673761Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2010-02-02T21:54:12.073120Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Text-content-sha1: f552a50b53177d35b29a4a0ab1cece918b5b5e9b
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2010-02-02T21:54:13.096862Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Text-content-sha1: 5d40e0a9ceda69f3d98d4851a6bee02c10a6e277
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Make a branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2010-02-02T21:54:16.042325Z
+PROPS-END
+
+Node-path: branches/the_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
diff --git a/opt/hgsubversion/tests/fixtures/single_rev.svndump b/opt/hgsubversion/tests/fixtures/single_rev.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/single_rev.svndump
@@ -0,0 +1,89 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
diff --git a/opt/hgsubversion/tests/fixtures/spaces-in-path.sh b/opt/hgsubversion/tests/fixtures/spaces-in-path.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/spaces-in-path.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+mkdir tags/versions
+mkdir tags/blah
+svn add *
+svn ci -m 'btt'
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+
+echo 'foo bar' > 'foo bar'
+svn add 'foo bar'
+svn ci -m 'Add files.'
+
+mkdir 'blah blah'
+echo 'another file' > 'blah blah/another file'
+svn add 'blah blah'
+svn ci -m 'Add files.'
+
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'Make a branch'
+cd ../..
+
+svnadmin dump temp/repo > spaces-in-path.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in spaces-in-path.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/spaces-in-path.svndump b/opt/hgsubversion/tests/fixtures/spaces-in-path.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/spaces-in-path.svndump
@@ -0,0 +1,243 @@
+SVN-fs-dump-format-version: 2
+
+UUID: fbe42c69-30e8-40e8-8707-cf3ddc79d968
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-09-16T01:53:29.172947Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-09-16T01:53:30.119780Z
+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: tags/blah
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags/versions
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-09-16T01:53:31.149862Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Text-content-sha1: d046cd9b7ffb7661e449683313d41f6fc33e3130
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Text-content-sha1: 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Text-content-sha1: 4bd6315d6d7824c4e376847ca7d116738ad2f29a
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Text-content-sha1: 37f385b028bf2f93a4b497ca9ff44eea63945b7f
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-09-16T01:53:32.079732Z
+PROPS-END
+
+Node-path: trunk/foo bar
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 5ceaa7ed396ccb8e959c02753cb4bd18
+Text-content-sha1: d53a205a336e07cf9eac45471b3870f9489288ec
+Content-length: 18
+
+PROPS-END
+foo bar
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-09-16T01:53:33.091599Z
+PROPS-END
+
+Node-path: trunk/blah blah
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/blah blah/another file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: fb319cc56653b713a8c7a54aa92f6efd
+Text-content-sha1: 98f46145eb13fc9d662fb2d07fe9faf9969be54b
+Content-length: 23
+
+PROPS-END
+another file
+
+
+Revision-number: 5
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Make a branch
+K 10
+svn:author
+V 5
+augie
+K 8
+svn:date
+V 27
+2009-09-16T01:53:36.062701Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: trunk
+
+
diff --git a/opt/hgsubversion/tests/fixtures/symlinks.sh b/opt/hgsubversion/tests/fixtures/symlinks.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/symlinks.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Generate symlinks.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+echo a > a
+ln -s a linka
+ln -s a linka2
+mkdir d
+ln -s a d/linka
+svn add a linka linka2 d
+svn ci -m "add symlinks"
+# Move symlinks
+svn mv linka linkaa
+svn mv d d2
+svn commit -m "moving symlinks"
+# Update symlinks (test "link " prefix vs applydelta)
+echo b > b
+rm linkaa
+ln -s b linkaa
+rm d2/linka
+ln -s b d2/linka
+svn ci -m "update symlinks"
+# Replace a symlink with a regular file
+rm linkaa
+echo data > linkaa
+svn propdel svn:special linkaa
+# Replace another symlink with a regular file in another way
+# It should mark linka2 as R(eplaced)
+svn rm linka2
+echo data2 > linka2
+svn add linka2
+svn propdel svn:special linka2
+svn rm d2/linka
+svn ci -m "undo link"
+cd ../..
+
+svnadmin dump testrepo > ../symlinks.svndump
diff --git a/opt/hgsubversion/tests/fixtures/symlinks.svndump b/opt/hgsubversion/tests/fixtures/symlinks.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/symlinks.svndump
@@ -0,0 +1,405 @@
+SVN-fs-dump-format-version: 2
+
+UUID: bcc1a3f4-ccae-49ba-b1c5-003ce86e3e31
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-15T21:58:13.685247Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:58:13.747067Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+add symlinks
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:58:14.243223Z
+PROPS-END
+
+Node-path: 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: trunk/d
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/d/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/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/linka2
+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
+
+Revision-number: 3
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 15
+moving symlinks
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:58:17.162148Z
+PROPS-END
+
+Node-path: trunk/d2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/d
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk/linkaa
+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
+Prop-content-length: 57
+Content-length: 57
+
+K 11
+svn:special
+V 1
+*
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk/linka
+Node-action: delete
+
+
+Node-path: trunk/d
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 15
+update symlinks
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:58:18.188491Z
+PROPS-END
+
+Node-path: trunk/d2/linka
+Node-kind: file
+Node-action: change
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 6
+
+link b
+
+Node-path: trunk/linkaa
+Node-kind: file
+Node-action: change
+Text-content-length: 6
+Text-content-md5: e9292b8c4fca95ac8c70b4ae040d0b79
+Text-content-sha1: 7325442a5f7383205e66db563025d51535883784
+Content-length: 6
+
+link b
+
+Revision-number: 5
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 9
+undo link
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-15T21:58:19.297689Z
+PROPS-END
+
+Node-path: trunk/d2/linka
+Node-action: delete
+
+
+Node-path: trunk/linka2
+Node-kind: file
+Node-action: replace
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: edc3d3797971f12c7f5e1d106dd5cee2
+Text-content-sha1: eff1098d818d1f471af4a2cbdb0223e4e030a158
+Content-length: 16
+
+PROPS-END
+data2
+
+
+Node-path: trunk/linkaa
+Node-kind: file
+Node-action: change
+Prop-content-length: 34
+Text-content-length: 5
+Text-content-md5: 6137cde4893c59f76f005a8123d8e8e6
+Text-content-sha1: c5d84736ba451747dd5f0eb9d17e104f3697ef47
+Content-length: 39
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+data
+
+
+Revision-number: 6
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 15
+recreate a link
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-11-08T14:57:19.667088Z
+PROPS-END
+
+Node-path: trunk/linka3
+Node-kind: file
+Node-action: add
+Prop-content-length: 59
+Text-content-length: 6
+Text-content-md5: c118dba188202a1efc975bef6064180b
+Text-content-sha1: 41f94e4692313bf7f7c92aa600002f1dff93d6bf
+Content-length: 65
+
+K 11
+svn:special
+V 1
+*
+K 14
+svn:needs-lock
+V 1
+*
+PROPS-END
+link a
+
+Revision-number: 7
+Prop-content-length: 128
+Content-length: 128
+
+K 7
+svn:log
+V 27
+delete property from a link
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-11-08T14:57:40.331635Z
+PROPS-END
+
+Node-path: trunk/linka3
+Node-kind: file
+Node-action: change
+Prop-content-length: 33
+Content-length: 33
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+
+
+Revision-number: 8
+Prop-content-length: 145
+Content-length: 145
+
+K 7
+svn:log
+V 44
+add an uglily named file and a symlink to it
+K 10
+svn:author
+V 6
+danchr
+K 8
+svn:date
+V 27
+2010-11-08T15:11:12.267958Z
+PROPS-END
+
+Node-path: trunk/link to this
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 0
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
+Text-content-sha1: da39a3ee5e6b4b0d3255bfef95601890afd80709
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: trunk/linka4
+Node-kind: file
+Node-action: add
+Prop-content-length: 33
+Text-content-length: 17
+Text-content-md5: aecd1324085a1fc5b7aa8950f92dcfb9
+Text-content-sha1: d22de0907c0b1878b057c873092e82fd9c50849b
+Content-length: 50
+
+K 11
+svn:special
+V 1
+*
+PROPS-END
+link link to this
+
diff --git a/opt/hgsubversion/tests/fixtures/tag_by_rename_branch.svndump b/opt/hgsubversion/tests/fixtures/tag_by_rename_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/tag_by_rename_branch.svndump
@@ -0,0 +1,239 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+tagging r3
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:29.837825Z
+PROPS-END
+
+Node-path: tags/tag_r3
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+tag from a tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T23:18:51.091356Z
+PROPS-END
+
+Node-path: tags/copied_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: tags/tag_r3
+
+
+Revision-number: 6
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+make a branch
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-10-20T21:31:18.706649Z
+PROPS-END
+
+Node-path: branches/dummy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 7
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 18
+make a tag with mv
+K 10
+svn:author
+V 5
+Augie
+K 8
+svn:date
+V 27
+2008-10-20T21:31:41.138126Z
+PROPS-END
+
+Node-path: branches/dummy
+Node-action: delete
+
+
+Node-path: tags/dummy
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 6
+Node-copyfrom-path: branches/dummy
+
+
diff --git a/opt/hgsubversion/tests/fixtures/tag_deletion_tag_branch.svndump b/opt/hgsubversion/tests/fixtures/tag_deletion_tag_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/tag_deletion_tag_branch.svndump
@@ -0,0 +1,226 @@
+SVN-fs-dump-format-version: 2
+
+UUID: eef88803-8121-4c2d-a584-b15733364666
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-05-30T18:12:25.144447Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 103
+Content-length: 103
+
+K 7
+svn:log
+V 4
+init
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:13:59.514904Z
+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
+
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: 207dd96a2baf1c1bfe2728ea67da7e37
+Content-length: 28
+
+PROPS-END
+content of alpha.
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 17
+Text-content-md5: 868f9a1cd3f678461c02fee4d8b7d44c
+Content-length: 27
+
+PROPS-END
+content of beta.
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: cee60c4f880a73eea79fac90547d0142
+Content-length: 28
+
+PROPS-END
+content of delta.
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: 865ce111ce132951995b1b7b63f0f2dc
+Content-length: 28
+
+PROPS-END
+content of gamma.
+
+
+Revision-number: 2
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+remove
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:14:10.610589Z
+PROPS-END
+
+Node-path: trunk/delta
+Node-action: delete
+
+
+Revision-number: 3
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+trunk at 2 tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:14:43.562547Z
+PROPS-END
+
+Node-path: tags/trunk_at_2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 15
+branch of trunk
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:15:06.771293Z
+PROPS-END
+
+Node-path: branches/from_2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+
+
+Revision-number: 5
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 7
+Untag 2
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:15:30.999412Z
+PROPS-END
+
+Node-path: tags/trunk_at_2
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+make a new tag
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-30T18:16:01.917326Z
+PROPS-END
+
+Node-path: tags/new_tag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: branches/from_2
+
+
diff --git a/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.sh b/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+REPOPATH="file://`pwd`/repo"
+svn co $REPOPATH wc
+cd wc
+mkdir -p branches/magic trunk tags
+svn add *
+svn ci -m 'btt'
+cd branches/magic
+for a in alpha beta gamma delta iota zeta eta theta ; do
+    echo $a > $a
+    svn add $a
+    svn ci -m "Add file $a"
+done
+cd ../..
+svn up
+svn cp $REPOPATH/branches/magic $REPOPATH/tags/magic -m 'Make magic tag'
+svn rm $REPOPATH/branches/magic/theta -m 'remove a file'
+svn cp $REPOPATH/branches/magic $REPOPATH/tags/magic2 -m 'Tag magic again'
+
+cd ../..
+svnadmin dump temp/repo > tag_name_same_as_branch.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in tag_name_same_as_branch.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.svndump b/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/tag_name_same_as_branch.svndump
@@ -0,0 +1,388 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 8ede5731-e772-49cc-a297-c19c9844b692
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-06-01T16:09:33.850620Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:34.084216Z
+PROPS-END
+
+Node-path: branches
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/magic
+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: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+Add file alpha
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:35.078795Z
+PROPS-END
+
+Node-path: branches/magic/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Text-content-sha1: d046cd9b7ffb7661e449683313d41f6fc33e3130
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Revision-number: 3
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+Add file beta
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:36.385254Z
+PROPS-END
+
+Node-path: branches/magic/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Text-content-sha1: 6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Revision-number: 4
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+Add file gamma
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:37.081077Z
+PROPS-END
+
+Node-path: branches/magic/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Text-content-sha1: 37f385b028bf2f93a4b497ca9ff44eea63945b7f
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 5
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+Add file delta
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:38.071461Z
+PROPS-END
+
+Node-path: branches/magic/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Text-content-sha1: 4bd6315d6d7824c4e376847ca7d116738ad2f29a
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Revision-number: 6
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+Add file iota
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:39.073363Z
+PROPS-END
+
+Node-path: branches/magic/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Text-content-sha1: 47e9aceee5149402971cda8590e9b912c1b1053e
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Revision-number: 7
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+Add file zeta
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:40.082055Z
+PROPS-END
+
+Node-path: branches/magic/zeta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: 2db8f255a13ae1e49099d9dad57b4a37
+Text-content-sha1: 2c5ea36ead157ee3089bcd883f26ea2c899b2521
+Content-length: 15
+
+PROPS-END
+zeta
+
+
+Revision-number: 8
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 12
+Add file eta
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:41.072675Z
+PROPS-END
+
+Node-path: branches/magic/eta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 655d1abbe3e97298b0948ba964ad2522
+Text-content-sha1: 3c106a7ec1fe510152eebcf2cc76e135cca63f62
+Content-length: 14
+
+PROPS-END
+eta
+
+
+Revision-number: 9
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+Add file theta
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:42.080715Z
+PROPS-END
+
+Node-path: branches/magic/theta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9485edf7b8ec0fdf0bc3bdb9d5fc6c28
+Text-content-sha1: dde607ddc995205a6a521f47511bd04fa506e286
+Content-length: 16
+
+PROPS-END
+theta
+
+
+Revision-number: 10
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 14
+Make magic tag
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:44.048672Z
+PROPS-END
+
+Node-path: tags/magic
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 9
+Node-copyfrom-path: branches/magic
+
+
+Revision-number: 11
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 13
+remove a file
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:44.080801Z
+PROPS-END
+
+Node-path: branches/magic/theta
+Node-action: delete
+
+
+Revision-number: 12
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 15
+Tag magic again
+K 10
+svn:author
+V 8
+afackler
+K 8
+svn:date
+V 27
+2009-06-01T16:09:44.115205Z
+PROPS-END
+
+Node-path: tags/magic2
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 11
+Node-copyfrom-path: branches/magic
+
+
diff --git a/opt/hgsubversion/tests/fixtures/tagged_vendor_and_oldest_not_trunk.svndump b/opt/hgsubversion/tests/fixtures/tagged_vendor_and_oldest_not_trunk.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/tagged_vendor_and_oldest_not_trunk.svndump
@@ -0,0 +1,325 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 0e935f28-8caa-dd11-b3dc-00105ae0362c
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-04T16:18:04.048615Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:18:29.661251Z
+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: 119
+Content-length: 119
+
+K 7
+svn:log
+V 19
+Add the vendor dir.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:19:16.816658Z
+PROPS-END
+
+Node-path: vendor
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: vendor/foobaz
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: vendor/foobaz/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: vendor/foobaz/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: vendor/foobaz/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: vendor/foobaz/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 139
+Content-length: 139
+
+K 7
+svn:log
+V 39
+Adding oldest data, which is not trunk.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:22:06.704260Z
+PROPS-END
+
+Node-path: branches/oldest
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/oldest/five
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: 014835e36358e38c7f7897d6571e4529
+Content-length: 15
+
+PROPS-END
+five
+
+
+Node-path: branches/oldest/four
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: 75ffdb827341e578959bfcabde3789d8
+Content-length: 15
+
+PROPS-END
+four
+
+
+Node-path: branches/oldest/one
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: 5bbf5a52328e7439ae6e719dfe712200
+Content-length: 14
+
+PROPS-END
+one
+
+
+Node-path: branches/oldest/three
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: febe6995bad457991331348f7b9c85fa
+Content-length: 16
+
+PROPS-END
+three
+
+
+Node-path: branches/oldest/two
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: c193497a1a06b2c72230e6146ff47080
+Content-length: 14
+
+PROPS-END
+two
+
+
+Revision-number: 4
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+delete
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:22:49.936769Z
+PROPS-END
+
+Node-path: trunk
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 124
+Content-length: 124
+
+K 7
+svn:log
+V 24
+create trunk from branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:23:00.562964Z
+PROPS-END
+
+Node-path: tags/foobaz_1
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: vendor/foobaz
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/oldest
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 6
+Prop-content-length: 146
+Content-length: 146
+
+K 7
+svn:log
+V 46
+copy data from a vendor branch tag into trunk.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-11-04T16:23:32.980956Z
+PROPS-END
+
+Node-path: trunk/foobaz
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: tags/foobaz_1
+
+
diff --git a/opt/hgsubversion/tests/fixtures/test_files_copied_from_outside_btt.svndump b/opt/hgsubversion/tests/fixtures/test_files_copied_from_outside_btt.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/test_files_copied_from_outside_btt.svndump
@@ -0,0 +1,170 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 115
+Content-length: 115
+
+K 7
+svn:log
+V 15
+Add third_party
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-27T03:16:13.831333Z
+PROPS-END
+
+Node-path: third_party
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: third_party/magic
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: third_party/magic/the_magic_software
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: bc82cb068ef102a1f4e296992e5979ef
+Content-length: 17
+
+PROPS-END
+magic!
+
+
+Revision-number: 4
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 31
+Copy magic in from third_party.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-27T03:16:32.737023Z
+PROPS-END
+
+Node-path: trunk/magic
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: third_party/magic
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/test_no_dates.svndump b/opt/hgsubversion/tests/fixtures/test_no_dates.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/test_no_dates.svndump
@@ -0,0 +1,133 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 3b9ee152-ff20-493a-9d97-5d739715df90
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-04-08T10:33:20.055686Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 55
+Content-length: 55
+
+K 7
+svn:log
+V 4
+init
+K 10
+svn:author
+V 3
+djc
+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: 56
+Content-length: 56
+
+K 7
+svn:log
+V 5
+add a
+K 10
+svn:author
+V 3
+djc
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 98
+Content-length: 98
+
+K 7
+svn:log
+V 1
+b
+K 10
+svn:author
+V 3
+djc
+K 8
+svn:date
+V 27
+2009-04-08T10:35:21.843827Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 3
+Text-content-md5: daa8075d6ac5ff8d0c6d4650adb4ef29
+Content-length: 3
+
+ab
+
+
+Revision-number: 4
+Prop-content-length: 52
+Content-length: 52
+
+K 7
+svn:log
+V 1
+c
+K 10
+svn:author
+V 3
+djc
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1
+Content-length: 4
+
+abc
+
+
diff --git a/opt/hgsubversion/tests/fixtures/truncatedhistory.sh b/opt/hgsubversion/tests/fixtures/truncatedhistory.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/truncatedhistory.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Generate truncatedhistory.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir project1
+mkdir project2
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+# Make a single revision in trunk
+cd project/project1
+echo a > a
+svn add a
+svn ci -m "add a"
+cd ..
+svn up
+# Rename the project
+svn mv project1 project2/trunk
+svn ci -m "rename project1"
+cd project2/trunk
+echo b > b
+svn add b
+svn ci -m "add b"
+cd ../../..
+
+svnadmin dump testrepo > ../truncatedhistory.svndump
diff --git a/opt/hgsubversion/tests/fixtures/truncatedhistory.svndump b/opt/hgsubversion/tests/fixtures/truncatedhistory.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/truncatedhistory.svndump
@@ -0,0 +1,147 @@
+SVN-fs-dump-format-version: 2
+
+UUID: a632509c-657a-43d2-ae0b-85387476b20e
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-11T19:00:25.260082Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-11T19:00:25.322759Z
+PROPS-END
+
+Node-path: project1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: project2
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 5
+add a
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-11T19:00:26.173269Z
+PROPS-END
+
+Node-path: project1/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 117
+Content-length: 117
+
+K 7
+svn:log
+V 15
+rename project1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-11T19:00:29.152593Z
+PROPS-END
+
+Node-path: project2/trunk
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: project1
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Node-path: project1
+Node-action: delete
+
+
+Revision-number: 4
+Prop-content-length: 106
+Content-length: 106
+
+K 7
+svn:log
+V 5
+add b
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-11T19:00:30.178261Z
+PROPS-END
+
+Node-path: project2/trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
diff --git a/opt/hgsubversion/tests/fixtures/two_heads.svndump b/opt/hgsubversion/tests/fixtures/two_heads.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/two_heads.svndump
@@ -0,0 +1,212 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
+Revision-number: 4
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 13
+Make a branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:35:39.984789Z
+PROPS-END
+
+Node-path: branches/the_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
+Revision-number: 5
+Prop-content-length: 123
+Content-length: 123
+
+K 7
+svn:log
+V 23
+add delta on the branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:39:05.520779Z
+PROPS-END
+
+Node-path: branches/the_branch/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Revision-number: 6
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 19
+Add gamma on trunk.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-08T01:39:29.950892Z
+PROPS-END
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
diff --git a/opt/hgsubversion/tests/fixtures/two_revs.svndump b/opt/hgsubversion/tests/fixtures/two_revs.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/two_revs.svndump
@@ -0,0 +1,119 @@
+SVN-fs-dump-format-version: 2
+
+UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-10-07T22:49:12.059692Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 11
+Empty dirs.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T22:49:41.118037Z
+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: 108
+Content-length: 108
+
+K 7
+svn:log
+V 9
+Add alpha
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:23:02.991743Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 12
+Text-content-md5: 3c72ebf8bbd7fa88b1fdcee5398b5a17
+Content-length: 22
+
+PROPS-END
+file: alpha
+
+
+Revision-number: 3
+Prop-content-length: 107
+Content-length: 107
+
+K 7
+svn:log
+V 8
+Add beta
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2008-10-07T23:28:22.651398Z
+PROPS-END
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 13
+Text-content-md5: 981d1eb5fd0bbe05354c292105944863
+Content-length: 23
+
+PROPS-END
+Data of beta
+
+
diff --git a/opt/hgsubversion/tests/fixtures/unorderedbranch.sh b/opt/hgsubversion/tests/fixtures/unorderedbranch.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unorderedbranch.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# Generate unorderedbranch.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+echo a > a
+svn add a
+svn ci -m "add a in trunk"
+echo b > b
+echo z > z
+svn add b z
+svn ci -m "add b and z in trunk"
+svn up
+cd ../branches
+# Copy from trunk past revision. The converted used to take the last
+# trunk revision as branch parent instead of the specified one.
+svn cp -r 2 ../trunk branch
+svn cp ../trunk/z branch
+echo c > branch/c
+svn add branch/c
+svn ci -m 'branch and add c'
+cd ../..
+
+svnadmin dump testrepo > ../unorderedbranch.svndump
diff --git a/opt/hgsubversion/tests/fixtures/unorderedbranch.svndump b/opt/hgsubversion/tests/fixtures/unorderedbranch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unorderedbranch.svndump
@@ -0,0 +1,175 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 0f1e934c-2a07-4fc9-acf5-47264bd6bc41
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-12-06T16:48:56.303240Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-06T16:48:56.429957Z
+PROPS-END
+
+Node-path: branches
+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: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+add a in trunk
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-06T16:48:57.178238Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 122
+Content-length: 122
+
+K 7
+svn:log
+V 20
+add b and z in trunk
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-06T16:48:58.191379Z
+PROPS-END
+
+Node-path: trunk/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Node-path: trunk/z
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: a8a78d0ff555c931f045b6f448129846
+Content-length: 12
+
+PROPS-END
+z
+
+
+Revision-number: 4
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 16
+branch and add c
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-12-06T16:49:02.185634Z
+PROPS-END
+
+Node-path: branches/branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Node-path: branches/branch/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Node-path: branches/branch/z
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: trunk/z
+Text-copy-source-md5: a8a78d0ff555c931f045b6f448129846
+Prop-content-length: 34
+Content-length: 34
+
+K 13
+svn:mergeinfo
+V 0
+
+PROPS-END
+
+
diff --git a/opt/hgsubversion/tests/fixtures/unrelatedbranch.sh b/opt/hgsubversion/tests/fixtures/unrelatedbranch.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unrelatedbranch.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Generate unrelatedbranch.svndump
+#
+
+mkdir temp
+cd temp
+
+mkdir project-orig
+cd project-orig
+mkdir trunk
+mkdir branches
+cd ..
+
+svnadmin create testrepo
+svnurl=file://`pwd`/testrepo
+svn import project-orig $svnurl -m "init project"
+
+svn co $svnurl project
+cd project/trunk
+echo a > a
+svn add a
+svn ci -m "add a in trunk"
+cd ../branches
+# Create an unrelated branch with another file. It used to lead the converter
+# to think branch1 was a copy of trunk, even without copy information.
+mkdir branch1
+echo b > branch1/b
+svn add branch1
+svn ci -m "add b in branch1"
+# Make a real branch too for comparison
+svn cp ../trunk branch2
+echo b > branch2/b
+svn add branch2/b
+svn ci -m "add b to branch2"
+# Add a file in the branch root for fun
+echo c > c
+svn add c
+svn ci -m "add c in branches/"
+# Even update it
+echo c >> c
+svn ci -m "change c"
+cd ../..
+
+svnadmin dump testrepo > ../unrelatedbranch.svndump
diff --git a/opt/hgsubversion/tests/fixtures/unrelatedbranch.svndump b/opt/hgsubversion/tests/fixtures/unrelatedbranch.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unrelatedbranch.svndump
@@ -0,0 +1,231 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 20b2173d-0f26-4070-9891-00196be06b32
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-11-25T23:08:28.726784Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 12
+init project
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:28.798249Z
+PROPS-END
+
+Node-path: branches
+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: 116
+Content-length: 116
+
+K 7
+svn:log
+V 14
+add a in trunk
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:29.163194Z
+PROPS-END
+
+Node-path: trunk/a
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3
+Content-length: 12
+
+PROPS-END
+a
+
+
+Revision-number: 3
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 16
+add b in branch1
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:30.176855Z
+PROPS-END
+
+Node-path: branches/branch1
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: branches/branch1/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Revision-number: 4
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 16
+add b to branch2
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:32.174817Z
+PROPS-END
+
+Node-path: branches/branch2
+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/branch2/a
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk/a
+Text-copy-source-md5: 60b725f10c9c85c70d97880dfe8191b3
+
+
+Node-path: branches/branch2/b
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 3b5d5c3712955042212316173ccf37be
+Content-length: 12
+
+PROPS-END
+b
+
+
+Revision-number: 5
+Prop-content-length: 120
+Content-length: 120
+
+K 7
+svn:log
+V 18
+add c in branches/
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:33.166454Z
+PROPS-END
+
+Node-path: branches/c
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 2
+Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1
+Content-length: 12
+
+PROPS-END
+c
+
+
+Revision-number: 6
+Prop-content-length: 109
+Content-length: 109
+
+K 7
+svn:log
+V 8
+change c
+K 10
+svn:author
+V 7
+pmezard
+K 8
+svn:date
+V 27
+2008-11-25T23:08:34.144541Z
+PROPS-END
+
+Node-path: branches/c
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: 63fad9092ad37713ebe26b3193f89c41
+Content-length: 4
+
+c
+c
+
+
diff --git a/opt/hgsubversion/tests/fixtures/unusual_tags.sh b/opt/hgsubversion/tests/fixtures/unusual_tags.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unusual_tags.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+mkdir temp
+cd temp
+svnadmin create repo
+svn co file://`pwd`/repo wc
+cd wc
+mkdir branches trunk tags
+mkdir tags/versions
+mkdir tags/blah
+svn add *
+svn ci -m 'btt'
+cd trunk
+for a in alpha beta gamma delta ; do
+    echo $a > $a
+    svn add $a
+done
+svn ci -m 'Add files.'
+cd ..
+svn up
+svn cp trunk branches/dev_branch
+svn ci -m 'branch'
+cd branches/dev_branch
+svn rm delta
+echo narf > alpha
+echo iota > iota
+svn add iota
+svn ci -m 'branch changes'
+cd ../..
+svn up
+svn cp branches/dev_branch tags/versions/branch_version
+svn ci -m 'Make a tag in tags/versions from branches/dev_branch'
+svn up
+svn cp trunk tags/blah/trunktag
+svn ci -m 'Make a tag in tags/blah from trunk'
+svn up
+cd ../..
+svnadmin dump temp/repo > unusual_tags.svndump
+echo
+echo 'Complete.'
+echo 'You probably want to clean up temp now.'
+echo 'Dump in unusual_tags.svndump'
+exit 0
diff --git a/opt/hgsubversion/tests/fixtures/unusual_tags.svndump b/opt/hgsubversion/tests/fixtures/unusual_tags.svndump
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/fixtures/unusual_tags.svndump
@@ -0,0 +1,262 @@
+SVN-fs-dump-format-version: 2
+
+UUID: f9b744e7-45eb-476b-878a-5ef2bae08236
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2009-05-31T00:36:58.665604Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 102
+Content-length: 102
+
+K 7
+svn:log
+V 3
+btt
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:36:59.110531Z
+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: tags/blah
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: tags/versions
+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: 110
+Content-length: 110
+
+K 7
+svn:log
+V 10
+Add files.
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:37:00.120547Z
+PROPS-END
+
+Node-path: trunk/alpha
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 9f9f90dbe3e5ee1218c86b8839db1995
+Content-length: 16
+
+PROPS-END
+alpha
+
+
+Node-path: trunk/beta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: f0cf2a92516045024a0c99147b28f05b
+Content-length: 15
+
+PROPS-END
+beta
+
+
+Node-path: trunk/delta
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: d2840cc81bc032bd1141b56687d0f93c
+Content-length: 16
+
+PROPS-END
+delta
+
+
+Node-path: trunk/gamma
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 303febb9068384eca46b5b6516843b35
+Content-length: 16
+
+PROPS-END
+gamma
+
+
+Revision-number: 3
+Prop-content-length: 105
+Content-length: 105
+
+K 7
+svn:log
+V 6
+branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:37:03.055780Z
+PROPS-END
+
+Node-path: branches/dev_branch
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 2
+Node-copyfrom-path: trunk
+
+
+Revision-number: 4
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 14
+branch changes
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:37:04.091514Z
+PROPS-END
+
+Node-path: branches/dev_branch/alpha
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: 5e723ed52db2000686425ca28bc5ba4a
+Content-length: 5
+
+narf
+
+
+Node-path: branches/dev_branch/iota
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: ebcf3971120220589f1dfbf8d56e25b9
+Content-length: 15
+
+PROPS-END
+iota
+
+
+Node-path: branches/dev_branch/delta
+Node-action: delete
+
+
+Revision-number: 5
+Prop-content-length: 152
+Content-length: 152
+
+K 7
+svn:log
+V 52
+Make a tag in tags/versions from branches/dev_branch
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:37:07.052404Z
+PROPS-END
+
+Node-path: tags/versions/branch_version
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: branches/dev_branch
+
+
+Revision-number: 6
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 34
+Make a tag in tags/blah from trunk
+K 10
+svn:author
+V 5
+durin
+K 8
+svn:date
+V 27
+2009-05-31T00:37:10.055893Z
+PROPS-END
+
+Node-path: tags/blah/trunktag
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 5
+Node-copyfrom-path: trunk
+
+
diff --git a/opt/hgsubversion/tests/run.py b/opt/hgsubversion/tests/run.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/run.py
@@ -0,0 +1,117 @@
+import optparse
+import os
+import sys
+import unittest
+
+import test_util
+test_util.SkipTest = None
+
+def tests():
+    import test_binaryfiles
+    import test_diff
+    import test_externals
+    import test_fetch_branches
+    import test_fetch_command
+    import test_fetch_command_regexes
+    import test_fetch_exec
+    import test_fetch_mappings
+    import test_fetch_renames
+    import test_fetch_symlinks
+    import test_fetch_truncated
+    import test_pull
+    import test_push_command
+    import test_push_renames
+    import test_push_dirs
+    import test_push_eol
+    import test_rebuildmeta
+    import test_single_dir_clone
+    import test_startrev
+    import test_svnwrap
+    import test_tags
+    import test_template_keywords
+    import test_utility_commands
+    import test_unaffected_core
+    import test_urls
+
+    sys.path.append(os.path.dirname(__file__))
+    sys.path.append(os.path.join(os.path.dirname(__file__), 'comprehensive'))
+
+    import test_stupid_pull
+    import test_verify
+
+    return locals()
+
+def comprehensive(mod):
+    dir = os.path.basename(os.path.dirname(mod.__file__))
+    return dir == 'comprehensive'
+
+if __name__ == '__main__':
+    description = ("This script runs the hgsubversion tests. If no tests are "
+                   "specified, all known tests are implied.")
+    parser = optparse.OptionParser(usage="%prog [options] [TESTS ...]",
+                                   description=description)
+    parser.add_option("-A", "--all",
+                      dest="comprehensive", action="store_true", default=False,
+                      help="include slow, but comprehensive tests")
+    parser.add_option("-v", "--verbose",
+                      dest="verbose", action="store_true", default=False,
+                      help="enable verbose output")
+    parser.add_option("", "--no-demandimport",
+                      dest="demandimport", action="store_false", default=True,
+                      help="disable Mercurial demandimport loading")
+    parser.add_option("", "--bindings",
+                      dest="bindings", action="store", default=None,
+                      choices=["swig", "subvertpy"],
+                      help="test using the specified bindings (swig or "
+                      "subvertpy)")
+    parser.add_option("", "--show-stdout",
+                      dest="showstdout", action="store_true", default=False,
+                      help="show stdout (hidden by default)")
+
+    (options, args) = parser.parse_args()
+
+    if options.verbose:
+        testargs = { 'descriptions': 3, 'verbosity': 2 }
+    else:
+        testargs = {'descriptions': 2}
+
+    sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+
+    if options.demandimport:
+        from mercurial import demandimport
+        demandimport.enable()
+
+    if options.bindings:
+        os.environ['HGSUBVERSION_BINDINGS'] = options.bindings
+
+    # make sure our copy of hgsubversion gets imported by loading test_util
+    import test_util
+    test_util.TestBase
+
+    # silence output when running outside nose
+    if not options.showstdout:
+        import tempfile
+        sys.stdout = tempfile.TemporaryFile()
+
+    all = tests()
+
+    args = [i.split('.py')[0].replace('-', '_') for i in args]
+
+    if not args:
+        check = lambda x: options.comprehensive or not comprehensive(x)
+        mods = [m for (n, m) in sorted(all.iteritems()) if check(m)]
+        suite = [m.suite() for m in mods]
+    else:
+        suite = []
+        for arg in args:
+            if arg == 'test_util':
+                continue
+            elif arg not in all:
+                print >> sys.stderr, 'test module %s not available' % arg
+            else:
+                suite.append(all[arg].suite())
+
+    runner = unittest.TextTestRunner(**testargs)
+    result = runner.run(unittest.TestSuite(suite))
+    if not result.wasSuccessful():
+        sys.exit(1)
diff --git a/opt/hgsubversion/tests/test_binaryfiles.py b/opt/hgsubversion/tests/test_binaryfiles.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_binaryfiles.py
@@ -0,0 +1,16 @@
+import test_util
+
+import unittest
+
+class TestFetchBinaryFiles(test_util.TestBase):
+    def test_binaryfiles(self, stupid=False):
+        repo = self._load_fixture_and_fetch('binaryfiles.svndump', stupid=stupid)
+        self.assertEqual('cce7fe400d8d', str(repo['tip']))
+
+    def test_binaryfiles_stupid(self):
+        self.test_binaryfiles(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBinaryFiles),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_diff.py b/opt/hgsubversion/tests/test_diff.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_diff.py
@@ -0,0 +1,43 @@
+import test_util
+
+import unittest
+
+from mercurial import ui
+
+from hgsubversion import wrappers
+
+expected_diff_output = '''Index: alpha
+===================================================================
+--- alpha\t(revision 3)
++++ alpha\t(working copy)
+@@ -1,1 +1,3 @@
+-file: alpha
++alpha
++
++added line
+Index: foo
+===================================================================
+new file mode 100644
+--- foo\t(revision 0)
++++ foo\t(working copy)
+@@ -0,0 +1,1 @@
++This is missing a newline.
+\ No newline at end of file
+'''
+
+class DiffTests(test_util.TestBase):
+    def test_diff_output(self):
+        self._load_fixture_and_fetch('two_revs.svndump')
+        self.commitchanges([('foo', 'foo', 'This is missing a newline.'),
+                            ('alpha', 'alpha', 'alpha\n\nadded line\n'),
+                            ])
+        u = ui.ui()
+        u.pushbuffer()
+        wrappers.diff(lambda x,y,z: None, u, self.repo, svn=True)
+        self.assertEqual(u.popbuffer(), expected_diff_output)
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(DiffTests),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_externals.py b/opt/hgsubversion/tests/test_externals.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_externals.py
@@ -0,0 +1,414 @@
+import test_util
+
+import os, unittest, sys
+
+from mercurial import commands
+from mercurial import util as hgutil
+try:
+    from mercurial import subrepo
+    # require svnsubrepo and hg >= 1.7.1
+    subrepo.svnsubrepo
+    hgutil.checknlink
+except (ImportError, AttributeError), e:
+    print >>sys.stderr, 'test_externals: skipping .hgsub tests'
+    subrepo = None
+
+from hgsubversion import svnexternals
+
+class TestFetchExternals(test_util.TestBase):
+    def test_externalsfile(self):
+        f = svnexternals.externalsfile()
+        f['t1'] = 'dir1 -r10 svn://foobar'
+        f['t 2'] = 'dir2 -r10 svn://foobar'
+        f['t3'] = ['dir31 -r10 svn://foobar', 'dir32 -r10 svn://foobar']
+
+        refext = """[t 2]
+ dir2 -r10 svn://foobar
+[t1]
+ dir1 -r10 svn://foobar
+[t3]
+ dir31 -r10 svn://foobar
+ dir32 -r10 svn://foobar
+"""
+        value = f.write()
+        self.assertEqual(refext, value)
+
+        f2 = svnexternals.externalsfile()
+        f2.read(value)
+        self.assertEqual(sorted(f), sorted(f2))
+        for t in f:
+            self.assertEqual(f[t], f2[t])
+
+    def test_parsedefinitions(self):
+        # Taken from svn book
+        samples = [
+            ('third-party/sounds             http://svn.example.com/repos/sounds',
+             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds', None,
+              'third-party/sounds             http://svn.example.com/repos/sounds')),
+
+            ('third-party/skins -r148        http://svn.example.com/skinproj',
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None,
+              'third-party/skins -r{REV}        http://svn.example.com/skinproj')),
+
+            ('third-party/skins -r 148        http://svn.example.com/skinproj',
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None,
+              'third-party/skins -r {REV}        http://svn.example.com/skinproj')),
+
+            ('http://svn.example.com/repos/sounds third-party/sounds',
+             ('third-party/sounds', None, 'http://svn.example.com/repos/sounds', None,
+              'http://svn.example.com/repos/sounds third-party/sounds')),
+
+            ('-r148 http://svn.example.com/skinproj third-party/skins',
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None,
+              '-r{REV} http://svn.example.com/skinproj third-party/skins')),
+
+            ('-r 148 http://svn.example.com/skinproj third-party/skins',
+             ('third-party/skins', '148', 'http://svn.example.com/skinproj', None,
+              '-r {REV} http://svn.example.com/skinproj third-party/skins')),
+
+            ('http://svn.example.com/skin-maker@21 third-party/skins/toolkit',
+             ('third-party/skins/toolkit', None, 'http://svn.example.com/skin-maker', '21',
+              'http://svn.example.com/skin-maker@21 third-party/skins/toolkit')),
+            ]
+
+        for line, expected in samples:
+            self.assertEqual(expected, svnexternals.parsedefinition(line))
+
+    def test_externals(self, stupid=False):
+        repo = self._load_fixture_and_fetch('externals.svndump', stupid=stupid)
+
+        ref0 = """[.]
+ ^/externals/project1 deps/project1
+"""
+        self.assertMultiLineEqual(ref0, repo[0]['.hgsvnexternals'].data())
+        ref1 = """\
+[.]
+ # A comment, then an empty line, then a blank line
+ 
+ ^/externals/project1 deps/project1
+     
+ -r2 ^/externals/project2@2 deps/project2
+"""
+        self.assertMultiLineEqual(ref1, repo[1]['.hgsvnexternals'].data())
+
+        ref2 = """[.]
+ -r2 ^/externals/project2@2 deps/project2
+[subdir]
+ ^/externals/project1 deps/project1
+[subdir2]
+ ^/externals/project1 deps/project1
+"""
+        actual = repo[2]['.hgsvnexternals'].data()
+        self.assertEqual(ref2, actual)
+
+        ref3 = """[.]
+ -r2 ^/externals/project2@2 deps/project2
+[subdir]
+ ^/externals/project1 deps/project1
+"""
+        self.assertEqual(ref3, repo[3]['.hgsvnexternals'].data())
+
+        ref4 = """[subdir]
+ ^/externals/project1 deps/project1
+"""
+        self.assertEqual(ref4, repo[4]['.hgsvnexternals'].data())
+
+        ref5 = """[.]
+ -r2 ^/externals/project2@2 deps/project2
+[subdir2]
+ ^/externals/project1 deps/project1
+"""
+        self.assertEqual(ref5, repo[5]['.hgsvnexternals'].data())
+
+        ref6 = """[.]
+ -r2 ^/externals/project2@2 deps/project2
+"""
+        self.assertEqual(ref6, repo[6]['.hgsvnexternals'].data())
+
+    def test_externals_stupid(self):
+        self.test_externals(True)
+
+    def test_updateexternals(self):
+        def checkdeps(deps, nodeps, repo, rev=None):
+            svnexternals.updateexternals(ui, [rev], repo)
+            for d in deps:
+                p = os.path.join(repo.root, d)
+                self.assertTrue(os.path.isdir(p),
+                                'missing: %s@%r' % (d, rev))
+            for d in nodeps:
+                p = os.path.join(repo.root, d)
+                self.assertTrue(not os.path.isdir(p),
+                                'unexpected: %s@%r' % (d, rev))
+
+        ui = self.ui()
+        repo = self._load_fixture_and_fetch('externals.svndump', stupid=0)
+        commands.update(ui, repo)
+        checkdeps(['deps/project1'], [], repo, 0)
+        checkdeps(['deps/project1', 'deps/project2'], [], repo, 1)
+        checkdeps(['subdir/deps/project1', 'subdir2/deps/project1',
+                   'deps/project2'],
+                  ['deps/project1'], repo, 2)
+        checkdeps(['subdir/deps/project1', 'deps/project2'],
+                  ['subdir2/deps/project1'], repo, 3)
+        checkdeps(['subdir/deps/project1'], ['deps/project2'], repo, 4)
+
+    def test_hgsub(self, stupid=False):
+        if subrepo is None:
+            return
+        repo = self._load_fixture_and_fetch('externals.svndump',
+                                            externals='subrepos',
+                                            stupid=stupid)
+        self.assertEqual("""\
+deps/project1 = [hgsubversion] :^/externals/project1 deps/project1
+""", repo[0]['.hgsub'].data())
+        self.assertEqual("""\
+HEAD deps/project1
+""", repo[0]['.hgsubstate'].data())
+
+        self.assertEqual("""\
+deps/project1 = [hgsubversion] :^/externals/project1 deps/project1
+deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2
+""", repo[1]['.hgsub'].data())
+        self.assertEqual("""\
+HEAD deps/project1
+2 deps/project2
+""", repo[1]['.hgsubstate'].data())
+
+        self.assertEqual("""\
+deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2
+subdir/deps/project1 = [hgsubversion] subdir:^/externals/project1 deps/project1
+subdir2/deps/project1 = [hgsubversion] subdir2:^/externals/project1 deps/project1
+""", repo[2]['.hgsub'].data())
+        self.assertEqual("""\
+2 deps/project2
+HEAD subdir/deps/project1
+HEAD subdir2/deps/project1
+""", repo[2]['.hgsubstate'].data())
+
+        self.assertMultiLineEqual("""\
+deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2
+subdir/deps/project1 = [hgsubversion] subdir:^/externals/project1 deps/project1
+""", repo[3]['.hgsub'].data())
+        self.assertEqual("""\
+2 deps/project2
+HEAD subdir/deps/project1
+""", repo[3]['.hgsubstate'].data())
+
+        self.assertEqual("""\
+subdir/deps/project1 = [hgsubversion] subdir:^/externals/project1 deps/project1
+""", repo[4]['.hgsub'].data())
+        self.assertEqual("""\
+HEAD subdir/deps/project1
+""", repo[4]['.hgsubstate'].data())
+
+        self.assertEqual("""\
+deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2
+subdir2/deps/project1 = [hgsubversion] subdir2:^/externals/project1 deps/project1
+""", repo[5]['.hgsub'].data())
+        self.assertEqual("""\
+2 deps/project2
+HEAD subdir2/deps/project1
+""", repo[5]['.hgsubstate'].data())
+
+        self.assertEqual("""\
+deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2
+""", repo[6]['.hgsub'].data())
+        self.assertEqual("""\
+2 deps/project2
+""", repo[6]['.hgsubstate'].data())
+
+    def test_hgsub_stupid(self):
+        self.test_hgsub(True)
+
+    def test_ignore(self):
+        repo = self._load_fixture_and_fetch('externals.svndump',
+                                            externals='ignore')
+        for rev in repo:
+            ctx = repo[rev]
+            self.assertTrue('.hgsvnexternals' not in ctx)
+            self.assertTrue('.hgsub' not in ctx)
+            self.assertTrue('.hgsubstate' not in ctx)
+
+    def test_updatehgsub(self):
+        def checkdeps(ui, repo, rev, deps, nodeps):
+            commands.update(ui, repo, node=str(rev))
+            for d in deps:
+                p = os.path.join(repo.root, d)
+                self.assertTrue(os.path.isdir(p),
+                                'missing: %s@%r' % (d, repo[None].rev()))
+            for d in nodeps:
+                p = os.path.join(repo.root, d)
+                self.assertTrue(not os.path.isdir(p),
+                                'unexpected: %s@%r' % (d, repo[None].rev()))
+
+        if subrepo is None:
+            return
+
+        ui = self.ui()
+        repo = self._load_fixture_and_fetch('externals.svndump',
+                                            stupid=0, externals='subrepos')
+        checkdeps(ui, repo, 0, ['deps/project1'], [])
+        checkdeps(ui, repo, 1, ['deps/project1', 'deps/project2'], [])
+        checkdeps(ui, repo, 2, ['subdir/deps/project1', 'subdir2/deps/project1',
+                   'deps/project2'],
+                  ['deps/project1'])
+        checkdeps(ui, repo, 3, ['subdir/deps/project1', 'deps/project2'],
+                  ['subdir2/deps/project1'])
+        checkdeps(ui, repo, 4, ['subdir/deps/project1'], ['deps/project2'])
+
+        # Test update --clean, used to crash
+        repo.wwrite('subdir/deps/project1/a', 'foobar', '')
+        commands.update(ui, repo, node='4', clean=True)
+
+    def test_mergeexternals(self, stupid=False):
+        if subrepo is None:
+            return
+        repo = self._load_fixture_and_fetch('mergeexternals.svndump',
+                                            externals='subrepos',
+                                            stupid=stupid)
+        # Check merged directories externals are fine
+        self.assertEqual("""\
+d1/ext = [hgsubversion] d1:^/trunk/common/ext ext
+d2/ext = [hgsubversion] d2:^/trunk/common/ext ext
+d3/ext3 = [hgsubversion] d3:^/trunk/common/ext ext3
+""", repo['tip']['.hgsub'].data())
+
+    def test_mergeexternals_stupid(self):
+        self.test_mergeexternals(True)
+
+class TestPushExternals(test_util.TestBase):
+    def test_push_externals(self, stupid=False):
+        test_util.load_fixture_and_fetch('pushexternals.svndump',
+                                         self.repo_path,
+                                         self.wc_path)
+        # Add a new reference on an existing and non-existing directory
+        changes = [
+            ('.hgsvnexternals', '.hgsvnexternals',
+             """[dir]
+ ../externals/project2 deps/project2
+[subdir1]
+ ../externals/project1 deps/project1
+[subdir2]
+ ../externals/project2 deps/project2
+"""),
+            ('subdir1/a', 'subdir1/a', 'a'),
+            ('subdir2/a', 'subdir2/a', 'a'),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+        # Remove all references from one directory, add a new one
+        # to the other (test multiline entries)
+        changes = [
+            ('.hgsvnexternals', '.hgsvnexternals',
+             """[subdir1]
+ ../externals/project1 deps/project1
+ ../externals/project2 deps/project2
+"""),
+            # This removal used to trigger the parent directory removal
+            ('subdir1/a', None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+        # Check subdir2/a is still there even if the externals were removed
+        self.assertTrue('subdir2/a' in self.repo['tip'])
+        self.assertTrue('subdir1/a' not in self.repo['tip'])
+
+        # Test externals removal
+        changes = [
+            ('.hgsvnexternals', None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+    def test_push_externals_stupid(self):
+        self.test_push_externals(True)
+
+    def test_push_hgsub(self, stupid=False):
+        if subrepo is None:
+            return
+
+        test_util.load_fixture_and_fetch('pushexternals.svndump',
+                                         self.repo_path,
+                                         self.wc_path,
+                                         externals='subrepos')
+        # Add a new reference on an existing and non-existing directory
+        changes = [
+            ('.hgsub', '.hgsub', """\
+dir/deps/project2 = [hgsubversion] dir:^/externals/project2 deps/project2
+subdir1/deps/project1 = [hgsubversion] subdir1:^/externals/project1 deps/project1
+subdir2/deps/project2 = [hgsubversion] subdir2:^/externals/project2 deps/project2
+"""),
+            ('.hgsubstate', '.hgsubstate', """\
+HEAD dir/deps/project2
+HEAD subdir1/deps/project1
+HEAD subdir2/deps/project2
+"""),
+            ('subdir1/a', 'subdir1/a', 'a'),
+            ('subdir2/a', 'subdir2/a', 'a'),
+            ]
+        self.svnco('externals/project2', '2', 'dir/deps/project2')
+        self.svnco('externals/project1', '2', 'subdir1/deps/project1')
+        self.svnco('externals/project2', '2', 'subdir2/deps/project2')
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+        # Check .hgsub and .hgsubstate were not pushed
+        self.assertEqual(['dir', 'subdir1', 'subdir1/a','subdir2',
+                          'subdir2/a'], self.svnls('trunk'))
+
+        # Remove all references from one directory, add a new one
+        # to the other (test multiline entries)
+        changes = [
+            ('.hgsub', '.hgsub', """\
+subdir1/deps/project1 = [hgsubversion] subdir1:^/externals/project1 deps/project1
+subdir1/deps/project2 = [hgsubversion] subdir1:^/externals/project2 deps/project2
+"""),
+            ('.hgsubstate', '.hgsubstate', """\
+HEAD subdir1/deps/project1
+HEAD subdir1/deps/project2
+"""),
+            # This removal used to trigger the parent directory removal
+            ('subdir1/a', None, None),
+            ]
+        self.svnco('externals/project1', '2', 'subdir1/deps/project1')
+        self.svnco('externals/project2', '2', 'subdir1/deps/project2')
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+        # Check subdir2/a is still there even if the externals were removed
+        self.assertTrue('subdir2/a' in self.repo['tip'])
+        self.assertTrue('subdir1/a' not in self.repo['tip'])
+
+        # Move the externals so they are defined on the base directory,
+        # this used to cause full branch removal when deleting the .hgsub
+        changes = [
+            ('.hgsub', '.hgsub', """\
+subdir1/deps/project1 = [hgsubversion] :^/externals/project1 subdir1/deps/project1
+"""),
+            ('.hgsubstate', '.hgsubstate', """\
+HEAD subdir1/deps/project1
+"""),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+        # Test externals removal
+        changes = [
+            ('.hgsub', None, None),
+            ('.hgsubstate', None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExternals),
+           unittest.TestLoader().loadTestsFromTestCase(TestPushExternals),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_branches.py b/opt/hgsubversion/tests/test_fetch_branches.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_branches.py
@@ -0,0 +1,168 @@
+import test_util
+
+import unittest
+
+from mercurial import hg
+from mercurial import node
+from mercurial import util as hgutil
+
+class TestFetchBranches(test_util.TestBase):
+    def _load_fixture_and_fetch(self, fixture_name, stupid, noupdate=True,
+                                subdir=''):
+        return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
+                                                self.wc_path, stupid=stupid,
+                                                noupdate=noupdate, subdir=subdir)
+
+    def _load_fixture_and_fetch_with_anchor(self, fixture_name, anchor):
+        test_util.load_svndump_fixture(self.repo_path, fixture_name)
+        source = '%s#%s' % (test_util.fileurl(self.repo_path), anchor)
+        test_util.hgclone(self.ui(), source, self.wc_path)
+        return hg.repository(self.ui(), self.wc_path)
+
+    def branches(self, repo):
+        hctxs = [repo[hn] for hn in repo.heads()]
+        openbranches = set(ctx.branch() for ctx in hctxs if
+                           ctx.extra().get('close', None) != '1')
+        closedbranches = set(ctx.branch() for ctx in hctxs if
+                             ctx.extra().get('close', None) == '1')
+        return sorted(openbranches), sorted(closedbranches)
+
+    def openbranches(self, repo):
+        return self.branches(repo)[0]
+
+    def test_rename_branch_parent(self, stupid=False):
+        repo = self._load_fixture_and_fetch('rename_branch_parent_dir.svndump', stupid)
+        heads = [repo[n] for n in repo.heads()]
+        heads = dict([(ctx.branch(), ctx) for ctx in heads])
+        # Let these tests disabled yet as the fix is not obvious
+        self.assertEqual(['dev_branch'], self.openbranches(repo))
+
+    def test_rename_branch_parent_stupid(self):
+        self.test_rename_branch_parent(stupid=True)
+
+    def test_unrelatedbranch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('unrelatedbranch.svndump', stupid)
+        heads = [repo[n] for n in repo.heads()]
+        heads = dict([(ctx.branch(), ctx) for ctx in heads])
+        # Let these tests disabled yet as the fix is not obvious
+        self.assertEqual(heads['branch1'].manifest().keys(), ['b'])
+        self.assertEqual(heads['branch2'].manifest().keys(), ['a', 'b'])
+
+    def test_unrelatedbranch_stupid(self):
+        self.test_unrelatedbranch(True)
+
+    def test_unorderedbranch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('unorderedbranch.svndump', stupid)
+        r = repo['branch']
+        self.assertEqual(0, r.parents()[0].rev())
+        self.assertEqual(['a', 'c', 'z'], sorted(r.manifest()))
+
+    def test_unorderedbranch_stupid(self):
+        self.test_unorderedbranch(True)
+
+    def test_renamed_branch_to_trunk(self, stupid=False):
+        repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump',
+                                            stupid)
+        self.assertEqual(repo['default'].parents()[0].branch(), 'dev_branch')
+        self.assert_('iota' in repo['default'])
+        self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default')
+        self.assert_('iota' not in repo['old_trunk'])
+        expected = ['default', 'old_trunk']
+        self.assertEqual(self.openbranches(repo), expected)
+
+    def test_renamed_branch_to_trunk_stupid(self):
+        self.test_renamed_branch_to_trunk(stupid=True)
+
+    def test_replace_trunk_with_branch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('replace_trunk_with_branch.svndump',
+                                            stupid)
+        self.assertEqual(repo['default'].parents()[0].branch(), 'test')
+        self.assertEqual(repo['tip'].branch(), 'default')
+        self.assertEqual(repo['tip'].extra().get('close'), '1')
+        self.assertEqual(self.openbranches(repo), ['default'])
+
+    def test_copybeforeclose(self, stupid=False):
+        repo = self._load_fixture_and_fetch('copybeforeclose.svndump', stupid)
+        self.assertEqual(repo['tip'].branch(), 'test')
+        self.assertEqual(repo['test'].extra().get('close'), '1')
+        self.assertEqual(repo['test']['b'].data(), 'a\n')
+
+    def test_copybeforeclose_stupid(self):
+        self.test_copybeforeclose(True)
+
+    def test_replace_trunk_with_branch_stupid(self):
+        self.test_replace_trunk_with_branch(stupid=True)
+
+    def test_branch_create_with_dir_delete_works(self, stupid=False):
+        repo = self._load_fixture_and_fetch('branch_create_with_dir_delete.svndump',
+                                            stupid)
+        self.assertEqual(repo['tip'].manifest().keys(),
+                         ['alpha', 'beta', 'iota', 'gamma', ])
+
+    def test_branch_tip_update_to_default(self, stupid=False):
+        repo = self._load_fixture_and_fetch('unorderedbranch.svndump',
+                                            stupid, noupdate=False)
+        self.assertEqual(repo[None].branch(), 'default')
+        self.assertTrue('tip' not in repo[None].tags())
+
+    def test_branch_tip_update_to_default_stupid(self):
+        self.test_branch_tip_update_to_default(True)
+
+    def test_branch_pull_anchor(self):
+        self.assertRaises(hgutil.Abort,
+                          self._load_fixture_and_fetch_with_anchor,
+                          'unorderedbranch.svndump', 'NaN')
+        repo = self._load_fixture_and_fetch_with_anchor(
+            'unorderedbranch.svndump', '4')
+        self.assertTrue('c' not in repo.branchtags())
+
+    def test_branches_weird_moves(self, stupid=False):
+        repo = self._load_fixture_and_fetch('renamedproject.svndump', stupid,
+                                            subdir='project')
+        heads = [repo[n] for n in repo.heads()]
+        heads = dict((ctx.branch(), ctx) for ctx in heads)
+        mdefault = sorted(heads['default'].manifest().keys())
+        mbranch = sorted(heads['branch'].manifest().keys())
+        self.assertEqual(mdefault, ['a', 'b', 'd/a'])
+        self.assertEqual(mbranch, ['a'])
+
+    def test_branches_weird_moves_stupid(self):
+        self.test_branches_weird_moves(True)
+
+    def test_branch_delete_parent_dir(self, stupid=False):
+        repo = self._load_fixture_and_fetch('branch_delete_parent_dir.svndump',
+                                            stupid)
+        openb, closedb = self.branches(repo)
+        self.assertEqual(openb, [])
+        self.assertEqual(closedb, ['dev_branch'])
+        self.assertEqual(list(repo['dev_branch']), ['foo'])
+
+    def test_replace_branch_with_branch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('replace_branch_with_branch.svndump',
+                                            stupid)
+        self.assertEqual(7, len(repo))
+        # tip is former topological branch1 being closed
+        ctx = repo['tip']
+        self.assertEqual('1', ctx.extra().get('close', '0'))
+        self.assertEqual('branch1', ctx.branch())
+        # r5 is where the replacement takes place
+        ctx = repo[5]
+        self.assertEqual(set(['a', 'c', 'dir/e', 'dir2/e', 'f', 'g']), set(ctx))
+        self.assertEqual('0', ctx.extra().get('close', '0'))
+        self.assertEqual('branch1', ctx.branch())
+        self.assertEqual('c\n', ctx['c'].data())
+        self.assertEqual('d\n', ctx['a'].data())
+        self.assertEqual('e\n', ctx['dir/e'].data())
+        self.assertEqual('e\n', ctx['dir2/e'].data())
+        self.assertEqual('f\n', ctx['f'].data())
+        self.assertEqual('g\n', ctx['g'].data())
+        for f in ctx:
+            self.assertTrue(not ctx[f].renamed())
+
+    def test_replace_branch_with_branch_stupid(self, stupid=False):
+        self.test_replace_branch_with_branch(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_command.py b/opt/hgsubversion/tests/test_fetch_command.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_command.py
@@ -0,0 +1,230 @@
+import test_util
+
+import os
+import unittest
+import urllib
+
+from mercurial import commands
+from mercurial import hg
+from mercurial import node
+from mercurial import ui
+
+class TestBasicRepoLayout(test_util.TestBase):
+
+    def test_no_dates(self):
+        repo = self._load_fixture_and_fetch('test_no_dates.svndump')
+        local_epoch = repo[0].date()
+        self.assertEqual(local_epoch[0], local_epoch[1])
+        self.assertEqual(repo[1].date(), repo[2].date())
+
+    def test_fresh_fetch_single_rev(self):
+        repo = self._load_fixture_and_fetch('single_rev.svndump')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        self.assertEqual(repo['tip'].extra()['convert_revision'],
+                         'svn:df2126f7-00ab-4d49-b42c-7e981dde0bcf/trunk@2')
+        self.assertEqual(repo['tip'], repo[0])
+
+    def test_fresh_fetch_two_revs(self):
+        repo = self._load_fixture_and_fetch('two_revs.svndump')
+        self.assertEqual(node.hex(repo[0].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         'c95251e0dd04697deee99b79cc407d7db76e6a5f')
+        self.assertEqual(repo['tip'], repo[1])
+
+    def test_branches(self):
+        repo = self._load_fixture_and_fetch('simple_branch.svndump')
+        self.assertEqual(node.hex(repo[0].node()),
+                         'a1ff9f5d90852ce7f8e607fa144066b0a06bdc57')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '545e36ed13615e39c5c8fb0c325109d8cb8e00c3')
+        self.assertEqual(len(repo['tip'].parents()), 1)
+        self.assertEqual(repo['tip'].parents()[0], repo['default'])
+        self.assertEqual(repo['tip'].extra()['convert_revision'],
+                         'svn:3cd547df-371e-4add-bccf-aba732a2baf5/branches/the_branch@4')
+        self.assertEqual(repo['default'].extra()['convert_revision'],
+                         'svn:3cd547df-371e-4add-bccf-aba732a2baf5/trunk@3')
+        self.assertEqual(len(repo.heads()), 1)
+
+    def test_two_branches_with_heads(self):
+        repo = self._load_fixture_and_fetch('two_heads.svndump')
+        self.assertEqual(node.hex(repo[0].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '1083037b18d85cd84fa211c5adbaeff0fea2cd9f')
+        self.assertEqual(node.hex(repo['the_branch'].node()),
+                         '4e256962fc5df545e2e0a51d0d1dc61c469127e6')
+        self.assertEqual(node.hex(repo['the_branch'].parents()[0].node()),
+                         'f1ff5b860f5dbb9a59ad0921a79da77f10f25109')
+        self.assertEqual(len(repo['tip'].parents()), 1)
+        self.assertEqual(repo['tip'], repo['default'])
+        self.assertEqual(len(repo.heads()), 2)
+
+    def test_many_special_cases_replay(self):
+        repo = self._load_fixture_and_fetch('many_special_cases.svndump')
+        self._many_special_cases_checks(repo)
+
+
+    def test_many_special_cases_diff(self):
+        repo = self._load_fixture_and_fetch('many_special_cases.svndump',
+                                            stupid=True)
+        self._many_special_cases_checks(repo)
+
+    def _many_special_cases_checks(self, repo):
+        self.assertEquals(node.hex(repo[0].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        # two possible hashes for bw compat to hg < 1.5, since hg 1.5
+        # sorts entries in extra()
+        self.assertTrue(node.hex(repo['tip'].node()) in
+                         ('e92012d8c170a0236c84166167f149c2e28548c6',
+                         'b7bdc73041b1852563deb1ef3f4153c2fe4484f2'))
+        self.assertEqual(node.hex(repo['the_branch'].node()),
+                         '4e256962fc5df545e2e0a51d0d1dc61c469127e6')
+        self.assertEqual(node.hex(repo['the_branch'].parents()[0].node()),
+                         'f1ff5b860f5dbb9a59ad0921a79da77f10f25109')
+        self.assertEqual(len(repo['tip'].parents()), 1)
+        self.assertEqual(repo['tip'], repo['default'])
+        self.assertEqual(len(repo.heads()), 2)
+
+    def test_file_mixed_with_branches(self):
+        repo = self._load_fixture_and_fetch('file_mixed_with_branches.svndump')
+        self.assertEqual(node.hex(repo['default'].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        assert 'README' not in repo
+        assert '../branches' not in repo
+
+    def test_files_copied_from_outside_btt(self):
+        repo = self._load_fixture_and_fetch(
+            'test_files_copied_from_outside_btt.svndump')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '3c78170e30ddd35f2c32faa0d8646ab75bba4f73')
+        self.assertEqual(len(repo.changelog), 2)
+
+    def test_file_renamed_in_from_outside_btt(self):
+        repo = self._load_fixture_and_fetch(
+                    'file_renamed_in_from_outside_btt.svndump')
+        self.assert_('LICENSE.file' in repo['default'])
+
+    def test_renamed_dir_in_from_outside_btt_not_repo_root(self):
+        repo = self._load_fixture_and_fetch(
+                    'fetch_missing_files_subdir.svndump', subdir='foo')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '269dcdd4361b2847e9f4288d4500e55d35df1f52')
+        self.assert_('bar/alpha' in repo['tip'])
+        self.assert_('foo' in repo['tip'])
+        self.assert_('bar/alpha' not in repo['tip'].parents()[0])
+        self.assert_('foo' in repo['tip'].parents()[0])
+
+    def test_oldest_not_trunk_and_tag_vendor_branch(self):
+        repo = self._load_fixture_and_fetch(
+            'tagged_vendor_and_oldest_not_trunk.svndump')
+        self.assertEqual(node.hex(repo['oldest'].node()),
+                         '926671740dec045077ab20f110c1595f935334fa')
+        self.assertEqual(repo['tip'].parents()[0].parents()[0],
+                         repo['oldest'])
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '1a6c3f30911d57abb67c257ec0df3e7bc44786f7')
+
+    def test_propedit_with_nothing_else(self, stupid=False):
+        repo = self._load_fixture_and_fetch('branch_prop_edit.svndump',
+                                            stupid=stupid)
+        self.assertEqual(repo['tip'].description(), 'Commit bogus propchange.')
+        self.assertEqual(repo['tip'].branch(), 'dev_branch')
+
+    def test_propedit_with_nothing_else_stupid(self):
+        self.test_propedit_with_nothing_else(stupid=True)
+
+    def test_entry_deletion(self, stupid=False):
+        repo = self._load_fixture_and_fetch('delentries.svndump',
+                                            stupid=stupid)
+        files = list(sorted(repo['tip'].manifest()))
+        self.assertEqual(['aa', 'd1/c', 'd1/d2prefix'], files)
+
+    def test_entry_deletion_stupid(self):
+        self.test_entry_deletion(stupid=True)
+
+    def test_fetch_when_trunk_has_no_files(self, stupid=False):
+        repo = self._load_fixture_and_fetch('file_not_in_trunk_root.svndump', stupid=stupid)
+        self.assertEqual(repo['tip'].branch(), 'default')
+
+    def test_fetch_when_trunk_has_no_files_stupid(self):
+        self.test_fetch_when_trunk_has_no_files(stupid=True)
+
+    def test_path_quoting(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'non_ascii_path_1.svndump')
+        subdir = '/b\xC3\xB8b'
+        quoted_subdir = urllib.quote(subdir)
+
+        repo_url = test_util.fileurl(self.repo_path)
+        wc_path = self.wc_path
+        wc2_path = wc_path + '-2'
+
+        ui = self.ui(stupid=stupid)
+
+        commands.clone(ui, repo_url + subdir, wc_path)
+        commands.clone(ui, repo_url + quoted_subdir, wc2_path)
+        repo  = hg.repository(ui, wc_path)
+        repo2 = hg.repository(ui, wc2_path)
+
+        self.assertEqual(repo['tip'].extra()['convert_revision'],
+                         repo2['tip'].extra()['convert_revision'])
+        self.assertEqual(len(repo), len(repo2))
+
+        for r in repo:
+            self.assertEqual(repo[r].hex(), repo2[r].hex())
+
+    def test_path_quoting_stupid(self):
+        self.test_path_quoting(True)
+
+    def test_identical_fixtures(self):
+        '''ensure that the non_ascii_path_N fixtures are identical'''
+        fixturepaths = [
+            os.path.join(test_util.FIXTURES, 'non_ascii_path_1.svndump'),
+            os.path.join(test_util.FIXTURES, 'non_ascii_path_2.svndump'),
+        ]
+        self.assertMultiLineEqual(open(fixturepaths[0]).read(),
+                                  open(fixturepaths[1]).read())
+
+class TestStupidPull(test_util.TestBase):
+    def test_stupid(self):
+        repo = test_util.load_fixture_and_fetch('two_heads.svndump',
+                                                self.repo_path,
+                                                self.wc_path,
+                                                True)
+        self.assertEqual(node.hex(repo[0].node()),
+                         '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '1083037b18d85cd84fa211c5adbaeff0fea2cd9f')
+        self.assertEqual(node.hex(repo['the_branch'].node()),
+                         '4e256962fc5df545e2e0a51d0d1dc61c469127e6')
+        self.assertEqual(repo['the_branch'].extra()['convert_revision'],
+                         'svn:df2126f7-00ab-4d49-b42c-7e981dde0bcf/branches/the_branch@5')
+        self.assertEqual(node.hex(repo['the_branch'].parents()[0].node()),
+                         'f1ff5b860f5dbb9a59ad0921a79da77f10f25109')
+        self.assertEqual(len(repo['tip'].parents()), 1)
+        self.assertEqual(repo['default'].extra()['convert_revision'],
+                         'svn:df2126f7-00ab-4d49-b42c-7e981dde0bcf/trunk@6')
+        self.assertEqual(repo['tip'], repo['default'])
+        self.assertEqual(len(repo.heads()), 2)
+
+    def test_oldest_not_trunk_and_tag_vendor_branch(self):
+        repo = test_util.load_fixture_and_fetch(
+            'tagged_vendor_and_oldest_not_trunk.svndump',
+            self.repo_path,
+            self.wc_path,
+            True)
+        repo = hg.repository(ui.ui(), self.wc_path)
+        self.assertEqual(node.hex(repo['oldest'].node()),
+                         '926671740dec045077ab20f110c1595f935334fa')
+        self.assertEqual(repo['tip'].parents()[0].parents()[0],
+                         repo['oldest'])
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '1a6c3f30911d57abb67c257ec0df3e7bc44786f7')
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
+           unittest.TestLoader().loadTestsFromTestCase(TestStupidPull),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_command_regexes.py b/opt/hgsubversion/tests/test_fetch_command_regexes.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_command_regexes.py
@@ -0,0 +1,75 @@
+import unittest
+
+from hgsubversion import stupid
+
+
+two_empties = """Index: __init__.py
+===================================================================
+Index: bar/__init__.py
+===================================================================
+Index: bar/test_muhaha.py
+===================================================================
+--- bar/test_muhaha.py	(revision 0)
++++ bar/test_muhaha.py	(revision 1)
+@@ -0,0 +1,2 @@
++
++blah blah blah, I'm a fake patch
+\ No newline at end of file
+"""
+
+binary_delta = """Index: trunk/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+
+Property changes on: trunk/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc
+___________________________________________________________________
+Added: svn:mime-type
+   + application/octet-stream
+
+Index: trunk/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures.rst
+===================================================================
+"""
+
+special_delta = """Index: delta
+===================================================================
+--- delta(revision 0)
++++ delta(revision 9)
+@@ -0,0 +1 @@
++link alpha
+\ No newline at end of file
+
+Property changes on: delta
+___________________________________________________________________
+Name: svn:special
+   + *
+
+"""
+
+class RegexTests(unittest.TestCase):
+    def test_empty_file_re(self):
+        matches = stupid.empty_file_patch_wont_make_re.findall(two_empties)
+        assert sorted(matches) == ['__init__.py', 'bar/__init__.py']
+
+    def test_any_matches_just_one(self):
+        pat = '''Index: trunk/django/contrib/admin/urls/__init__.py
+===================================================================
+'''
+        matches = stupid.any_file_re.findall(pat)
+        assert len(matches) == 1
+
+    def test_special_re(self):
+        matches = stupid.property_special_set_re.findall(special_delta)
+        assert len(matches) == 1
+
+    def test_any_file_re(self):
+        matches = stupid.any_file_re.findall(two_empties)
+        assert sorted(matches) == ['__init__.py', 'bar/__init__.py',
+                                   'bar/test_muhaha.py']
+
+    def test_binary_file_re(self):
+        matches = stupid.binary_file_re.findall(binary_delta)
+        assert matches == ['trunk/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc']
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(RegexTests)
diff --git a/opt/hgsubversion/tests/test_fetch_exec.py b/opt/hgsubversion/tests/test_fetch_exec.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_exec.py
@@ -0,0 +1,38 @@
+import test_util
+
+import unittest
+
+from mercurial import node
+
+class TestFetchExec(test_util.TestBase):
+    def _load_fixture_and_fetch(self, fixture_name, stupid):
+        return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
+                                                self.wc_path, stupid=stupid)
+
+    def assertexec(self, ctx, files, isexec=True):
+        for f in files:
+            self.assertEqual(isexec, 'x' in ctx[f].flags())
+
+    def test_exec(self, stupid=False):
+        repo = self._load_fixture_and_fetch('executebit.svndump', stupid)
+        self.assertexec(repo[0], ['text1', 'binary1', 'empty1'], True)
+        self.assertexec(repo[0], ['text2', 'binary2', 'empty2'], False)
+        self.assertexec(repo[1], ['text1', 'binary1', 'empty1'], False)
+        self.assertexec(repo[1], ['text2', 'binary2', 'empty2'], True)
+
+    def test_exec_stupid(self):
+        self.test_exec(True)
+
+    def test_empty_prop_val_executable(self, stupid=False):
+        repo = self._load_fixture_and_fetch('executable_file_empty_prop.svndump', stupid)
+        self.assertEqual(node.hex(repo['tip'].node()),
+                         '08e6b380bf291b361a418203a1cb9427213cd1fd')
+        self.assertEqual(repo['tip']['foo'].flags(), 'x')
+
+    def test_empty_prop_val_executable_stupid(self):
+        self.test_empty_prop_val_executable(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExec),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_mappings.py b/opt/hgsubversion/tests/test_fetch_mappings.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_mappings.py
@@ -0,0 +1,329 @@
+"""Tests for author maps and file maps.
+"""
+import test_util
+
+import os
+import unittest
+
+from mercurial import commands
+from mercurial import hg
+from mercurial import node
+from mercurial import util as hgutil
+
+from hgsubversion import maps
+from hgsubversion import svncommands
+from hgsubversion import util
+
+class MapTests(test_util.TestBase):
+    @property
+    def authors(self):
+        return os.path.join(self.tmpdir, 'authormap')
+
+    @property
+    def filemap(self):
+        return os.path.join(self.tmpdir, 'filemap')
+
+    @property
+    def branchmap(self):
+        return os.path.join(self.tmpdir, 'branchmap')
+	
+    @property
+    def tagmap(self):
+        return os.path.join(self.tmpdir, 'tagmap')
+
+    def test_author_map(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump')
+        authormap = open(self.authors, 'w')
+        authormap.write('Augie=Augie Fackler <durin42@gmail.com> # stuffy\n')
+        authormap.write("Augie Fackler <durin42@gmail.com>\n")
+        authormap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'authormap', self.authors)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, authors=self.authors)
+        self.assertEqual(self.repo[0].user(),
+                         'Augie Fackler <durin42@gmail.com>')
+        self.assertEqual(self.repo['tip'].user(),
+                        'evil@5b65bade-98f3-4993-a01f-b7a6710da339')
+
+    def test_author_map_stupid(self):
+        self.test_author_map(True)
+
+    def test_author_map_closing_author(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump')
+        authormap = open(self.authors, 'w')
+        authormap.write("evil=Testy <test@test>")
+        authormap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'authormap', self.authors)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, authors=self.authors)
+        self.assertEqual(self.repo[0].user(),
+                         'Augie@5b65bade-98f3-4993-a01f-b7a6710da339')
+        self.assertEqual(self.repo['tip'].user(),
+                        'Testy <test@test>')
+
+    def test_author_map_closing_author_stupid(self):
+        self.test_author_map_closing_author(True)
+
+    def test_author_map_no_author(self, stupid=False):
+        self._load_fixture_and_fetch('no-author.svndump', stupid=stupid)
+        users = set(self.repo[r].user() for r in self.repo)
+        expected_users = ['(no author)@%s' % self.repo.svnmeta().uuid]
+        self.assertEqual(sorted(users), expected_users)
+        test_util.rmtree(self.wc_path)
+
+        authormap = open(self.authors, 'w')
+        authormap.write("(no author)=Testy <test@example.com>")
+        authormap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'authormap', self.authors)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, authors=self.authors)
+        users = set(self.repo[r].user() for r in self.repo)
+        expected_users = ['Testy <test@example.com>']
+        self.assertEqual(sorted(users), expected_users)
+
+    def test_author_map_no_author_stupid(self):
+        self.test_author_map_no_author(True)
+
+    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')
+        new.write(open(orig).read())
+        new.close()
+        test = maps.AuthorMap(self.ui(), self.authors)
+        fromself = set(test)
+        test.load(orig)
+        all = set(test)
+        self.assertEqual(fromself.symmetric_difference(all), set())
+
+    def test_file_map(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump')
+        filemap = open(self.filemap, 'w')
+        filemap.write("include alpha\n")
+        filemap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'filemap', self.filemap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, filemap=self.filemap)
+        self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d')
+        self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155')
+
+    def test_file_map_stupid(self):
+        self.test_file_map(True)
+
+    def test_file_map_exclude(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump')
+        filemap = open(self.filemap, 'w')
+        filemap.write("exclude alpha\n")
+        filemap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'filemap', self.filemap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, filemap=self.filemap)
+        self.assertEqual(node.hex(self.repo[0].node()), '2c48f3525926ab6c8b8424bcf5eb34b149b61841')
+        self.assertEqual(node.hex(self.repo['default'].node()), 'b37a3c0297b71f989064d9b545b5a478bbed7cc1')
+
+    def test_file_map_exclude_stupid(self):
+        self.test_file_map_exclude(True)
+
+    def test_branchmap(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("badname = good-name # stuffy\n")
+        branchmap.write("feature = default\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        branches = set(self.repo[i].branch() for i in self.repo)
+        self.assert_('badname' not in branches)
+        self.assert_('good-name' in branches)
+        self.assertEquals(self.repo[2].branch(), 'default')
+
+    def test_branchmap_stupid(self):
+        self.test_branchmap(True)
+
+    def test_branchmap_tagging(self, stupid=False):
+        '''test tagging a renamed branch, which used to raise an exception'''
+        test_util.load_svndump_fixture(self.repo_path, 'commit-to-tag.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("magic = art\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        branches = set(self.repo[i].branch() for i in self.repo)
+        self.assertEquals(sorted(branches), ['art', 'closeme'])
+
+    def test_branchmap_tagging_stupid(self):
+        self.test_branchmap_tagging(True)
+
+    def test_branchmap_empty_commit(self, stupid=False):
+        '''test mapping an empty commit on a renamed branch'''
+        test_util.load_svndump_fixture(self.repo_path, 'propset-branch.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("the-branch = bob\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        branches = set(self.repo[i].branch() for i in self.repo)
+        self.assertEquals(sorted(branches), ['bob', 'default'])
+
+    def test_branchmap_empty_commit_stupid(self):
+        '''test mapping an empty commit on a renamed branch (stupid)'''
+        self.test_branchmap_empty_commit(True)
+
+    def test_branchmap_combine(self, stupid=False):
+        '''test combining two branches, but retaining heads'''
+        test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("badname = default\n")
+        branchmap.write("feature = default\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        branches = set(self.repo[i].branch() for i in self.repo)
+        self.assertEquals(sorted(branches), ['default'])
+        self.assertEquals(len(self.repo.heads()), 2)
+        self.assertEquals(len(self.repo.branchheads('default')), 2)
+
+        # test that the mapping does not affect branch info
+        branches = self.repo.svnmeta().branches
+        self.assertEquals(sorted(branches.keys()),
+                          [None, 'badname', 'feature'])
+
+    def test_branchmap_combine_stupid(self):
+        '''test combining two branches, but retaining heads (stupid)'''
+        self.test_branchmap_combine(True)
+
+    def test_branchmap_rebuildmeta(self, stupid=False):
+        '''test rebuildmeta on a branchmapped clone'''
+        test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("badname = dit\n")
+        branchmap.write("feature = dah\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        originfo = self.repo.svnmeta().branches
+
+        # clone & rebuild
+        ui = self.ui(stupid)
+        src, dest = test_util.hgclone(ui, self.wc_path, self.wc_path + '_clone',
+                                      update=False)
+        svncommands.rebuildmeta(ui, dest,
+                                args=[test_util.fileurl(self.repo_path)])
+
+        # just check the keys; assume the contents are unaffected by the branch
+        # map and thus properly tested by other tests
+        self.assertEquals(sorted(src.svnmeta().branches),
+                          sorted(dest.svnmeta().branches))
+
+    def test_branchmap_rebuildmeta_stupid(self):
+        '''test rebuildmeta on a branchmapped clone (stupid)'''
+        self.test_branchmap_rebuildmeta(True)
+
+    def test_branchmap_verify(self, stupid=False):
+        '''test verify on a branchmapped clone'''
+        test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump')
+        branchmap = open(self.branchmap, 'w')
+        branchmap.write("badname = dit\n")
+        branchmap.write("feature = dah\n")
+        branchmap.close()
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'branchmap', self.branchmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, branchmap=self.branchmap)
+        repo = self.repo
+
+        for r in repo:
+            self.assertEquals(svncommands.verify(ui, repo, rev=r), 0)
+
+    def test_branchmap_verify_stupid(self):
+        '''test verify on a branchmapped clone (stupid)'''
+        self.test_branchmap_verify(True)
+
+    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.
+        '''
+        test_util.load_svndump_fixture(self.repo_path, '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, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'basic_tag_tests.svndump')
+        tagmap = open(self.tagmap, 'w')
+        tagmap.write("tag_r3 = 3.x # stuffy\n")
+        tagmap.write("copied_tag = \n")
+        tagmap.close()
+
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'tagmap', self.tagmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, tagmap=self.tagmap)
+        tags = self.repo.tags()
+        assert 'tag_r3' not in tags
+        assert '3.x' in tags
+        assert 'copied_tag' not in tags
+
+    def test_tagmap_stupid(self):
+        self.test_tagmap(True)
+
+    def test_tagren_changed(self, stupid=False):
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'commit-to-tag.svndump')
+        tagmap = open(self.tagmap, 'w')
+        tagmap.write("edit-at-create = edit-past\n")
+        tagmap.write("also-edit = \n")
+        tagmap.write("will-edit = edit-future\n")
+        tagmap.close()
+
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'tagmap', self.tagmap)
+        commands.clone(ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, tagmap=self.tagmap)
+        tags = self.repo.tags()
+
+    def test_tagren_changed_stupid(self):
+        self.test_tagren_changed(True)
+
+    def test_empty_log_message(self, stupid=False):
+        repo = self._load_fixture_and_fetch('empty-log-message.svndump',
+                                            stupid=stupid)
+
+        self.assertEqual(repo['tip'].description(), '')
+
+        test_util.rmtree(self.wc_path)
+
+        ui = self.ui(stupid)
+        ui.setconfig('hgsubversion', 'defaultmessage', 'blyf')
+        commands.clone(ui, test_util.fileurl(self.repo_path), self.wc_path)
+
+        self.assertEqual(self.repo['tip'].description(), 'blyf')
+
+
+    def test_empty_log_message_stupid(self):
+        self.test_empty_log_message(True)
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(MapTests)
diff --git a/opt/hgsubversion/tests/test_fetch_renames.py b/opt/hgsubversion/tests/test_fetch_renames.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_renames.py
@@ -0,0 +1,80 @@
+import test_util
+
+import sys
+import unittest
+
+class TestFetchRenames(test_util.TestBase):
+    def _debug_print_copies(self, repo):
+        w = sys.stderr.write
+        for rev in repo:
+            ctx = repo[rev]
+            w('%d - %s\n' % (ctx.rev(), ctx.branch()))
+            for f in ctx:
+                fctx = ctx[f]
+                w('%s: %r %r\n' % (f, fctx.data(), fctx.renamed()))
+
+    def _test_rename(self, stupid):
+        repo = self._load_fixture_and_fetch('renames.svndump', stupid=stupid)
+        # self._debug_print_copies(repo)
+
+        # Map revnum to mappings of dest name to (source name, dest content)
+        copies = {
+            4: {
+                'a1': ('a', 'a\n'),
+                'a2': ('a', 'a\n'),
+                'b1': ('b', 'b\nc\n'),
+                'da1/daf': ('da/daf', 'c\n'),
+                'da1/db/dbf': ('da/db/dbf', 'd\n'),
+                'da2/daf': ('da/daf', 'c\n'),
+                'da2/db/dbf': ('da/db/dbf', 'd\n'),
+                },
+            5: {
+                'c1': ('c', 'c\nc\n'),
+                },
+            9: {
+                'unchanged2': ('unchanged', 'unchanged\n'),
+                'unchangeddir2/f': ('unchangeddir/f', 'unchanged2\n'),
+                },
+            10: {
+                 'groupdir2/b': ('groupdir/b', 'b\n')
+                 },
+            }
+        for rev in repo:
+            ctx = repo[rev]
+            copymap = copies.get(rev, {})
+            for f in ctx.manifest():
+                cp = ctx[f].renamed()
+                self.assertEqual(bool(cp), bool(copymap.get(f)),
+                                 'copy records differ for %s in %d' % (f, rev))
+                if not cp:
+                    continue
+                self.assertEqual(cp[0], copymap[f][0])
+                self.assertEqual(ctx[f].data(), copymap[f][1])
+
+        self.assertEqual(repo['tip']['changed3'].data(), 'changed\nchanged3\n')
+
+    def test_rename(self):
+        self._test_rename(False)
+
+    def test_rename_stupid(self):
+        self._test_rename(True)
+
+    def _test_case(self, stupid):
+        repo = self._load_fixture_and_fetch('filecase.svndump', stupid=stupid)
+        files = {
+            0: ['A', 'a', 'e/a', 'b', 'd/a', 'D/a', 'f/a', 'F'],
+            1: ['A', 'a', 'E/a', 'B', 'd/A', 'D/a', 'f/a', 'F'],
+            }
+        for rev in repo:
+            self.assertEqual(sorted(files[rev]), sorted(repo[rev].manifest()))
+
+    def test_case(self):
+        self._test_case(False)
+
+    def test_case_stupid(self):
+        self._test_case(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchRenames),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_symlinks.py b/opt/hgsubversion/tests/test_fetch_symlinks.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_symlinks.py
@@ -0,0 +1,58 @@
+import test_util
+
+import unittest
+
+class TestFetchSymlinks(test_util.TestBase):
+    def _load_fixture_and_fetch(self, fixture_name, stupid):
+        return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
+                                                self.wc_path, stupid=stupid)
+
+    def test_symlinks(self, stupid=False):
+        repo = self._load_fixture_and_fetch('symlinks.svndump', stupid)
+        # Check symlinks throughout history
+        links = {
+            0: {
+                'linka': 'a',
+                'linka2': 'a',
+                'd/linka': 'a',
+                },
+            1: {
+                'linkaa': 'a',
+                'linka2': 'a',
+                'd2/linka': 'a',
+                },
+            2: {
+                'linkaa': 'b',
+                'linka2': 'a',
+                'd2/linka': 'b',
+                },
+            3: {
+                },
+            4: {
+                'linka3': 'a',
+                },
+            5: {
+                'linka3': 'a',
+                },
+            6: {
+                'linka3': 'a',
+                'linka4': 'link to this',
+                },
+            }
+            
+        for rev in repo:
+            ctx = repo[rev]
+            for f in ctx.manifest():
+                self.assertEqual(f in links[rev], 'l' in ctx[f].flags())
+                if f in links[rev]:
+                    self.assertEqual(links[rev][f], ctx[f].data())
+            for f in links[rev]:
+                self.assertTrue(f in ctx)
+
+    def test_symlinks_stupid(self):
+        self.test_symlinks(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchSymlinks),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_fetch_truncated.py b/opt/hgsubversion/tests/test_fetch_truncated.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_fetch_truncated.py
@@ -0,0 +1,36 @@
+import test_util
+
+import unittest
+
+from mercurial import commands
+from mercurial import hg
+
+class TestFetchTruncatedHistory(test_util.TestBase):
+    def test_truncated_history(self, stupid=False):
+        # Test repository does not follow the usual layout
+        test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump')
+        svn_url = test_util.fileurl(self.repo_path + '/project2')
+        commands.clone(self.ui(stupid), svn_url, self.wc_path, noupdate=True)
+        repo = hg.repository(self.ui(stupid), self.wc_path)
+
+        # We are converting /project2/trunk coming from:
+        #
+        # Changed paths:
+        #     D /project1
+        #     A /project2/trunk (from /project1:2)
+        #
+        # Here a full fetch should be performed since we are starting
+        # the conversion on an already filled branch.
+        tip = repo['tip']
+        files = tip.manifest().keys()
+        files.sort()
+        self.assertEqual(files, ['a', 'b'])
+        self.assertEqual(repo['tip']['a'].data(), 'a\n')
+
+    def test_truncated_history_stupid(self):
+        self.test_truncated_history(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_pull.py b/opt/hgsubversion/tests/test_pull.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_pull.py
@@ -0,0 +1,56 @@
+import test_util
+
+import os.path
+import subprocess
+from mercurial import ui
+from mercurial import util as hgutil
+from mercurial import commands
+
+class TestPull(test_util.TestBase):
+    def setUp(self):
+        super(TestPull, self).setUp()
+
+    def _load_fixture_and_fetch(self, fixture_name):
+        return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
+                                                self.wc_path, stupid=False,
+                                                noupdate=False)
+
+    def test_nochanges(self):
+        self._load_fixture_and_fetch('single_rev.svndump')
+        state = self.repo.parents()
+        commands.pull(self.repo.ui, self.repo)
+        self.assertEqual(state, self.repo.parents())
+
+    def test_onerevision_noupdate(self):
+        repo = self._load_fixture_and_fetch('single_rev.svndump')
+        state = repo.parents()
+        self._add_svn_rev({'trunk/alpha': 'Changed'})
+        commands.pull(self.repo.ui, repo)
+        self.assertEqual(state, repo.parents())
+        self.assertTrue('tip' not in repo[None].tags())
+
+    def test_onerevision_doupdate(self):
+        repo = self._load_fixture_and_fetch('single_rev.svndump')
+        state = repo.parents()
+        self._add_svn_rev({'trunk/alpha': 'Changed'})
+        commands.pull(self.repo.ui, repo, update=True)
+        self.failIfEqual(state, repo.parents())
+        self.assertTrue('tip' in repo[None].tags())
+
+    def test_onerevision_divergent(self):
+        repo = self._load_fixture_and_fetch('single_rev.svndump')
+        self.commitchanges((('alpha', 'alpha', 'Changed another way'),))
+        state = repo.parents()
+        self._add_svn_rev({'trunk/alpha': 'Changed one way'})
+        try:
+            commands.pull(self.repo.ui, repo, update=True)
+        except hgutil.Abort:
+            # hg < 1.9 raised when crossing branches
+            pass
+        self.assertEqual(state, repo.parents())
+        self.assertTrue('tip' not in repo[None].tags())
+        self.assertEqual(len(repo.heads()), 2)
+
+def suite():
+    import unittest, sys
+    return unittest.findTestCases(sys.modules[__name__])
diff --git a/opt/hgsubversion/tests/test_push_command.py b/opt/hgsubversion/tests/test_push_command.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_push_command.py
@@ -0,0 +1,501 @@
+import test_util
+
+import atexit
+import errno
+import os
+import random
+import shutil
+import socket
+import subprocess
+import unittest
+
+from mercurial import context
+from mercurial import commands
+from mercurial import hg
+from mercurial import node
+from mercurial import revlog
+from mercurial import util as hgutil
+
+import time
+
+
+class PushTests(test_util.TestBase):
+    def setUp(self):
+        test_util.TestBase.setUp(self)
+        test_util.load_fixture_and_fetch('simple_branch.svndump',
+                                         self.repo_path,
+                                         self.wc_path)
+
+    def test_cant_push_empty_ctx(self):
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError()
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'automated test',
+                             [],
+                             file_callback,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500',
+                             {'branch': 'default',})
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        old_tip = repo['tip'].node()
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertEqual(tip.node(), old_tip)
+
+    def test_cant_push_with_changes(self):
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            return context.memfilectx(
+                path=path, data='foo', islink=False,
+                isexec=False, copied=False)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file'],
+                             file_callback,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500',
+                             {'branch': 'default',})
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        # Touch an existing file
+        repo.wwrite('beta', 'something else', '')
+        try:
+            self.pushrevisions()
+        except hgutil.Abort:
+            pass
+        tip = self.repo['tip']
+        self.assertEqual(new_hash, tip.node())
+
+    def internal_push_over_svnserve(self, subdir='', commit=True):
+        test_util.load_svndump_fixture(self.repo_path, 'simple_branch.svndump')
+        open(os.path.join(self.repo_path, 'conf', 'svnserve.conf'),
+             'w').write('[general]\nanon-access=write\n[sasl]\n')
+        self.port = random.randint(socket.IPPORT_USERRESERVED, 65535)
+        self.host = 'localhost'
+        args = ['svnserve', '--daemon', '--foreground',
+                '--listen-port=%d' % self.port,
+                '--listen-host=%s' % self.host,
+                '--root=%s' % self.repo_path]
+
+        svnserve = subprocess.Popen(args, stdout=subprocess.PIPE,
+                                    stderr=subprocess.STDOUT)
+        self.svnserve_pid = svnserve.pid
+        try:
+            time.sleep(2)
+            import shutil
+            shutil.rmtree(self.wc_path)
+            commands.clone(self.ui(),
+                           'svn://%s:%d/%s' % (self.host, self.port, subdir),
+                           self.wc_path, noupdate=True)
+
+            repo = self.repo
+            old_tip = repo['tip'].node()
+            expected_parent = repo['default'].node()
+            def file_callback(repo, memctx, path):
+                if path == 'adding_file':
+                    return context.memfilectx(path=path,
+                                              data='foo',
+                                              islink=False,
+                                              isexec=False,
+                                              copied=False)
+                raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+            ctx = context.memctx(repo,
+                                 parents=(repo['default'].node(), node.nullid),
+                                 text='automated test',
+                                 files=['adding_file'],
+                                 filectxfn=file_callback,
+                                 user='an_author',
+                                 date='2008-10-07 20:59:48 -0500',
+                                 extra={'branch': 'default',})
+            new_hash = repo.commitctx(ctx)
+            if not commit:
+                return # some tests use this test as an extended setup.
+            hg.update(repo, repo['tip'].node())
+            oldauthor = repo['tip'].user()
+            commands.push(repo.ui, repo)
+            tip = self.repo['tip']
+            self.assertNotEqual(oldauthor, tip.user())
+            self.assertNotEqual(tip.node(), old_tip)
+            self.assertEqual(tip.parents()[0].node(), expected_parent)
+            self.assertEqual(tip['adding_file'].data(), 'foo')
+            self.assertEqual(tip.branch(), 'default')
+            # unintended behaviour:
+            self.assertNotEqual('an_author', tip.user())
+            self.assertEqual('(no author)', tip.user().rsplit('@', 1)[0])
+        finally:
+            # TODO: use svnserve.kill() in Python >2.5
+            test_util.kill_process(svnserve)
+
+    def test_push_over_svnserve(self):
+        self.internal_push_over_svnserve()
+
+    def test_push_over_svnserve_with_subdir(self):
+        self.internal_push_over_svnserve(subdir='///branches////the_branch/////')
+
+    def test_push_to_default(self, commit=True):
+        repo = self.repo
+        old_tip = repo['tip'].node()
+        expected_parent = repo['default'].node()
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file'],
+                             file_callback,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500',
+                             {'branch': 'default',})
+        new_hash = repo.commitctx(ctx)
+        if not commit:
+            return # some tests use this test as an extended setup.
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), old_tip)
+        self.assertEqual(node.hex(tip.parents()[0].node()),
+                         node.hex(expected_parent))
+        self.assertEqual(tip['adding_file'].data(), 'foo')
+        self.assertEqual(tip.branch(), 'default')
+
+    def test_push_two_revs_different_local_branch(self):
+        def filectxfn(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data=path,
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        oldtiphash = self.repo['default'].node()
+        ctx = context.memctx(self.repo,
+                             (self.repo[0].node(), revlog.nullid, ),
+                             'automated test',
+                             ['gamma', ],
+                             filectxfn,
+                             'testy',
+                             '2008-12-21 16:32:00 -0500',
+                             {'branch': 'localbranch', })
+        newhash = self.repo.commitctx(ctx)
+        ctx = context.memctx(self.repo,
+                             (newhash, revlog.nullid),
+                             'automated test2',
+                             ['delta', ],
+                             filectxfn,
+                             'testy',
+                             '2008-12-21 16:32:00 -0500',
+                             {'branch': 'localbranch', })
+        newhash = self.repo.commitctx(ctx)
+        repo = self.repo
+        hg.update(repo, newhash)
+        commands.push(repo.ui, repo)
+        self.assertEqual(self.repo['tip'].parents()[0].parents()[0].node(), oldtiphash)
+        self.assertEqual(self.repo['tip'].files(), ['delta', ])
+        self.assertEqual(self.repo['tip'].manifest().keys(),
+                         ['alpha', 'beta', 'gamma', 'delta'])
+
+    def test_push_two_revs(self):
+        # set up some work for us
+        self.test_push_to_default(commit=False)
+        repo = self.repo
+        old_tip = repo['tip'].node()
+        expected_parent = repo['tip'].parents()[0].node()
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file2':
+                return context.memfilectx(path=path,
+                                          data='foo2',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file2'],
+                             file_callback,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500',
+                             {'branch': 'default',})
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), old_tip)
+        self.assertNotEqual(tip.parents()[0].node(), old_tip)
+        self.assertEqual(tip.parents()[0].parents()[0].node(), expected_parent)
+        self.assertEqual(tip['adding_file2'].data(), 'foo2')
+        self.assertEqual(tip['adding_file'].data(), 'foo')
+        self.assertEqual(tip.parents()[0]['adding_file'].data(), 'foo')
+        try:
+            self.assertEqual(tip.parents()[0]['adding_file2'].data(), 'foo')
+            assert False, "this is impossible, adding_file2 should not be in this manifest."
+        except revlog.LookupError, e:
+            pass
+        self.assertEqual(tip.branch(), 'default')
+
+    def test_push_to_branch(self, push=True):
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['the_branch'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file'],
+                             file_callback,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500',
+                             {'branch': 'the_branch',})
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        if push:
+            self.pushrevisions()
+            tip = self.repo['tip']
+            self.assertNotEqual(tip.node(), new_hash)
+            self.assertEqual(tip['adding_file'].data(), 'foo')
+            self.assertEqual(tip.branch(), 'the_branch')
+
+    def test_push_to_non_tip(self):
+        self.test_push_to_branch(push=False)
+        wc2path = self.wc_path + '_clone'
+        u = self.repo.ui
+        test_util.hgclone(self.repo.ui, self.wc_path, wc2path, update=False)
+        res = self.pushrevisions()
+        self.assertEqual(0, res)
+        oldf = open(os.path.join(self.wc_path, '.hg', 'hgrc'))
+        hgrc = oldf.read()
+        oldf.close()
+        shutil.rmtree(self.wc_path)
+        test_util.hgclone(u, wc2path, self.wc_path, update=False)
+        oldf = open(os.path.join(self.wc_path, '.hg', 'hgrc'), 'w')
+        oldf.write(hgrc)
+        oldf.close()
+
+        # do a commit here
+        self.commitchanges([('foobaz', 'foobaz', 'This file is added on default.', ),
+                            ],
+                           parent='default',
+                           message='commit to default')
+        from hgsubversion import svncommands
+        svncommands.rebuildmeta(u,
+                                self.repo,
+                                args=[test_util.fileurl(self.repo_path)])
+
+
+        hg.update(self.repo, self.repo['tip'].node())
+        oldnode = self.repo['tip'].hex()
+        self.pushrevisions(expected_extra_back=1)
+        self.assertNotEqual(oldnode, self.repo['tip'].hex(), 'Revision was not pushed.')
+
+    def test_delete_file(self):
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            raise IOError(errno.ENOENT, '%s is deleted' % path)
+        old_files = set(repo['default'].manifest().keys())
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'automated test',
+                             ['alpha'],
+                             file_callback,
+                             'an author',
+                             '2008-10-29 21:26:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertEqual(old_files,
+                         set(tip.manifest().keys() + ['alpha']))
+        self.assert_('alpha' not in tip.manifest())
+
+    def test_push_executable_file(self):
+        self.test_push_to_default(commit=True)
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            if path == 'gamma':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=True,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['tip'].node(), node.nullid),
+                             'message',
+                             ['gamma', ],
+                             file_callback,
+                             'author',
+                             '2008-10-29 21:26:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.clean(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), new_hash)
+        self.assert_('@' in self.repo['tip'].user())
+        self.assertEqual(tip['gamma'].flags(), 'x')
+        self.assertEqual(tip['gamma'].data(), 'foo')
+        self.assertEqual([x for x in tip.manifest().keys() if 'x' not in
+                          tip[x].flags()], ['alpha', 'beta', 'adding_file', ])
+
+    def test_push_symlink_file(self):
+        self.test_push_to_default(commit=True)
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            if path == 'gamma':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=True,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['tip'].node(), node.nullid),
+                             'message',
+                             ['gamma', ],
+                             file_callback,
+                             'author',
+                             '2008-10-29 21:26:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), new_hash)
+        self.assertEqual(tip['gamma'].flags(), 'l')
+        self.assertEqual(tip['gamma'].data(), 'foo')
+        self.assertEqual([x for x in tip.manifest().keys() if 'l' not in
+                          tip[x].flags()], ['alpha', 'beta', 'adding_file', ])
+
+    def test_push_existing_file_newly_symlink(self):
+        self.test_push_existing_file_newly_execute(execute=False,
+                                                   link=True,
+                                                   expected_flags='l')
+
+    def test_push_existing_file_newly_execute(self, execute=True,
+                                              link=False, expected_flags='x'):
+        self.test_push_to_default()
+        repo = self.repo
+        def file_callback(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='foo',
+                                      islink=link,
+                                      isexec=execute,
+                                      copied=False)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'message',
+                             ['alpha', ],
+                             file_callback,
+                             'author',
+                             '2008-1-1 00:00:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), new_hash)
+        self.assertEqual(tip['alpha'].data(), 'foo')
+        self.assertEqual(tip.parents()[0]['alpha'].flags(), '')
+        self.assertEqual(tip['alpha'].flags(), expected_flags)
+        # while we're here, double check pushing an already-executable file
+        # works
+        repo = self.repo
+        def file_callback2(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='bar',
+                                      islink=link,
+                                      isexec=execute,
+                                      copied=False)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'message',
+                             ['alpha', ],
+                             file_callback2,
+                             'author',
+                             '2008-1-1 00:00:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), new_hash)
+        self.assertEqual(tip['alpha'].data(), 'bar')
+        self.assertEqual(tip.parents()[0]['alpha'].flags(), expected_flags)
+        self.assertEqual(tip['alpha'].flags(), expected_flags)
+        # now test removing the property entirely
+        repo = self.repo
+        def file_callback3(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='bar',
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        ctx = context.memctx(repo,
+                             (repo['default'].node(), node.nullid),
+                             'message',
+                             ['alpha', ],
+                             file_callback3,
+                             'author',
+                             '2008-01-01 00:00:00 -0500',
+                             {'branch': 'default', })
+        new_hash = repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        tip = self.repo['tip']
+        self.assertNotEqual(tip.node(), new_hash)
+        self.assertEqual(tip['alpha'].data(), 'bar')
+        self.assertEqual(tip.parents()[0]['alpha'].flags(), expected_flags)
+        self.assertEqual(tip['alpha'].flags(), '')
+
+    def test_push_outdated_base_text(self):
+        self.test_push_two_revs()
+        changes = [('adding_file', 'adding_file', 'different_content', ),
+                   ]
+        par = self.repo['tip'].rev()
+        self.commitchanges(changes, parent=par)
+        self.pushrevisions()
+        changes = [('adding_file', 'adding_file',
+                    'even_more different_content', ),
+                   ]
+        self.commitchanges(changes, parent=par)
+        try:
+            self.pushrevisions()
+            assert False, 'This should have aborted!'
+        except hgutil.Abort, e:
+            self.assertEqual(e.args[0],
+                             'Outgoing changesets parent is not at subversion '
+                             'HEAD\n'
+                             '(pull again and rebase on a newer revision)')
+
+
+def suite():
+    test_classes = [PushTests, ]
+    tests = []
+    # This is the quickest hack I could come up with to load all the tests from
+    # both classes. Would love a patch that simplifies this without adding
+    # dependencies.
+    for tc in test_classes:
+        for attr in dir(tc):
+            if attr.startswith('test_'):
+                tests.append(tc(attr))
+    return unittest.TestSuite(tests)
diff --git a/opt/hgsubversion/tests/test_push_dirs.py b/opt/hgsubversion/tests/test_push_dirs.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_push_dirs.py
@@ -0,0 +1,109 @@
+import test_util
+
+import unittest
+
+class TestPushDirectories(test_util.TestBase):
+    def test_push_dirs(self):
+        self._load_fixture_and_fetch('emptyrepo.svndump')
+
+        changes = [
+            # Single file in single directory
+            ('d1/a', 'd1/a', 'a\n'),
+            # Two files in one directory
+            ('d2/a', 'd2/a', 'a\n'),
+            ('d2/b', 'd2/b', 'a\n'),
+            # Single file in empty directory hierarchy
+            ('d31/d32/d33/d34/a', 'd31/d32/d33/d34/a', 'a\n'),
+            ('d31/d32/a', 'd31/d32/a', 'a\n'),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.svnls('trunk'),
+                          ['d1', 'd1/a', 'd2', 'd2/a', 'd2/b', 'd31',
+                           'd31/d32', 'd31/d32/a', 'd31/d32/d33',
+                           'd31/d32/d33/d34', 'd31/d32/d33/d34/a'])
+
+        # Add one revision with changed files only, no directory addition
+        # or deletion.
+        changes = [
+            ('d1/a', 'd1/a', 'aa\n'),
+            ('d2/a', 'd2/a', 'aa\n'),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+
+        changes = [
+            # Remove single file in single directory
+            ('d1/a', None, None),
+            # Remove one file out of two
+            ('d2/a', None, None),
+            # Removing this file should remove one empty parent dir too
+            ('d31/d32/d33/d34/a', None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.svnls('trunk'),
+                         ['d2', 'd2/b', 'd31', 'd31/d32', 'd31/d32/a', ])
+
+    def test_push_new_dir_project_root_not_repo_root(self):
+        self._load_fixture_and_fetch('fetch_missing_files_subdir.svndump',
+                                     subdir='foo')
+        changes = [('magic_new/a', 'magic_new/a', 'ohai', ),
+                   ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.svnls('foo/trunk'), ['bar',
+                                                   'bar/alpha',
+                                                   'bar/beta',
+                                                   'bar/delta',
+                                                   'bar/gamma',
+                                                   'foo',
+                                                   'magic_new',
+                                                   'magic_new/a'])
+
+    def test_push_new_file_existing_dir_root_not_repo_root(self):
+        self._load_fixture_and_fetch('empty_dir_in_trunk_not_repo_root.svndump',
+                                     subdir='project')
+        changes = [('narf/a', 'narf/a', 'ohai', ),
+                   ]
+        self.commitchanges(changes)
+        self.assertEqual(self.svnls('project/trunk'), ['a',
+                                                       'narf',])
+        self.pushrevisions()
+        self.assertEqual(self.svnls('project/trunk'), ['a',
+                                                       'narf',
+                                                       'narf/a'])
+        changes = [('narf/a', None, None, ),
+                   ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.svnls('project/trunk'), ['a' ,])
+
+    def test_push_single_dir_change_in_subdir(self):
+        # Tests simple pushing from default branch to a single dir repo
+        # Changes a file in a subdir (regression).
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='tags')
+        changes = [('tag_r3/alpha', 'tag_r3/alpha', 'foo'),
+                   ('tag_r3/new', 'tag_r3/new', 'foo'),
+                   ('new_dir/new', 'new_dir/new', 'foo'),
+                   ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.svnls('tags'),
+                         ['copied_tag',
+                          'copied_tag/alpha',
+                          'copied_tag/beta',
+                          'new_dir',
+                          'new_dir/new',
+                          'tag_r3',
+                          'tag_r3/alpha',
+                          'tag_r3/beta',
+                          'tag_r3/new'])
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestPushDirectories),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_push_eol.py b/opt/hgsubversion/tests/test_push_eol.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_push_eol.py
@@ -0,0 +1,42 @@
+import test_util
+
+import unittest
+
+class TestPushEol(test_util.TestBase):
+    def setUp(self):
+        test_util.TestBase.setUp(self)
+        test_util.load_fixture_and_fetch('emptyrepo.svndump',
+                                         self.repo_path,
+                                         self.wc_path)
+
+    def _test_push_dirs(self, stupid):
+        changes = [
+            # Root files with LF, CRLF and mixed EOL
+            ('lf', 'lf', 'a\nb\n\nc'),
+            ('crlf', 'crlf', 'a\r\nb\r\n\r\nc'),
+            ('mixed', 'mixed', 'a\r\nb\n\r\nc\nd'),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+        changes = [
+            # Update all files once, with same EOL
+            ('lf', 'lf', 'a\nb\n\nc\na\nb\n\nc'),
+            ('crlf', 'crlf', 'a\r\nb\r\n\r\nc\r\na\r\nb\r\n\r\nc'),
+            ('mixed', 'mixed', 'a\r\nb\n\r\nc\nd\r\na\r\nb\n\r\nc\nd'),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions(stupid)
+        self.assertchanges(changes, self.repo['tip'])
+
+    def test_push_dirs(self):
+        self._test_push_dirs(False)
+
+    def test_push_dirs_stupid(self):
+        self._test_push_dirs(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestPushEol),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_push_renames.py b/opt/hgsubversion/tests/test_push_renames.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_push_renames.py
@@ -0,0 +1,124 @@
+import test_util
+
+import sys
+import unittest
+
+class TestPushRenames(test_util.TestBase):
+    def setUp(self):
+        test_util.TestBase.setUp(self)
+        test_util.load_fixture_and_fetch('pushrenames.svndump',
+                                         self.repo_path,
+                                         self.wc_path,
+                                         True)
+
+    def _debug_print_copies(self, ctx):
+        w = sys.stderr.write
+        for f in ctx.files():
+            if f not in ctx:
+                w('R %s\n' % f)
+            else:
+                w('U %s %r\n' % (f, ctx[f].data()))
+                if ctx[f].renamed():
+                    w('%s copied from %s\n' % (f, ctx[f].renamed()[0]))
+
+    def test_push_renames(self):
+        repo = self.repo
+
+        changes = [
+            # Regular copy of a single file
+            ('a', 'a2', None),
+            # Copy and update of target
+            ('a', 'a3', 'aa\n'),
+            # Regular move of a single file
+            ('b', 'b2', None),
+            ('b', None, None),
+            # Regular move and update of target
+            ('c', 'c2', 'c\nc\n'),
+            ('c', None, None),
+            # Copy and update of source and targets
+            ('d', 'd2', 'd\nd2\n'),
+            ('d', 'd', 'd\nd\n'),
+            # Double copy and removal (aka copy and move)
+            ('e', 'e2', 'e\ne2\n'),
+            ('e', 'e3', 'e\ne3\n'),
+            ('e', None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        tip = self.repo['tip']
+        # self._debug_print_copies(tip)
+        self.assertchanges(changes, tip)
+
+    def test_push_rename_with_space(self):
+        changes = [
+            ('random/dir with space/file with space',
+             'random/dir with space/file with space',
+             'file contents'),
+            ]
+        self.commitchanges(changes)
+
+        changes = [
+            ('random/dir with space/file with space',
+             'random2/dir with space/file with space',
+             None),
+            ('random/dir with space/file with space',
+             None, None),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual(self.repo['tip'].manifest().keys(),
+                         ['a', 'c', 'b', 'e', 'd',
+                          'random2/dir with space/file with space'])
+
+    def test_push_rename_tree(self):
+        repo = self.repo
+
+        changes = [
+            ('geek/alpha', 'geek/alpha', 'content',),
+            ('geek/beta', 'geek/beta', 'content',),
+            ('geek/delta', 'geek/delta', 'content',),
+            ('geek/gamma', 'geek/gamma', 'content',),
+            ('geek/later/pi', 'geek/later/pi', 'content geek/later/pi',),
+            ('geek/later/rho', 'geek/later/rho', 'content geek/later/rho', ),
+            ('geek/other/blah', 'geek/other/blah', 'content geek/other/blah', ),
+            ('geek/other/another/layer', 'geek/other/another/layer', 'content deep file', ),
+            ]
+
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertchanges(changes, self.repo['tip'])
+
+        changes = [
+            # rename (copy + remove) all of geek to greek
+            ('geek/alpha', 'greek/alpha', None, ),
+            ('geek/beta', 'greek/beta', None, ),
+            ('geek/delta', 'greek/delta', None, ),
+            ('geek/gamma', 'greek/gamma', None, ),
+            ('geek/later/pi', 'greek/later/pi', None, ),
+            ('geek/later/rho', 'greek/later/rho', None, ),
+            ('geek/other/blah', 'greek/other/blah', None, ),
+            ('geek/other/another/layer', 'greek/other/another/layer', None, ),
+
+            ('geek/alpha', None, None, ),
+            ('geek/beta', None, None, ),
+            ('geek/delta', None, None, ),
+            ('geek/gamma', None, None, ),
+            ('geek/later/pi', None, None, ),
+            ('geek/later/rho', None, None, ),
+            ('geek/other/blah', None, None, ),
+            ('geek/other/another/layer', None, None, ),
+            ]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        # print '\n'.join(sorted(self.svnls('trunk')))
+        assert reduce(lambda x, y: x and y,
+                      ('geek' not in f for f in self.svnls('trunk'))),(
+            'This failure means rename of an entire tree is broken.'
+            ' There is a print on the preceding line commented out '
+            'that should help you.')
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestPushRenames),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_rebuildmeta.py b/opt/hgsubversion/tests/test_rebuildmeta.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_rebuildmeta.py
@@ -0,0 +1,105 @@
+import test_util
+
+import os
+import pickle
+import unittest
+
+from mercurial import context
+from mercurial import extensions
+from mercurial import hg
+from mercurial import ui
+
+from hgsubversion import svncommands
+from hgsubversion import svnmeta
+
+def _do_case(self, name, stupid, single):
+    subdir = test_util.subdir.get(name, '')
+    layout = 'auto'
+    if single:
+        layout = 'single'
+    self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid, layout=layout)
+    assert len(self.repo) > 0
+    wc2_path = self.wc_path + '_clone'
+    u = ui.ui()
+    src, dest = test_util.hgclone(u, self.wc_path, wc2_path, update=False)
+
+    # insert a wrapper that prevents calling changectx.children()
+    def failfn(orig, ctx):
+        self.fail('calling %s is forbidden; it can cause massive slowdowns '
+                  'when rebuilding large repositories' % orig)
+
+    origchildren = getattr(context.changectx, 'children')
+    extensions.wrapfunction(context.changectx, 'children', failfn)
+
+    try:
+        svncommands.rebuildmeta(u, dest,
+                                args=[test_util.fileurl(self.repo_path +
+                                                        subdir), ])
+    finally:
+        # remove the wrapper
+        context.changectx.children = origchildren
+
+    self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')),
+                    'no .hg/svn directory in the source!')
+    self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')),
+                    'no .hg/svn directory in the destination!')
+    dest = hg.repository(u, os.path.dirname(dest.path))
+    for tf in ('rev_map', 'uuid', 'tagmap', 'layout', 'subdir', ):
+        stf = os.path.join(src.path, 'svn', tf)
+        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()
+        self.assertMultiLineEqual(old, new)
+        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')))
+    self.assertEqual(sorted(srcbi.keys()), sorted(destbi.keys()))
+    revkeys = svnmeta.SVNMeta(dest).revmap.keys()
+    for branch in destbi:
+        srcinfo = srcbi[branch]
+        destinfo = destbi[branch]
+        if srcinfo[:2] == (None, 0) or destinfo[:2] == (None, 0):
+            self.assertTrue(srcinfo[2] <= destinfo[2],
+                            'Latest revision for %s decreased from %d to %d!'
+                            % (branch or 'default', srcinfo[2], destinfo[2]))
+            self.assertEqual(srcinfo[0], destinfo[0])
+        else:
+            pr = sorted(filter(lambda x: x[1] == srcinfo[0] and x[0] <= srcinfo[1],
+                        revkeys), reverse=True)[0][0]
+            self.assertEqual(pr, destinfo[1])
+            self.assertEqual(srcinfo[2], destinfo[2])
+
+
+def buildmethod(case, name, stupid, single):
+    m = lambda self: self._do_case(case, stupid, single)
+    m.__name__ = name
+    m.__doc__ = ('Test rebuildmeta on %s with %s replay. (%s)' %
+                 (case,
+                  (stupid and 'stupid') or 'real',
+                  (single and 'single') or 'standard',
+                  )
+                 )
+    return m
+
+
+attrs = {'_do_case': _do_case,
+         }
+for case in [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')]:
+    # this fixture results in an empty repository, don't use it
+    if case == 'project_root_not_repo_root.svndump':
+        continue
+    bname = 'test_' + case[:-len('.svndump')]
+    attrs[bname] = buildmethod(case, bname, False, False)
+    name = bname + '_stupid'
+    attrs[name] = buildmethod(case, name, True, False)
+    name = bname + '_single'
+    attrs[name] = buildmethod(case, name, False, True)
+
+RebuildMetaTests = type('RebuildMetaTests', (test_util.TestBase, ), attrs)
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(RebuildMetaTests),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_single_dir_clone.py b/opt/hgsubversion/tests/test_single_dir_clone.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_single_dir_clone.py
@@ -0,0 +1,270 @@
+import test_util
+
+import errno
+import shutil
+import unittest
+
+from mercurial import commands
+from mercurial import context
+from mercurial import hg
+from mercurial import node
+from mercurial import ui
+
+class TestSingleDir(test_util.TestBase):
+    def test_clone_single_dir_simple(self):
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='')
+        self.assertEqual(repo.branchtags().keys(), ['default'])
+        self.assertEqual(repo['tip'].manifest().keys(),
+                         ['trunk/beta',
+                          'tags/copied_tag/alpha',
+                          'trunk/alpha',
+                          'tags/copied_tag/beta',
+                          'branches/branch_from_tag/alpha',
+                          'tags/tag_r3/alpha',
+                          'tags/tag_r3/beta',
+                          'branches/branch_from_tag/beta'])
+
+    def test_auto_detect_single(self):
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='auto')
+        self.assertEqual(repo.branchtags().keys(), ['default',
+                                                    'branch_from_tag'])
+        oldmanifest = test_util.filtermanifest(repo['default'].manifest().keys())
+        # remove standard layout
+        shutil.rmtree(self.wc_path)
+        # try again with subdir to get single dir clone
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='auto',
+                                            subdir='trunk')
+        self.assertEqual(repo.branchtags().keys(), ['default', ])
+        self.assertEqual(repo['default'].manifest().keys(), oldmanifest)
+
+    def test_externals_single(self):
+        repo = self._load_fixture_and_fetch('externals.svndump',
+                                            stupid=False,
+                                            layout='single')
+        for rev in repo:
+            assert '.hgsvnexternals' not in repo[rev].manifest()
+        return # TODO enable test when externals in single are fixed
+        expect = """[.]
+ -r2 ^/externals/project2@2 deps/project2
+[subdir]
+ ^/externals/project1 deps/project1
+[subdir2]
+ ^/externals/project1 deps/project1
+"""
+        test = 2
+        self.assertEqual(self.repo[test]['.hgsvnexternals'].data(), expect)
+
+    def test_externals_single_whole_repo(self):
+        # This is the test which demonstrates the brokenness of externals
+        return # TODO enable test when externals in single are fixed
+        repo = self._load_fixture_and_fetch('externals.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='')
+        for rev in repo:
+            rc = repo[rev]
+            if '.hgsvnexternals' in rc:
+                extdata = rc['.hgsvnexternals'].data()
+                assert '[.]' not in extdata
+                print extdata
+        expect = '' # Not honestly sure what this should be...
+        test = 4
+        self.assertEqual(self.repo[test]['.hgsvnexternals'].data(), expect)
+
+    def test_push_single_dir(self):
+        # Tests simple pushing from default branch to a single dir repo
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='')
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            elif path == 'adding_binary':
+                return context.memfilectx(path=path,
+                                          data='\0binary',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(repo,
+                             (repo['tip'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file', 'adding_binary'],
+                             file_callback,
+                             'an_author',
+                             '2009-10-19 18:49:30 -0500',
+                             {'branch': 'default',})
+        repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions()
+        self.assertTrue('adding_file' in self.svnls(''))
+        self.assertEqual('application/octet-stream',
+                         self.svnpropget('adding_binary', 'svn:mime-type'))
+        # Now add another commit and test mime-type being reset
+        changes = [('adding_binary', 'adding_binary', 'no longer binary')]
+        self.commitchanges(changes)
+        self.pushrevisions()
+        self.assertEqual('', self.svnpropget('adding_binary', 'svn:mime-type'))
+
+    def test_push_single_dir_at_subdir(self):
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='trunk')
+        def filectxfn(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='contents of %s' % path,
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        ctx = context.memctx(repo,
+                             (repo['tip'].node(), node.nullid),
+                             'automated test',
+                             ['bogus'],
+                             filectxfn,
+                             'an_author',
+                             '2009-10-19 18:49:30 -0500',
+                             {'branch': 'localhacking',})
+        n = repo.commitctx(ctx)
+        self.assertEqual(self.repo['tip']['bogus'].data(),
+                         'contents of bogus')
+        before = repo['tip'].hex()
+        hg.update(repo, self.repo['tip'].hex())
+        self.pushrevisions()
+        self.assertNotEqual(before, self.repo['tip'].hex())
+        self.assertEqual(self.repo['tip']['bogus'].data(),
+                         'contents of bogus')
+
+    def test_push_single_dir_one_incoming_and_two_outgoing(self):
+        # Tests simple pushing from default branch to a single dir repo
+        # Pushes two outgoing over one incoming svn rev
+        # (used to cause an "unknown revision")
+        # This can happen if someone committed to svn since our last pull (race).
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='trunk')
+        self._add_svn_rev({'trunk/alpha': 'Changed'})
+        def file_callback(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='data of %s' % path,
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        for fn in ['one', 'two']:
+            ctx = context.memctx(repo,
+                                 (repo['tip'].node(), node.nullid),
+                                 'automated test',
+                                 [fn],
+                                 file_callback,
+                                 'an_author',
+                                 '2009-10-19 18:49:30 -0500',
+                                 {'branch': 'default',})
+            repo.commitctx(ctx)
+        hg.update(repo, repo['tip'].node())
+        self.pushrevisions(expected_extra_back=1)
+        self.assertTrue('trunk/one' in self.svnls(''))
+        self.assertTrue('trunk/two' in self.svnls(''))
+
+    def test_push_single_dir_branch(self):
+        # Tests local branches pushing to a single dir repo. Creates a fork at
+        # tip. The default branch adds a file called default, while branch foo
+        # adds a file called foo, then tries to push the foo branch and default
+        # branch in that order.
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=False,
+                                            layout='single',
+                                            subdir='')
+        def file_callback(data):
+            def cb(repo, memctx, path):
+                if path == data:
+                    return context.memfilectx(path=path,
+                                              data=data,
+                                              islink=False,
+                                              isexec=False,
+                                              copied=False)
+                raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+            return cb
+
+        def commit_to_branch(name, parent):
+            repo.commitctx(context.memctx(repo,
+                                          (parent, node.nullid),
+                                          'automated test (%s)' % name,
+                                          [name],
+                                          file_callback(name),
+                                          'an_author',
+                                          '2009-10-19 18:49:30 -0500',
+                                          {'branch': name,}))
+
+        parent = repo['tip'].node()
+        commit_to_branch('default', parent)
+        commit_to_branch('foo', parent)
+        hg.update(repo, repo['foo'].node())
+        self.pushrevisions()
+        repo = self.repo # repo is outdated after the rebase happens, refresh
+        self.assertTrue('foo' in self.svnls(''))
+        self.assertEqual(repo.branchtags().keys(), ['default'])
+        # Have to cross to another branch head, so hg.update doesn't work
+        commands.update(ui.ui(),
+                        self.repo,
+                        self.repo.branchheads('default')[1],
+                        clean=True)
+        self.pushrevisions()
+        self.assertTrue('default' in self.svnls(''))
+        self.assertEquals(len(self.repo.branchheads('default')), 1)
+
+    @test_util.requiresoption('branch')
+    def test_push_single_dir_renamed_branch(self, stupid=False):
+        # Tests pulling and pushing with a renamed branch
+        # Based on test_push_single_dir
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'branch_from_tag.svndump')
+        cmd = ['clone', '--layout=single', '--branch=flaf']
+        if stupid:
+            cmd.append('--stupid')
+        cmd += [test_util.fileurl(self.repo_path), self.wc_path]
+        test_util.dispatch(cmd)
+
+        def file_callback(repo, memctx, path):
+            if path == 'adding_file':
+                return context.memfilectx(path=path,
+                                          data='foo',
+                                          islink=False,
+                                          isexec=False,
+                                          copied=False)
+            raise IOError(errno.EINVAL, 'Invalid operation: ' + path)
+        ctx = context.memctx(self.repo,
+                             (self.repo['tip'].node(), node.nullid),
+                             'automated test',
+                             ['adding_file'],
+                             file_callback,
+                             'an_author',
+                             '2009-10-19 18:49:30 -0500',
+                             {'branch': 'default',})
+        self.repo.commitctx(ctx)
+        hg.update(self.repo, self.repo['tip'].node())
+        self.pushrevisions()
+        self.assertTrue('adding_file' in self.svnls(''))
+
+        self.assertEquals(set(['flaf']),
+                          set(self.repo[i].branch() for i in self.repo))
+
+    @test_util.requiresoption('branch')
+    def test_push_single_dir_renamed_branch_stupid(self):
+        self.test_push_single_dir_renamed_branch(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestSingleDir)]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_startrev.py b/opt/hgsubversion/tests/test_startrev.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_startrev.py
@@ -0,0 +1,70 @@
+import test_util
+
+import os
+import unittest
+
+def _do_case(self, name, subdir, stupid):
+    wc_base = self.wc_path
+    self.wc_path = wc_base + '_full'
+    headclone = self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid,
+                                             layout='single', startrev='HEAD')
+    self.wc_path = wc_base + '_head'
+    fullclone = self._load_fixture_and_fetch(name, subdir=subdir, stupid=stupid,
+                                             layout='single')
+
+    fulltip = fullclone['tip']
+    headtip = headclone['tip']
+    # viewing diff's of lists of files is easier on the eyes
+    self.assertMultiLineEqual('\n'.join(fulltip), '\n'.join(headtip))
+
+    for f in fulltip:
+        self.assertMultiLineEqual(fulltip[f].data(), headtip[f].data())
+
+    self.assertNotEqual(len(fullclone), 0, "full clone shouldn't be empty")
+    self.assertEqual(len(headclone), 1,
+                     "shallow clone should have just one revision, not %d"
+                     % len(headclone))
+
+def buildmethod(case, name, subdir, stupid):
+    m = lambda self: self._do_case(case, subdir.strip('/'), stupid)
+    m.__name__ = name
+    m.__doc__ = ('Test clone with startrev on %s%s with %s replay.' %
+                 (case, subdir, (stupid and 'stupid') or 'real'))
+    return m
+
+
+# these fixtures contain no files at HEAD and would result in empty clones
+nofiles = set([
+    'binaryfiles.svndump',
+    'emptyrepo.svndump',
+])
+
+# these fixtures contain no files in trunk at HEAD and would result in an empty
+# shallow clone if cloning trunk, so we use another subdirectory
+subdirmap = {
+    'commit-to-tag.svndump': '/branches/magic',
+    'pushexternals.svndump': '',
+    'tag_name_same_as_branch.svndump': '/branches/magic',
+}
+
+attrs = {'_do_case': _do_case,
+         }
+
+for case in [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')]:
+    if case in nofiles:
+        continue
+
+    subdir = test_util.subdir.get(case, '') + subdirmap.get(case, '/trunk')
+
+    bname = 'test_' + case[:-len('.svndump')]
+    attrs[bname] = buildmethod(case, bname, subdir, False)
+    name = bname + '_stupid'
+    attrs[name] = buildmethod(case, name, subdir, True)
+
+StartRevTests = type('StartRevTests', (test_util.TestBase, ), attrs)
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(StartRevTests),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_svnwrap.py b/opt/hgsubversion/tests/test_svnwrap.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_svnwrap.py
@@ -0,0 +1,76 @@
+import test_util
+
+import imp
+import os
+import subprocess
+import shutil
+import tempfile
+import unittest
+
+from hgsubversion import svnwrap
+
+import os
+import stat
+def force_rm(path):
+    os.chmod(
+        path,
+        os.stat(path).st_mode | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH
+    )
+    os.remove(path)
+
+class TestBasicRepoLayout(unittest.TestCase):
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
+        self.repo_path = '%s/testrepo' % self.tmpdir
+        subprocess.call(['svnadmin', 'create', self.repo_path,])
+        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
+                                'project_root_at_repo_root.svndump'))
+        proc = subprocess.call(['svnadmin', 'load', self.repo_path,],
+                                stdin=inp,
+                                close_fds=test_util.canCloseFds,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        assert proc == 0
+        self.repo = svnwrap.SubversionRepo(test_util.fileurl(self.repo_path))
+
+    def tearDown(self):
+        del self.repo
+        shutil.rmtree(self.tmpdir, onerror=lambda func, path, e: force_rm(path))
+
+
+    def test_num_revs(self):
+        revs = list(self.repo.revisions())
+        self.assertEqual(len(revs), 7)
+        r = revs[1]
+        self.assertEqual(r.revnum, 2)
+        self.assertEqual(sorted(r.paths.keys()),
+                  ['trunk/alpha', 'trunk/beta', 'trunk/delta'])
+        for r in revs:
+            for p in r.paths:
+                # make sure these paths are always non-absolute for sanity
+                if p:
+                    assert p[0] != '/'
+        revs = list(self.repo.revisions(start=3))
+        self.assertEqual(len(revs), 4)
+
+class TestRootAsSubdirOfRepo(TestBasicRepoLayout):
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp('svnwrap_test')
+        self.repo_path = '%s/testrepo' % self.tmpdir
+        subprocess.call(['svnadmin', 'create', self.repo_path,])
+        inp = open(os.path.join(os.path.dirname(__file__), 'fixtures',
+                                'project_root_not_repo_root.svndump'))
+        ret = subprocess.call(['svnadmin', 'load', self.repo_path,],
+                              stdin=inp,
+                              close_fds=test_util.canCloseFds,
+                              stdout=subprocess.PIPE,
+                              stderr=subprocess.STDOUT)
+        assert ret == 0
+        self.repo = svnwrap.SubversionRepo(test_util.fileurl(
+            self.repo_path + '/dummyproj'
+        ))
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout),
+           unittest.TestLoader().loadTestsFromTestCase(TestRootAsSubdirOfRepo)]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_tags.py b/opt/hgsubversion/tests/test_tags.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_tags.py
@@ -0,0 +1,284 @@
+import test_util
+
+import os, sys, cStringIO, difflib
+import unittest
+
+from mercurial import commands
+from mercurial import hg
+from mercurial import node
+from mercurial import ui
+
+from hgsubversion import svncommands
+from hgsubversion import svnrepo
+
+class TestTags(test_util.TestBase):
+    def _load_fixture_and_fetch(self, fixture_name, stupid=False):
+        return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
+                                                self.wc_path, stupid=stupid)
+
+    def test_tags(self, stupid=False):
+        repo = self._load_fixture_and_fetch('basic_tag_tests.svndump',
+                                            stupid=stupid)
+        self.assertEqual(sorted(repo.tags()), ['copied_tag', 'tag_r3', 'tip'])
+        self.assertEqual(repo['tag_r3'], repo['copied_tag'])
+        self.assertEqual(repo['tag_r3'].rev(), 1)
+
+    def test_tags_stupid(self):
+        self.test_tags(stupid=True)
+
+    def test_remove_tag(self, stupid=False):
+        repo = self._load_fixture_and_fetch('remove_tag_test.svndump',
+                                            stupid=stupid)
+        self.assertEqual(repo['tag_r3'].rev(), 1)
+        self.assert_('copied_tag' not in repo.tags())
+
+    def test_remove_tag_stupid(self):
+        self.test_remove_tag(stupid=True)
+
+    def test_rename_tag(self, stupid=False):
+        expected = """\
+node: hg=default@2:svn=trunk@4
+tagging r3
+  tag_r3: hg=default@1:svn=trunk@3
+
+node: hg=default@3:svn=trunk@5
+tag from a tag
+  tag_r3: hg=default@1:svn=trunk@3
+  copied_tag: hg=default@1:svn=trunk@3
+
+node: hg=default@4:svn=trunk@6
+rename a tag
+  tag_r3: hg=default@1:svn=trunk@3
+  copied_tag: hg=default@1:svn=trunk@3
+  copied_tag: hg=default@-1:svn=unk@unk
+  other_tag_r3: hg=default@1:svn=trunk@3
+"""
+        self._test_tags('rename_tag_test.svndump', expected, stupid)
+
+    def test_rename_tag_stupid(self):
+        self.test_rename_tag(stupid=True)
+
+    def test_branch_from_tag(self, stupid=False):
+        repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
+                                            stupid=stupid)
+        self.assert_('branch_from_tag' in repo.branchtags())
+        self.assertEqual(repo[1], repo['tag_r3'])
+        self.assertEqual(repo['branch_from_tag'].parents()[0], repo['copied_tag'])
+
+    def test_branch_from_tag_stupid(self):
+        self.test_branch_from_tag(stupid=True)
+
+    def test_tag_by_renaming_branch(self, stupid=False):
+        repo = self._load_fixture_and_fetch('tag_by_rename_branch.svndump',
+                                            stupid=stupid)
+        branches = set(repo[h] for h in repo.heads())
+        self.assert_('dummy' not in branches)
+        self.assertEqual(repo['dummy'], repo['tip'].parents()[0],
+                         '%r != %r[0]' % (repo['dummy'],
+                                              repo['tip'].parents()))
+        extra = repo['tip'].extra().copy()
+        extra.pop('convert_revision', None)
+        self.assertEqual(extra, {'branch': 'dummy', 'close': '1'})
+
+    def test_tag_by_renaming_branch_stupid(self):
+        self.test_tag_by_renaming_branch(stupid=True)
+
+    def test_deletion_of_tag_on_trunk_after_branching(self):
+        repo = self._load_fixture_and_fetch('tag_deletion_tag_branch.svndump')
+        branches = set(repo[h].extra()['branch'] for h in repo.heads())
+        self.assertEqual(branches, set(['default', 'from_2', ]))
+        self.assertEqual(
+            repo.tags(),
+            {'tip': 'g\xdd\xcd\x93\x03g\x1e\x7f\xa6-[V%\x99\x07\xd3\x9d>(\x94',
+             'new_tag': '=\xb8^\xb5\x18\xa9M\xdb\xf9\xb62Z\xa0\xb5R6+\xfe6.'})
+
+    def test_tags_in_unusual_location(self):
+        repo = self._load_fixture_and_fetch('unusual_tags.svndump')
+        branches = set(repo[h].extra()['branch']
+                       for h in repo.heads())
+        self.assertEqual(branches, set(['default', 'dev_branch']))
+        tags = repo.tags()
+        del tags['tip']
+        self.assertEqual(
+            tags,
+            {'blah/trunktag': '\xd3$@\xd7\xd8Nu\xce\xa6%\xf1u\xd9b\x1a\xb2\x81\xc2\xb0\xb1',
+             'versions/branch_version': 'I\x89\x1c>z#\xfc._K#@:\xd6\x1f\x96\xd6\x83\x1b|',
+             })
+
+    def test_most_recent_is_edited_stupid(self):
+        self.test_most_recent_is_edited(True)
+
+    def test_most_recent_is_edited(self, stupid=False):
+        repo = self._load_fixture_and_fetch('most-recent-is-edit-tag.svndump',
+                                            stupid=stupid)
+        self.repo.ui.status(
+            "Note: this test failing may be because of a rebuildmeta failure.\n"
+            "You should check that before assuming issues with this test.\n")
+        wc2_path = self.wc_path + '2'
+        src, dest = test_util.hgclone(repo.ui, self.wc_path, wc2_path, update=False)
+        svncommands.rebuildmeta(repo.ui,
+                               dest,
+                               args=[test_util.fileurl(self.repo_path), ])
+        commands.pull(self.repo.ui, self.repo, stupid=stupid)
+        dtags, srctags = dest.tags(), self.repo.tags()
+        dtags.pop('tip')
+        srctags.pop('tip')
+        self.assertEqual(dtags, srctags)
+        self.assertEqual(dest.heads(), self.repo.heads())
+
+    def test_edited_tag_stupid(self):
+        self.test_edited_tag(True)
+
+    def test_edited_tag(self, stupid=False):
+       repo = self._load_fixture_and_fetch('commit-to-tag.svndump',
+                                           stupid=stupid)
+       headcount = 6
+       self.assertEqual(len(repo.heads()), headcount)
+       heads = repo.heads()
+       openheads = [h for h in heads if not repo[h].extra().get('close', False)]
+       closedheads = set(heads) - set(openheads)
+       self.assertEqual(len(openheads), 1)
+       self.assertEqual(len(closedheads), headcount-1)
+       closedheads = sorted(list(closedheads),
+                            cmp=lambda x,y: cmp(repo[x].rev(), repo[y].rev()))
+
+       # closeme has no open heads
+       for h in openheads:
+           self.assertNotEqual('closeme', repo[openheads[0]].branch())
+
+       self.assertEqual(1, len(self.repo.branchheads('magic')))
+
+       alsoedit, editlater, closeme, willedit, editcreate, = closedheads
+       self.assertEqual(
+           repo[willedit].extra(),
+           {'close': '1',
+            'branch': 'magic',
+            'convert_revision': 'svn:af82cc90-c2d2-43cd-b1aa-c8a78449440a/tags/will-edit@19'})
+       self.assertEqual(willedit, repo.tags()['will-edit'])
+       self.assertEqual(repo['will-edit'].manifest().keys(), ['alpha',
+                                                              'beta',
+                                                              'gamma',
+                                                              ])
+       self.assertEqual(
+           repo[alsoedit].extra(),
+           {'close': '1',
+            'branch': 'magic',
+            'convert_revision': 'svn:af82cc90-c2d2-43cd-b1aa-c8a78449440a/tags/also-edit@14'})
+       self.assertEqual(repo[alsoedit].parents()[0].node(), repo.tags()['also-edit'])
+       self.assertEqual(repo['also-edit'].manifest().keys(),
+                        ['beta',
+                         '.hgtags',
+                         'delta',
+                         'alpha',
+                         'omega',
+                         'iota',
+                         'gamma',
+                         'lambda',
+                         ])
+
+       self.assertEqual(editlater, repo['edit-later'].node())
+       self.assertEqual(
+           repo[closeme].extra(),
+           {'close': '1',
+            'branch': 'closeme',
+            'convert_revision': 'svn:af82cc90-c2d2-43cd-b1aa-c8a78449440a/branches/closeme@17'})
+       self.assertEqual('alpha\nalpha\n', repo['edit-at-create']['alpha'].data())
+
+    def test_tags_in_unusual_location(self):
+        repo = self._load_fixture_and_fetch('tag_name_same_as_branch.svndump')
+        self.assertEqual(len(repo.heads()), 1)
+        branches = set(repo[h].extra()['branch']
+                       for h in repo.heads())
+        self.assertEqual(branches, set(['magic', ]))
+        tags = repo.tags()
+        del tags['tip']
+        self.assertEqual(
+            tags,
+            {'magic': '\xa2b\xb9\x03\xc6\xbd\x903\x95\xf5\x0f\x94\xcey\xc4E\xfaE6\xaa',
+             'magic2': '\xa3\xa2D\x86aM\xc0v\xb9\xb0\x18\x14\xad\xacwBUi}\xe2',
+             })
+
+    def test_old_tag_map_rebuilds(self):
+        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')
+        commands.pull(repo.ui, repo)
+        self.assertEqual(open(tm).read().splitlines()[0], '2')
+
+    def _debug_print_tags(self, repo, ctx, fp):
+        def formatnode(ctx):
+            crev = ctx.extra().get('convert_revision', 'unk/unk@unk')
+            path, rev = crev.rsplit('@', 1)
+            path = path.split('/', 1)[-1]
+            branch = ctx.branch() or 'default'
+            return 'hg=%s@%d:svn=%s@%s' % (branch, ctx.rev(), path, rev)
+
+        w = fp.write
+        desc = ctx.description().splitlines()[0].strip()
+        if '.hgtags' not in ctx or not ctx['.hgtags'].data().strip():
+            return
+        w('node: %s\n' % formatnode(ctx))
+        w('%s\n' % desc)
+        for line in ctx['.hgtags'].data().splitlines(False):
+            node, name = line.split(None, 1)
+            w('  %s: %s\n' % (name, formatnode(repo[node])))
+        w('\n')
+
+    def _test_tags(self, testpath, expected, stupid=False):
+        repo = self._load_fixture_and_fetch(testpath, stupid=stupid)
+        fp = cStringIO.StringIO()
+        for r in repo:
+            self._debug_print_tags(repo, repo[r], fp=fp)
+        output = fp.getvalue().strip()
+        expected = expected.strip()
+        if expected == output:
+            return
+        expected = expected.splitlines()
+        output = output.splitlines()
+        diff = difflib.unified_diff(expected, output, 'expected', 'output')
+        self.assert_(False, '\n' + '\n'.join(diff))
+
+    def test_tagging_into_tag(self, stupid=False):
+        expected = """\
+node: hg=test@2:svn=branches/test@4
+First tag.
+  test-0.1: hg=test@1:svn=branches/test@3
+
+node: hg=test@3:svn=branches/test@5
+Weird tag.
+  test-0.1: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=test@1:svn=branches/test@3
+
+node: hg=test@4:svn=branches/test@6
+Fix tag pt 1.
+  test-0.1: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=default@-1:svn=unk@unk
+  test-0.1-real: hg=test@1:svn=branches/test@3
+
+node: hg=test@5:svn=branches/test@7
+Remove weird.
+  test-0.1: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=default@-1:svn=unk@unk
+  test-0.1-real: hg=test@1:svn=branches/test@3
+  test-0.1: hg=default@-1:svn=unk@unk
+
+node: hg=test@6:svn=branches/test@8
+Fix tag pt 2.
+  test-0.1: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=test@1:svn=branches/test@3
+  test-0.1/test: hg=default@-1:svn=unk@unk
+  test-0.1-real: hg=test@1:svn=branches/test@3
+  test-0.1: hg=default@-1:svn=unk@unk
+  test-0.1-real: hg=default@-1:svn=unk@unk
+  test-0.1: hg=test@1:svn=branches/test@3
+"""
+        self._test_tags('renametagdir.svndump', expected, stupid=stupid)
+
+    def test_tagging_into_tag_stupid(self):
+        self.test_tagging_into_tag(True)
+
+
+def suite():
+    return unittest.TestLoader().loadTestsFromTestCase(TestTags)
diff --git a/opt/hgsubversion/tests/test_template_keywords.py b/opt/hgsubversion/tests/test_template_keywords.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_template_keywords.py
@@ -0,0 +1,86 @@
+import test_util
+
+import unittest
+
+from mercurial import commands
+from mercurial import error
+from mercurial import ui
+try:
+    from mercurial import templatekw
+    templatekw.keywords
+except ImportError:
+    templatekw = None
+
+try:
+    from mercurial import revset
+    revset.methods
+except ImportError:
+   revset = None
+
+class CapturingUI(ui.ui):
+
+    def __init__(self, *args, **kwds):
+        super(CapturingUI, self).__init__(*args, **kwds)
+        self._output = ""
+
+    def write(self, msg, *args, **kwds):
+        self._output += msg
+
+
+class TestLogKeywords(test_util.TestBase):
+    @test_util.requiresmodule(templatekw)
+    def test_svn_keywords(self):
+        defaults = {'date': None, 'rev': None, 'user': None}
+        repo = self._load_fixture_and_fetch('two_revs.svndump')
+
+        # we want one commit that isn't from Subversion
+        self.commitchanges([('foo', 'foo', 'frobnicate\n')])
+
+        ui = CapturingUI()
+        commands.log(ui, repo, template='{rev}:{svnrev} ', **defaults)
+        self.assertEqual(ui._output, '0:2 1:3 2: ')
+        ui = CapturingUI()
+        commands.log(ui, repo, template='{rev}:{svnpath} ', **defaults)
+        self.assertEqual(ui._output, '0:/trunk 1:/trunk 2: ')
+        ui = CapturingUI()
+        commands.log(ui, repo, template='{rev}:{svnuuid} ', **defaults)
+        self.assertEqual(ui._output,
+                         ('0:df2126f7-00ab-4d49-b42c-7e981dde0bcf '
+                          '1:df2126f7-00ab-4d49-b42c-7e981dde0bcf '
+                          '2: '))
+
+    @test_util.requiresmodule(revset)
+    @test_util.requiresmodule(templatekw)
+    def test_svn_revsets(self):
+        repo = self._load_fixture_and_fetch('two_revs.svndump')
+
+        # we want one commit that isn't from Subversion
+        self.commitchanges([('foo', 'foo', 'frobnicate\n')])
+
+        defaults = {'date': None, 'rev': ['fromsvn()'], 'user': None}
+
+        ui = CapturingUI()
+        commands.log(ui, repo, template='{rev}:{svnrev} ', **defaults)
+        self.assertEqual(ui._output, '0:2 1:3 ')
+
+        defaults = {'date': None, 'rev': ['svnrev(2)'], 'user': None}
+
+        ui = CapturingUI()
+        commands.log(ui, repo, template='{rev}:{svnrev} ', **defaults)
+        self.assertEqual(ui._output, '0:2 ')
+
+        defaults = {'date': None, 'rev': ['fromsvn(1)'], 'user': None}
+
+        self.assertRaises(error.ParseError,
+                          commands.log, self.ui(), repo,
+                          template='{rev}:{svnrev} ', **defaults)
+
+        defaults = {'date': None, 'rev': ['svnrev(1, 2)'], 'user': None}
+
+        self.assertRaises(error.ParseError,
+                          commands.log, self.ui(), repo,
+                          template='{rev}:{svnrev} ', **defaults)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestLogKeywords),]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_unaffected_core.py b/opt/hgsubversion/tests/test_unaffected_core.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_unaffected_core.py
@@ -0,0 +1,92 @@
+import test_util
+
+import os
+import unittest
+
+from mercurial import commands
+from mercurial import dispatch
+from mercurial import error
+from mercurial import hg
+from mercurial import node
+from mercurial import ui
+
+def _dispatch(ui, cmd):
+    try:
+        req = dispatch.request(cmd, ui=ui)
+        dispatch._dispatch(req)
+    except AttributeError:
+        dispatch._dispatch(ui, cmd)
+
+class TestMercurialCore(test_util.TestBase):
+    '''
+    Test that the core Mercurial operations aren't broken by hgsubversion.
+    '''
+
+    @test_util.requiresoption('updaterev')
+    def test_update(self):
+        ''' Test 'clone --updaterev' '''
+        ui = self.ui()
+        _dispatch(ui, ['init', self.wc_path])
+        repo = self.repo
+        repo.ui.setconfig('ui', 'username', 'anonymous')
+
+        fpath = os.path.join(self.wc_path, 'it')
+        f = file(fpath, 'w')
+        f.write('C1')
+        f.flush()
+        commands.add(ui, repo)
+        commands.commit(ui, repo, message="C1")
+        f.write('C2')
+        f.flush()
+        commands.commit(ui, repo, message="C2")
+        f.write('C3')
+        f.flush()
+        commands.commit(ui, repo, message="C3")
+
+        self.assertEqual(len(repo), 3)
+
+        updaterev = 1
+        _dispatch(ui, ['clone', self.wc_path, self.wc_path + '2',
+                                '--updaterev=%s' % updaterev])
+
+        repo2 = hg.repository(ui, self.wc_path + '2')
+
+        self.assertEqual(str(repo[updaterev]), str(repo2['.']))
+
+    @test_util.requiresoption('branch')
+    def test_branch(self):
+        ''' Test 'clone --branch' '''
+        ui = self.ui()
+        _dispatch(ui, ['init', self.wc_path])
+        repo = self.repo
+        repo.ui.setconfig('ui', 'username', 'anonymous')
+
+        fpath = os.path.join(self.wc_path, 'it')
+        f = file(fpath, 'w')
+        f.write('C1')
+        f.flush()
+        commands.add(ui, repo)
+        commands.branch(ui, repo, label="B1")
+        commands.commit(ui, repo, message="C1")
+        f.write('C2')
+        f.flush()
+        commands.branch(ui, repo, label="default")
+        commands.commit(ui, repo, message="C2")
+        f.write('C3')
+        f.flush()
+        commands.branch(ui, repo, label="B2")
+        commands.commit(ui, repo, message="C3")
+
+        self.assertEqual(len(repo), 3)
+
+        branch = 'B1'
+        _dispatch(ui, ['clone', self.wc_path, self.wc_path + '2',
+                                '--branch', branch])
+
+        repo2 = hg.repository(ui, self.wc_path + '2')
+
+        self.assertEqual(repo[branch].hex(), repo2['.'].hex())
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestMercurialCore)]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_urls.py b/opt/hgsubversion/tests/test_urls.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_urls.py
@@ -0,0 +1,79 @@
+import test_util
+
+import unittest
+import urllib
+
+from hgsubversion.svnwrap import parse_url
+from hgsubversion import svnrepo
+
+class TestSubversionUrls(test_util.TestBase):
+    def test_standard_url(self):
+        self.assertEqual((None, None, 'file:///var/svn/repo'),
+                         parse_url('file:///var/svn/repo'))
+
+    def test_user_url(self):
+        self.assertEqual(
+            ('joe', None, 'https://svn.testurl.com/repo'),
+            parse_url('https://joe@svn.testurl.com/repo'))
+        self.assertEqual(
+            ('bob', None, 'https://svn.testurl.com/repo'),
+            parse_url('https://joe@svn.testurl.com/repo', 'bob'))
+
+    def test_password_url(self):
+        self.assertEqual(
+            (None, 't3stpw', 'svn+ssh://svn.testurl.com/repo'),
+            parse_url('svn+ssh://:t3stpw@svn.testurl.com/repo'))
+        self.assertEqual(
+            (None, '123abc', 'svn+ssh://svn.testurl.com/repo'),
+            parse_url('svn+ssh://:t3stpw@svn.testurl.com/repo', None, '123abc'))
+
+    def test_svnssh_preserve_user(self):
+        self.assertEqual(
+            ('user', 't3stpw', 'svn+ssh://user@svn.testurl.com/repo', ),
+            parse_url('svn+ssh://user:t3stpw@svn.testurl.com/repo'))
+        self.assertEqual(
+            ('bob', '123abc', 'svn+ssh://bob@svn.testurl.com/repo', ),
+            parse_url('svn+ssh://user:t3stpw@svn.testurl.com/repo', 'bob', '123abc'))
+        self.assertEqual(
+            ('user2', None, 'svn+ssh://user2@svn.testurl.com/repo', ),
+            parse_url('svn+ssh://user2@svn.testurl.com/repo'))
+        self.assertEqual(
+            ('bob', None, 'svn+ssh://bob@svn.testurl.com/repo', ),
+            parse_url('svn+ssh://user2@svn.testurl.com/repo', 'bob'))
+
+    def test_user_password_url(self):
+        self.assertEqual(
+            ('joe', 't3stpw', 'https://svn.testurl.com/repo'),
+            parse_url('https://joe:t3stpw@svn.testurl.com/repo'))
+        self.assertEqual(
+            ('bob', '123abc', 'https://svn.testurl.com/repo'),
+            parse_url('https://joe:t3stpw@svn.testurl.com/repo', 'bob', '123abc'))
+
+    def test_url_rewriting(self):
+        ui = test_util.ui.ui()
+        ui.setconfig('hgsubversion', 'username', 'bob')
+        repo = svnrepo.svnremoterepo(ui, 'svn+ssh://joe@foo/bar')
+        self.assertEqual('svn+ssh://bob@foo/bar', repo.svnauth[0])
+
+        repo = svnrepo.svnremoterepo(ui, 'svn+http://joe@foo/bar')
+        self.assertEqual(('http://foo/bar', 'bob', None), repo.svnauth)
+
+        repo = svnrepo.svnremoterepo(ui, 'svn+https://joe@foo/bar')
+        self.assertEqual(('https://foo/bar', 'bob', None), repo.svnauth)
+
+    def test_quoting(self):
+        ui = self.ui()
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'non_ascii_path_1.svndump')
+
+        repo_url = test_util.fileurl(self.repo_path)
+        subdir = '/b\xC3\xB8b'
+        quoted_subdir = urllib.quote(subdir)
+
+        repo1 = svnrepo.svnremoterepo(ui, repo_url + subdir)
+        repo2 = svnrepo.svnremoterepo(ui, repo_url + quoted_subdir)
+        self.assertEqual(repo1.svnurl, repo2.svnurl)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestSubversionUrls)]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tests/test_util.py b/opt/hgsubversion/tests/test_util.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_util.py
@@ -0,0 +1,487 @@
+import StringIO
+import difflib
+import errno
+import gettext
+import imp
+import os
+import shutil
+import stat
+import subprocess
+import sys
+import tempfile
+import unittest
+import urllib
+
+_rootdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, _rootdir)
+
+from mercurial import cmdutil
+from mercurial import commands
+from mercurial import context
+from mercurial import dispatch as dispatchmod
+from mercurial import hg
+from mercurial import i18n
+from mercurial import node
+from mercurial import ui
+from mercurial import util
+from mercurial import extensions
+
+try:
+    SkipTest = unittest.SkipTest
+except AttributeError:
+    try:
+        from unittest2 import SkipTest
+    except ImportError:
+        try:
+            from nose import SkipTest
+        except ImportError:
+            SkipTest = None
+
+from hgsubversion import util
+
+# Documentation for Subprocess.Popen() says:
+#   "Note that on Windows, you cannot set close_fds to true and
+#   also redirect the standard handles by setting stdin, stdout or
+#   stderr."
+canCloseFds='win32' not in sys.platform
+
+if not 'win32' in sys.platform:
+    def kill_process(popen_obj):
+        os.kill(popen_obj.pid, 9)
+else:
+    import ctypes
+    from ctypes.wintypes import BOOL, DWORD, HANDLE, UINT
+
+    def win_status_check(result, func, args):
+        if result == 0:
+            raise ctypes.WinError()
+        return args
+
+    def WINAPI(returns, func, *params):
+        assert len(params) % 2 == 0
+
+        func.argtypes = tuple(params[0::2])
+        func.resvalue = returns
+        func.errcheck = win_status_check
+
+        return func
+
+    # dwDesiredAccess
+    PROCESS_TERMINATE = 0x0001
+
+    OpenProcess = WINAPI(HANDLE, ctypes.windll.kernel32.OpenProcess,
+        DWORD, 'dwDesiredAccess',
+        BOOL, 'bInheritHandle',
+        DWORD, 'dwProcessId',
+    )
+
+    CloseHandle =  WINAPI(BOOL, ctypes.windll.kernel32.CloseHandle,
+        HANDLE, 'hObject'
+    )
+
+    TerminateProcess = WINAPI(BOOL, ctypes.windll.kernel32.TerminateProcess,
+        HANDLE, 'hProcess',
+        UINT, 'uExitCode'
+    )
+
+    def kill_process(popen_obj):
+        phnd = OpenProcess(PROCESS_TERMINATE, False, popen_obj.pid)
+        TerminateProcess(phnd, 1)
+        CloseHandle(phnd)
+
+# Fixtures that need to be pulled at a subdirectory of the repo path
+subdir = {'truncatedhistory.svndump': '/project2',
+          'fetch_missing_files_subdir.svndump': '/foo',
+          'empty_dir_in_trunk_not_repo_root.svndump': '/project',
+          'project_root_not_repo_root.svndump': '/dummyproj',
+          'project_name_with_space.svndump': '/project name',
+          'non_ascii_path_1.svndump': '/b\xC3\xB8b',
+          'non_ascii_path_2.svndump': '/b%C3%B8b',
+          }
+
+FIXTURES = os.path.join(os.path.abspath(os.path.dirname(__file__)),
+                        'fixtures')
+
+
+def _makeskip(name, message):
+    if SkipTest:
+        def skip(*args, **kwargs):
+            raise SkipTest(message)
+        skip.__name__ = name
+        return skip
+
+def requiresmodule(mod):
+    """Skip a test if the specified module is not None."""
+    def decorator(fn):
+        if fn is None:
+            return
+        if mod is not None:
+            return fn
+        return _makeskip(fn.__name__, 'missing required feature')
+    return decorator
+
+
+def requiresoption(option):
+    '''Skip a test if commands.clone does not take the specified option.'''
+    def decorator(fn):
+        for entry in cmdutil.findcmd('clone', commands.table)[1][1]:
+            if entry[1] == option:
+                return fn
+        # no match found, so skip
+        if SkipTest:
+            return _makeskip(fn.__name__,
+                             'test requires clone to accept %s' % option)
+        # no skipping support, so erase decorated method
+        return
+    if not isinstance(option, str):
+        raise TypeError('requiresoption takes a string argument')
+    return decorator
+
+def filtermanifest(manifest):
+    return [f for f in manifest if f not in util.ignoredfiles]
+
+def fileurl(path):
+    path = os.path.abspath(path).replace(os.sep, '/')
+    drive, path = os.path.splitdrive(path)
+    if drive:
+        drive = '/' + drive
+    url = 'file://%s%s' % (drive, path)
+    return url
+
+def testui(stupid=False, layout='auto', startrev=0):
+    u = ui.ui()
+    bools = {True: 'true', False: 'false'}
+    u.setconfig('ui', 'quiet', bools[True])
+    u.setconfig('extensions', 'hgsubversion', '')
+    u.setconfig('hgsubversion', 'stupid', bools[stupid])
+    u.setconfig('hgsubversion', 'layout', layout)
+    u.setconfig('hgsubversion', 'startrev', startrev)
+    return u
+
+def load_svndump_fixture(path, fixture_name):
+    '''Loads an svnadmin dump into a fresh repo at path, which should not
+    already exist.
+    '''
+    if os.path.exists(path): rmtree(path)
+    subprocess.call(['svnadmin', 'create', path,],
+                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    inp = open(os.path.join(FIXTURES, fixture_name))
+    proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=inp,
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    proc.communicate()
+
+def dispatch(cmd):
+    try:
+        req = dispatchmod.request(cmd)
+        dispatchmod.dispatch(req)
+    except AttributeError, e:
+        dispatchmod.dispatch(cmd)
+
+def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False,
+                           subdir='', noupdate=True, layout='auto',
+                           startrev=0, externals=None):
+    load_svndump_fixture(repo_path, fixture_name)
+    if subdir:
+        repo_path += '/' + subdir
+
+    cmd = [
+        'clone',
+        '--layout=%s' % layout,
+        '--startrev=%s' % startrev,
+        fileurl(repo_path),
+        wc_path,
+    ]
+    if stupid:
+        cmd.append('--stupid')
+    if noupdate:
+        cmd.append('--noupdate')
+    if externals:
+        cmd[:0] = ['--config', 'hgsubversion.externals=%s' % externals]
+
+    dispatch(cmd)
+
+    return hg.repository(testui(), wc_path)
+
+def rmtree(path):
+    # Read-only files cannot be removed under Windows
+    for root, dirs, files in os.walk(path):
+        for f in files:
+            f = os.path.join(root, f)
+            try:
+                s = os.stat(f)
+            except OSError, e:
+                if e.errno == errno.ENOENT:
+                    continue
+                raise
+            if (s.st_mode & stat.S_IWRITE) == 0:
+                os.chmod(f, s.st_mode | stat.S_IWRITE)
+    shutil.rmtree(path)
+
+def _verify_our_modules():
+    '''
+    Verify that hgsubversion was imported from the correct location.
+
+    The correct location is any location within the parent directory of the
+    directory containing this file.
+    '''
+
+    for modname, module in sys.modules.iteritems():
+        if not module or not modname.startswith('hgsubversion.'):
+            continue
+
+        modloc = module.__file__
+        cp = os.path.commonprefix((os.path.abspath(__file__), modloc))
+        assert cp.rstrip(os.sep) == _rootdir, (
+            'Module location verification failed: hgsubversion was imported '
+            'from the wrong path!'
+        )
+
+def hgclone(ui, source, dest, update=True):
+    if getattr(hg, 'peer', None):
+        # Since 1.9 (d976542986d2)
+        src, dest = hg.clone(ui, {}, source, dest, update=update)
+    else:
+        src, dest = hg.clone(ui, source, dest, update=update)
+    return src, dest
+
+class TestBase(unittest.TestCase):
+    def setUp(self):
+        _verify_our_modules()
+
+        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)
+
+        self.oldwd = os.getcwd()
+        self.tmpdir = tempfile.mkdtemp(
+            'svnwrap_test', dir=os.environ.get('HGSUBVERSION_TEST_TEMP', None))
+        self.hgrc = os.path.join(self.tmpdir, '.hgrc')
+        os.environ['HGRCPATH'] = self.hgrc
+        rc = open(self.hgrc, 'w')
+        for l in '[extensions]', 'hgsubversion=':
+            print >> rc, l
+
+        self.repo_path = '%s/testrepo' % self.tmpdir
+        self.wc_path = '%s/testrepo_wc' % self.tmpdir
+        self.svn_wc = None
+
+        # Previously, we had a MockUI class that wrapped ui, and giving access
+        # to the stream. The ui.pushbuffer() and ui.popbuffer() can be used
+        # instead. Using the regular UI class, with all stderr redirected to
+        # stdout ensures that the test setup is much more similar to usage
+        # setups.
+        self.patch = (ui.ui.write_err, ui.ui.write)
+        setattr(ui.ui, self.patch[0].func_name, self.patch[1])
+
+    def tearDown(self):
+        for var, val in self.oldenv.iteritems():
+            if val is None:
+                del os.environ[var]
+            else:
+                os.environ[var] = val
+        i18n.t = self.oldt
+        rmtree(self.tmpdir)
+        os.chdir(self.oldwd)
+        setattr(ui.ui, self.patch[0].func_name, self.patch[0])
+
+        _verify_our_modules()
+
+    def ui(self, stupid=False, layout='auto'):
+        return testui(stupid, layout)
+
+    def _load_fixture_and_fetch(self, fixture_name, subdir=None, stupid=False,
+                                layout='auto', startrev=0, externals=None):
+        if layout == 'single':
+            if subdir is None:
+                subdir = 'trunk'
+        elif subdir is None:
+            subdir = ''
+        return load_fixture_and_fetch(fixture_name, self.repo_path,
+                                      self.wc_path, subdir=subdir,
+                                      stupid=stupid, layout=layout,
+                                      startrev=startrev, externals=externals)
+
+    def _add_svn_rev(self, changes):
+        '''changes is a dict of filename -> contents'''
+        if self.svn_wc is None:
+            self.svn_wc = os.path.join(self.tmpdir, 'testsvn_wc')
+            subprocess.call([
+                'svn', 'co', '-q', fileurl(self.repo_path),
+                self.svn_wc
+            ],
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+
+        for filename, contents in changes.iteritems():
+            # filenames are / separated
+            filename = filename.replace('/', os.path.sep)
+            filename = os.path.join(self.svn_wc, filename)
+            open(filename, 'w').write(contents)
+            # may be redundant
+            subprocess.call(['svn', 'add', '-q', filename],
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        subprocess.call([
+            'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes'],
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+
+    # define this as a property so that it reloads anytime we need it
+    @property
+    def repo(self):
+        return hg.repository(testui(), self.wc_path)
+
+    def pushrevisions(self, stupid=False, expected_extra_back=0):
+        before = len(self.repo)
+        self.repo.ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        res = commands.push(self.repo.ui, self.repo)
+        after = len(self.repo)
+        self.assertEqual(expected_extra_back, after - before)
+        return res
+
+    def svnls(self, path, rev='HEAD'):
+        path = self.repo_path + '/' + path
+        path = util.normalize_url(fileurl(path))
+        args = ['svn', 'ls', '-r', rev, '-R', path]
+        p = subprocess.Popen(args,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT)
+        stdout, stderr = p.communicate()
+        if p.returncode:
+            raise Exception('svn ls failed on %s: %r' % (path, stderr))
+        entries = [e.strip('/') for e in stdout.splitlines()]
+        entries.sort()
+        return entries
+
+    def svnco(self, svnpath, rev, path):
+        path = os.path.join(self.wc_path, path)
+        subpath = os.path.dirname(path)
+        if not os.path.isdir(subpath):
+            os.makedirs(subpath)
+        svnpath = fileurl(self.repo_path + '/' + svnpath)
+        args = ['svn', 'co', '-r', rev, svnpath, path]
+        p = subprocess.Popen(args,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT)
+        stdout, stderr = p.communicate()
+        if p.returncode:
+            raise Exception('svn co failed on %s: %r' % (svnpath, stderr))
+
+    def svnpropget(self, path, prop, rev='HEAD'):
+        path = self.repo_path + '/' + path
+        path = util.normalize_url(fileurl(path))
+        args = ['svn', 'propget', '-r', str(rev), prop, path]
+        p = subprocess.Popen(args,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT)
+        stdout, stderr = p.communicate()
+        if p.returncode:
+            raise Exception('svn ls failed on %s: %r' % (path, stderr))
+        return stdout.strip()
+
+    def commitchanges(self, changes, parent='tip', message='automated test'):
+        """Commit changes to mercurial directory
+
+        'changes' is a sequence of tuples (source, dest, data). It can look
+        like:
+        - (source, source, data) to set source content to data
+        - (source, dest, None) to set dest content to source one, and mark it as
+        copied from source.
+        - (source, dest, data) to set dest content to data, and mark it as copied
+        from source.
+        - (source, None, None) to remove source.
+        """
+        repo = self.repo
+        parentctx = repo[parent]
+
+        changed, removed = [], []
+        for source, dest, newdata in changes:
+            if dest is None:
+                removed.append(source)
+            else:
+                changed.append(dest)
+
+        def filectxfn(repo, memctx, path):
+            if path in removed:
+                raise IOError(errno.ENOENT,
+                              "File \"%s\" no longer exists" % path)
+            entry = [e for e in changes if path == e[1]][0]
+            source, dest, newdata = entry
+            if newdata is None:
+                newdata = parentctx[source].data()
+            copied = None
+            if source != dest:
+                copied = source
+            return context.memfilectx(path=dest,
+                                      data=newdata,
+                                      islink=False,
+                                      isexec=False,
+                                      copied=copied)
+
+        ctx = context.memctx(repo,
+                             (parentctx.node(), node.nullid),
+                             message,
+                             changed + removed,
+                             filectxfn,
+                             'an_author',
+                             '2008-10-07 20:59:48 -0500')
+        nodeid = repo.commitctx(ctx)
+        repo = self.repo
+        hg.clean(repo, nodeid)
+        return nodeid
+
+    def assertchanges(self, changes, ctx):
+        """Assert that all 'changes' (as in defined in commitchanged())
+        went into ctx.
+        """
+        for source, dest, data in changes:
+            if dest is None:
+                self.assertTrue(source not in ctx)
+                continue
+            self.assertTrue(dest in ctx)
+            if data is None:
+                data = ctx.parents()[0][source].data()
+            self.assertEqual(ctx[dest].data(), data)
+            if dest != source:
+                copy = ctx[dest].renamed()
+                self.assertEqual(copy[0], source)
+
+    def assertMultiLineEqual(self, first, second, msg=None):
+        """Assert that two multi-line strings are equal. (Based on Py3k code.)
+        """
+        try:
+            return super(TestBase, self).assertMultiLineEqual(first, second,
+                                                              msg)
+        except AttributeError:
+            pass
+
+        self.assert_(isinstance(first, str),
+                     ('First argument is not a string'))
+        self.assert_(isinstance(second, str),
+                     ('Second argument is not a string'))
+
+        if first != second:
+            diff = ''.join(difflib.unified_diff(first.splitlines(True),
+                                                second.splitlines(True),
+                                                fromfile='a',
+                                                tofile='b'))
+            msg = '%s\n%s' % (msg or '', diff)
+            raise self.failureException, msg
+
+    def draw(self, repo):
+        """Helper function displaying a repository graph, especially
+        useful when debugging comprehensive tests.
+        """
+        # Could be more elegant, but it works with stock hg
+        _ui = ui.ui()
+        _ui.setconfig('extensions', 'graphlog', '')
+        extensions.loadall(_ui)
+        graphlog = extensions.find('graphlog')
+        templ = """\
+changeset: {rev}:{node|short}
+branch:    {branches}
+tags:      {tags}
+summary:   {desc|firstline}
+files:     {files}
+
+"""
+        graphlog.graphlog(_ui, repo, rev=None, template=templ)
diff --git a/opt/hgsubversion/tests/test_utility_commands.py b/opt/hgsubversion/tests/test_utility_commands.py
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tests/test_utility_commands.py
@@ -0,0 +1,256 @@
+import test_util
+
+import os
+import unittest
+
+from hgext import rebase
+from mercurial import hg
+from mercurial import revlog
+from mercurial import context
+from mercurial import node
+from mercurial import commands
+from mercurial import util as hgutil
+
+from hgsubversion import util
+from hgsubversion import svncommands
+from hgsubversion import wrappers
+
+expected_info_output = '''URL: %(repourl)s/%(branch)s
+Repository Root: %(repourl)s
+Repository UUID: df2126f7-00ab-4d49-b42c-7e981dde0bcf
+Revision: %(rev)s
+Node Kind: directory
+Last Changed Author: durin
+Last Changed Rev: %(rev)s
+Last Changed Date: %(date)s
+'''
+
+class UtilityTests(test_util.TestBase):
+    @property
+    def repourl(self):
+        return util.normalize_url(test_util.fileurl(self.repo_path))
+
+    def test_info_output(self):
+        self._load_fixture_and_fetch('two_heads.svndump')
+        hg.update(self.repo, 'the_branch')
+        u = self.ui()
+        u.pushbuffer()
+        svncommands.info(u, self.repo)
+        actual = u.popbuffer()
+        expected = (expected_info_output %
+                    {'date': '2008-10-08 01:39:05 +0000 (Wed, 08 Oct 2008)',
+                     'repourl': self.repourl,
+                     'branch': 'branches/the_branch',
+                     'rev': 5,
+                     })
+        self.assertMultiLineEqual(actual, expected)
+        hg.update(self.repo, 'default')
+        u.pushbuffer()
+        svncommands.info(u, self.repo)
+        actual = u.popbuffer()
+        expected = (expected_info_output %
+                    {'date': '2008-10-08 01:39:29 +0000 (Wed, 08 Oct 2008)',
+                     'repourl': self.repourl,
+                     'branch': 'trunk',
+                     'rev': 6,
+                     })
+        self.assertMultiLineEqual(actual, expected)
+        hg.update(self.repo, 'default')
+        u.pushbuffer()
+        svncommands.info(u, self.repo, rev=3)
+        actual = u.popbuffer()
+        expected = (expected_info_output %
+                    {'date': '2008-10-08 01:39:05 +0000 (Wed, 08 Oct 2008)',
+                     'repourl': self.repourl,
+                     'branch': 'branches/the_branch',
+                     'rev': 5,
+                     })
+        self.assertMultiLineEqual(actual, expected)
+
+    def test_info_single(self):
+        self._load_fixture_and_fetch('two_heads.svndump', subdir='trunk')
+        hg.update(self.repo, 'tip')
+        u = self.ui()
+        u.pushbuffer()
+        svncommands.info(u, self.repo)
+        actual = u.popbuffer()
+        expected = (expected_info_output %
+                    {'date': '2008-10-08 01:39:29 +0000 (Wed, 08 Oct 2008)',
+                     'repourl': self.repourl,
+                     'branch': 'trunk',
+                     'rev': 6,
+                     })
+        self.assertMultiLineEqual(expected, actual)
+
+    def test_missing_metadata(self):
+        self._load_fixture_and_fetch('two_heads.svndump')
+        test_util.rmtree(self.repo.join('svn'))
+        self.assertRaises(hgutil.Abort,
+                          self.repo.svnmeta)
+        self.assertRaises(hgutil.Abort,
+                          svncommands.info,
+                          self.ui(), repo=self.repo, args=[])
+        self.assertRaises(hgutil.Abort,
+                          svncommands.genignore,
+                          self.ui(), repo=self.repo, args=[])
+
+        os.remove(self.repo.join('hgrc'))
+        self.assertRaises(hgutil.Abort,
+                          self.repo.svnmeta)
+        self.assertRaises(hgutil.Abort,
+                          svncommands.info,
+                          self.ui(), repo=self.repo, args=[])
+        self.assertRaises(hgutil.Abort,
+                          svncommands.genignore,
+                          self.ui(), repo=self.repo, args=[])
+
+        self.assertRaises(hgutil.Abort,
+                          svncommands.rebuildmeta,
+                          self.ui(), repo=self.repo, args=[])
+
+    def test_parent_output(self):
+        self._load_fixture_and_fetch('two_heads.svndump')
+        u = self.ui()
+        u.pushbuffer()
+        parents = (self.repo['the_branch'].node(), revlog.nullid, )
+        def filectxfn(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='added',
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        ctx = context.memctx(self.repo,
+                             parents,
+                             'automated test',
+                             ['added_bogus_file', 'other_added_file', ],
+                             filectxfn,
+                             'testy',
+                             '2008-12-21 16:32:00 -0500',
+                             {'branch': 'localbranch', })
+        new = self.repo.commitctx(ctx)
+        hg.update(self.repo, new)
+        wrappers.parents(lambda x, y: None, u, self.repo, svn=True)
+        actual = u.popbuffer()
+        self.assertEqual(actual, '3:4e256962fc5d\n')
+
+        hg.update(self.repo, 'default')
+
+        # Make sure styles work
+        u.pushbuffer()
+        wrappers.parents(lambda x, y: None, u, self.repo, svn=True, style='compact')
+        actual = u.popbuffer()
+        self.assertEqual(actual, '4:1083037b18d8\n')
+
+        # custom templates too
+        u.pushbuffer()
+        wrappers.parents(lambda x, y: None, u, self.repo, svn=True, template='{node}\n')
+        actual = u.popbuffer()
+        self.assertEqual(actual, '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n')
+
+        u.pushbuffer()
+        wrappers.parents(lambda x, y: None, u, self.repo, svn=True)
+        actual = u.popbuffer()
+        self.assertEqual(actual, '4:1083037b18d8\n')
+
+    def test_outgoing_output(self):
+        self._load_fixture_and_fetch('two_heads.svndump')
+        u = self.ui()
+        parents = (self.repo['the_branch'].node(), revlog.nullid, )
+        def filectxfn(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='added',
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        ctx = context.memctx(self.repo,
+                             parents,
+                             'automated test',
+                             ['added_bogus_file', 'other_added_file', ],
+                             filectxfn,
+                             'testy',
+                             '2008-12-21 16:32:00 -0500',
+                             {'branch': 'localbranch', })
+        new = self.repo.commitctx(ctx)
+        hg.update(self.repo, new)
+        u.pushbuffer()
+        commands.outgoing(u, self.repo, self.repourl)
+        actual = u.popbuffer()
+        self.assertTrue(node.hex(self.repo['localbranch'].node())[:8] in actual)
+        self.assertEqual(actual.strip(), '5:6de15430fa20')
+        hg.update(self.repo, 'default')
+        u.pushbuffer()
+        commands.outgoing(u, self.repo, self.repourl)
+        actual = u.popbuffer()
+        self.assertEqual(actual, '')
+
+    def test_rebase(self):
+        self._load_fixture_and_fetch('two_revs.svndump')
+        parents = (self.repo[0].node(), revlog.nullid, )
+        def filectxfn(repo, memctx, path):
+            return context.memfilectx(path=path,
+                                      data='added',
+                                      islink=False,
+                                      isexec=False,
+                                      copied=False)
+        ctx = context.memctx(self.repo,
+                             parents,
+                             'automated test',
+                             ['added_bogus_file', 'other_added_file', ],
+                             filectxfn,
+                             'testy',
+                             '2008-12-21 16:32:00 -0500',
+                             {'branch': 'localbranch', })
+        self.repo.commitctx(ctx)
+        self.assertEqual(self.repo['tip'].branch(), 'localbranch')
+        beforerebasehash = self.repo['tip'].node()
+        hg.update(self.repo, 'tip')
+        wrappers.rebase(rebase.rebase, self.ui(), self.repo, svn=True)
+        self.assertEqual(self.repo['tip'].branch(), 'localbranch')
+        self.assertEqual(self.repo['tip'].parents()[0].parents()[0], self.repo[0])
+        self.assertNotEqual(beforerebasehash, self.repo['tip'].node())
+
+    def test_genignore(self):
+        """ Test generation of .hgignore file. """
+        test_util.load_fixture_and_fetch('ignores.svndump', self.repo_path,
+                                         self.wc_path, noupdate=False)
+        u = self.ui()
+        u.pushbuffer()
+        svncommands.genignore(u, self.repo, self.wc_path)
+        self.assertMultiLineEqual(open(os.path.join(self.wc_path, '.hgignore')).read(),
+                         '.hgignore\nsyntax:glob\nblah\notherblah\nbaz/magic\n')
+
+    def test_genignore_single(self):
+        self._load_fixture_and_fetch('ignores.svndump', subdir='trunk')
+        hg.update(self.repo, 'tip')
+        u = self.ui()
+        u.pushbuffer()
+        svncommands.genignore(u, self.repo, self.wc_path)
+        self.assertMultiLineEqual(open(os.path.join(self.wc_path, '.hgignore')).read(),
+                               '.hgignore\nsyntax:glob\nblah\notherblah\nbaz/magic\n')
+
+    def test_list_authors(self):
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'replace_trunk_with_branch.svndump')
+        u = self.ui()
+        u.pushbuffer()
+        svncommands.listauthors(u,
+                                     args=[test_util.fileurl(self.repo_path)],
+                                     authors=None)
+        actual = u.popbuffer()
+        self.assertMultiLineEqual(actual, 'Augie\nevil\n')
+
+
+    def test_list_authors_map(self):
+        test_util.load_svndump_fixture(self.repo_path,
+                                       'replace_trunk_with_branch.svndump')
+        author_path = os.path.join(self.repo_path, 'authors')
+        svncommands.listauthors(self.ui(),
+                                     args=[test_util.fileurl(self.repo_path)],
+                                     authors=author_path)
+        self.assertMultiLineEqual(open(author_path).read(), 'Augie=\nevil=\n')
+
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(UtilityTests),
+          ]
+    return unittest.TestSuite(all)
diff --git a/opt/hgsubversion/tools/bisect-find-bad.sh b/opt/hgsubversion/tools/bisect-find-bad.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tools/bisect-find-bad.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+. $(dirname $0)/common.sh
+hg svn verify
+exit $?
diff --git a/opt/hgsubversion/tools/common.sh b/opt/hgsubversion/tools/common.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tools/common.sh
@@ -0,0 +1,26 @@
+function verify_current_revision()
+{
+    /bin/rm -rf *
+    exportcmd="svn export `hg svn info 2> /dev/null | grep '^URL: ' | sed 's/URL: //'`@`hg svn info | grep ^Revision | sed 's/.*: //;s/ .*//'` . --force"
+    `echo $exportcmd` > /dev/null
+    x=$?
+    if [[ "$x" != "0" ]] ; then
+        echo $exportcmd
+        echo 'export failed!'
+        return 255
+    fi
+    if [[ "`hg st | wc -l | python -c 'import sys; print sys.stdin.read().strip()'`" == "0" ]] ; then
+        return 0
+    else
+        if [[ $1 != "keep" ]] ; then
+            revert_all_files
+        fi
+        return 1
+    fi
+}
+
+function revert_all_files()
+{
+    hg revert --all
+    hg purge
+}
diff --git a/opt/hgsubversion/tools/verify-all-heads.sh b/opt/hgsubversion/tools/verify-all-heads.sh
new file mode 100644
--- /dev/null
+++ b/opt/hgsubversion/tools/verify-all-heads.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+. $(dirname $0)/common.sh
+
+for b in `hg branches -aq` ; do
+    hg co $b || break
+    echo verifying $b
+    hg svn verify
+done
