Release 0.4.13.
[jelmer/subvertpy.git] / __init__.py
index c9323bd0a6bb21faa6c52f45f971bec0e29fe03e..74d756a1d2081a24e72b6fddc3793438e4061e73 100644 (file)
@@ -2,7 +2,7 @@
 
 # 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
+# the Free Software Foundation; either version 3 of the License, or
 # (at your option) any later version.
 
 # This program is distributed in the hope that it will be useful,
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-"""
-Support for Subversion branches
+"""Support for Subversion branches
+
+Bazaar can be used with Subversion branches through the bzr-svn plugin.
+
+Most Bazaar commands should work fine with Subversion branches. To 
+create new branches in Subversion using push, it is currently necessary
+to use the svn-push command rather than the standard push command.
+
+bzr-svn also adds two new commands to Bazaar:
+
+ - bzr svn-import
+ - bzr dpush
+
+For more information about bzr-svn, see the bzr-svn FAQ.
+
 """
 import bzrlib
+from bzrlib import log
 from bzrlib.bzrdir import BzrDirFormat, format_registry
+from bzrlib.errors import BzrError
 from bzrlib.commands import Command, register_command, display_command, Option
 from bzrlib.help_topics import topic_registry
 from bzrlib.revisionspec import SPEC_TYPES
-from bzrlib.trace import warning
+from bzrlib.trace import warning, mutter
 from bzrlib.transport import register_lazy_transport, register_transport_proto
 
-import format
-import revspec
+import os
 
 # versions ending in 'exp' mean experimental mappings
 # versions ending in 'dev' mean development version
 # versions ending in 'final' mean release (well tested, etc)
-version_info = (0, 4, 9, 'dev', 0)
+version_info = (0, 4, 13, 'final', 0)
 
 if version_info[3] == 'final':
     version_string = '%d.%d.%d' % version_info[:3]
@@ -39,7 +53,7 @@ else:
     version_string = '%d.%d.%d%s%d' % version_info
 __version__ = version_string
 
-COMPATIBLE_BZR_VERSIONS = [(1, 3)]
+COMPATIBLE_BZR_VERSIONS = [(1, 6), (1, 7)]
 
 def check_bzrlib_version(desired):
     """Check that bzrlib is compatible.
@@ -48,19 +62,17 @@ def check_bzrlib_version(desired):
     If version is compatible version + 1, assume compatible, with deprecations
     Otherwise, assume incompatible.
     """
-    import bzrlib
     bzrlib_version = bzrlib.version_info[:2]
     if (bzrlib_version in desired or 
         ((bzrlib_version[0], bzrlib_version[1]-1) in desired and 
-         bzrlib.version_info[3] == 'dev')):
+         bzrlib.version_info[3] in ('dev', 'exp'))):
         return
-    from bzrlib.errors import BzrError
     if bzrlib_version < desired[0]:
         raise BzrError('Installed bzr version %s is too old to be used with bzr-svn, at least %s.%s required' % (bzrlib.__version__, desired[0][0], desired[0][1]))
     else:
         warning('bzr-svn is not up to date with installed bzr version %s.'
-                ' \nThere should be a newer version of bzr-svn available.' 
-                % (bzrlib.__version__))
+                ' \nThere should be a newer version of bzr-svn available.',
+                bzrlib.__version__)
         if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
             raise BzrError('Version mismatch')
 
@@ -68,20 +80,55 @@ def check_subversion_version():
     """Check that Subversion is compatible.
 
     """
+    def check_mtime(m):
+        """Check whether a C extension is out of date."""
+        (base, _) = os.path.splitext(m.__file__)
+        c_file = "%s.c" % base
+        if not os.path.exists(c_file):
+            return True
+        if os.path.getmtime(m.__file__) < os.path.getmtime(c_file):
+            return False
+        return True
     try:
-        import svn.delta
+        from bzrlib.plugins.svn import client, ra, repos, wc
+        for x in client, ra, repos, wc:
+            if not check_mtime(x):
+                warning("bzr-svn extensions are outdated and need to be rebuilt")
+                break
     except ImportError:
-        warning('No Python bindings for Subversion installed. See the '
-                'bzr-svn README for details.')
-        raise bzrlib.errors.BzrError("missing python subversion bindings")
-    if (not hasattr(svn.delta, 'svn_delta_invoke_txdelta_window_handler') and 
-        not hasattr(svn.delta, 'tx_invoke_window_handler')):
-        warning('Installed Subversion version does not have updated Python '
-                'bindings. See the bzr-svn README for details.')
-        raise bzrlib.errors.BzrError("incompatible python subversion bindings")
+        warning("Unable to load bzr-svn extensions - did you build it?")
+        raise
+    ra_version = ra.version()
+    if (ra_version[0] >= 5 and getattr(ra, 'SVN_REVISION', None) and 
+        27729 <= ra.SVN_REVISION < 31470):
+        warning('Installed Subversion has buggy svn.ra.get_log() '
+                'implementation, please install newer.')
+
+    mutter("bzr-svn: using Subversion %d.%d.%d (%s)" % ra_version)
+
+
+def check_rebase_version(min_version):
+    """Check what version of bzr-rebase is installed.
+
+    Raises an exception when the version installed is older than 
+    min_version.
+
+    :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
+    """
+    from bzrlib.plugins.svn.errors import RebaseNotPresent
+    try:
+        from bzrlib.plugins.rebase import version_info as rebase_version_info
+        if rebase_version_info[:2] < min_version:
+            raise RebaseNotPresent("Version %r present, at least %r required" 
+                                   % (rebase_version_info, min_version))
+    except ImportError, e:
+        raise RebaseNotPresent(e)
+
 
 check_subversion_version()
 
+from bzrlib.plugins.svn import format, revspec
+
 register_transport_proto('svn+ssh://', 
     help="Access using the Subversion smart server tunneled over SSH.")
 register_transport_proto('svn+file://', 
@@ -97,7 +144,7 @@ register_lazy_transport('svn://', 'bzrlib.plugins.svn.transport',
 register_lazy_transport('svn+', 'bzrlib.plugins.svn.transport', 
                         'SvnRaTransport')
 topic_registry.register_lazy('svn-branching-schemes', 
-                             'bzrlib.plugins.svn.scheme',
+                             'bzrlib.plugins.svn.mapping3.scheme',
                              'help_schemes', 'Subversion branching schemes')
 
 BzrDirFormat.register_control_format(format.SvnRemoteFormat)
@@ -110,30 +157,33 @@ format_registry.register("subversion-wc", format.SvnWorkingTreeDirFormat,
                          native=False, hidden=True)
 SPEC_TYPES.append(revspec.RevisionSpec_svn)
 
-versions_checked = False
+log.properties_handler_registry.register_lazy("subversion",
+                                              "bzrlib.plugins.svn.log",
+                                              "show_subversion_properties")
+
+_versions_checked = False
 def lazy_check_versions():
     """Check whether all dependencies have the right versions.
     
     :note: Only checks once, caches the result."""
-    global versions_checked
-    if versions_checked:
+    global _versions_checked
+    if _versions_checked:
         return
-    versions_checked = True
+    _versions_checked = True
     check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
 
-optimizers_registered = False
+_optimizers_registered = False
 def lazy_register_optimizers():
     """Register optimizers for fetching between Subversion and Bazaar 
     repositories.
     
     :note: Only registers on the first call."""
-    global optimizers_registered
-    if optimizers_registered:
+    global _optimizers_registered
+    if _optimizers_registered:
         return
     from bzrlib.repository import InterRepository
-    import commit
-    import fetch
-    optimizers_registered = True
+    from bzrlib.plugins.svn import commit, fetch
+    _optimizers_registered = True
     InterRepository.register_optimiser(fetch.InterFromSvnRepository)
     InterRepository.register_optimiser(commit.InterToSvnRepository)
 
@@ -145,7 +195,7 @@ def get_scheme(schemename):
     """
     if isinstance(schemename, unicode):
         schemename = schemename.encode("ascii")
-    from scheme import BranchingScheme
+    from bzrlib.plugins.svn.mapping3.scheme import BranchingScheme
     from bzrlib.errors import BzrCommandError
     
     ret = BranchingScheme.find_scheme(schemename)
@@ -157,6 +207,9 @@ def get_scheme(schemename):
 class cmd_svn_import(Command):
     """Convert a Subversion repository to a Bazaar repository.
     
+    To save disk space, only branches will be created by default 
+    (no working trees). To create a tree for a branch, run "bzr co" in 
+    it.
     """
     takes_args = ['from_location', 'to_location?']
     takes_options = [Option('trees', help='Create working trees.'),
@@ -167,6 +220,10 @@ class cmd_svn_import(Command):
                      Option('scheme', type=get_scheme,
                          help='Branching scheme (none, trunk, etc). '
                               'Default: auto.'),
+                     Option('keep', 
+                         help="Don't delete branches removed in Subversion."),
+                     Option('incremental',
+                         help="Import revisions incrementally."),
                      Option('prefix', type=str, 
                          help='Only consider branches of which path starts '
                               'with prefix.')
@@ -174,14 +231,14 @@ class cmd_svn_import(Command):
 
     @display_command
     def run(self, from_location, to_location=None, trees=False, 
-            standalone=False, scheme=None, all=False, prefix=None):
-        from bzrlib.branch import Branch
+            standalone=False, scheme=None, all=False, prefix=None, keep=False,
+            incremental=False):
         from bzrlib.bzrdir import BzrDir
-        from bzrlib.errors import BzrCommandError, NoRepositoryPresent, NotBranchError
+        from bzrlib.errors import BzrCommandError, NoRepositoryPresent
         from bzrlib import urlutils
-        from convert import convert_repository
-        from repository import SvnRepository
-        import os
+        from bzrlib.plugins.svn.convert import convert_repository
+        from bzrlib.plugins.svn.mapping3 import repository_guess_scheme
+        from bzrlib.plugins.svn.repository import SvnRepository
 
         if to_location is None:
             to_location = os.path.basename(from_location.rstrip("/\\"))
@@ -192,7 +249,7 @@ class cmd_svn_import(Command):
             standalone = False
 
         if os.path.isfile(from_location):
-            from convert import load_dumpfile
+            from bzrlib.plugins.svn.convert import load_dumpfile
             import tempfile
             tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
             load_dumpfile(from_location, tmp_repos)
@@ -204,36 +261,48 @@ class cmd_svn_import(Command):
         try:
             from_repos = from_dir.open_repository()
         except NoRepositoryPresent, e:
-            try:
-                Branch.open(from_location)
-                raise BzrCommandError("No Repository found at %s. "
-                    "For individual branches, use 'bzr branch'." % from_location)
-            except NotBranchError:
-                if prefix is not None:
-                    raise BzrCommandError("Path inside repository specified and --prefix specified")
-                from_repos = from_dir.find_repository()
-                prefix = urlutils.relative_url(from_repos.base, from_location)
-                self.outf.write("Importing branches below %s\n" % 
-                        urlutils.unescape_for_display(prefix, self.outf.encoding))
+            if prefix is not None:
+                raise BzrCommandError("Path inside repository specified "
+                                      "and --prefix specified")
+            from_repos = from_dir.find_repository()
+            prefix = urlutils.relative_url(from_repos.base, from_location)
+            prefix = prefix.encode("utf-8")
+
+        from_repos.lock_read()
+        try:
+            (guessed_scheme, scheme) = repository_guess_scheme(from_repos, 
+                from_repos.get_latest_revnum())
 
-        if prefix is not None:
-            prefix = prefix.strip("/") + "/"
+            if prefix is not None:
+                prefix = prefix.strip("/") + "/"
+                if guessed_scheme.is_branch(prefix):
+                    raise BzrCommandError("%s appears to contain a branch. " 
+                            "For individual branches, use 'bzr branch'." % 
+                            from_location)
 
-        if not isinstance(from_repos, SvnRepository):
-            raise BzrCommandError(
-                    "Not a Subversion repository: %s" % from_location)
+                self.outf.write("Importing branches with prefix /%s\n" % 
+                    urlutils.unescape_for_display(prefix, self.outf.encoding))
 
-        def filter_branch((branch_path, revnum, exists)):
-            if prefix is not None and not branch_path.startswith(prefix):
-                return False
-            return exists
+            if not isinstance(from_repos, SvnRepository):
+                raise BzrCommandError(
+                        "Not a Subversion repository: %s" % from_location)
 
-        convert_repository(from_repos, to_location, scheme, not standalone, 
-                trees, all, filter_branch=filter_branch)
+            def filter_branch(branch):
+                if (prefix is not None and 
+                    not branch.get_branch_path().startswith(prefix)):
+                    return False
+                return True
 
-        if tmp_repos is not None:
-            from bzrlib import osutils
-            osutils.rmtree(tmp_repos)
+            convert_repository(from_repos, to_location, scheme, None, 
+                               not standalone, trees, all, 
+                               filter_branch=filter_branch,
+                               keep=keep, incremental=incremental)
+
+            if tmp_repos is not None:
+                from bzrlib import osutils
+                osutils.rmtree(tmp_repos)
+        finally:
+            from_repos.unlock()
 
 
 register_command(cmd_svn_import)
@@ -249,7 +318,8 @@ class cmd_svn_upgrade(Command):
 
     @display_command
     def run(self, from_repository=None, verbose=False):
-        from upgrade import upgrade_branch, upgrade_workingtree
+        from bzrlib.plugins.svn.upgrade import (upgrade_branch, 
+                                                upgrade_workingtree)
         from bzrlib.branch import Branch
         from bzrlib.errors import NoWorkingTree, BzrCommandError
         from bzrlib.repository import Repository
@@ -306,10 +376,11 @@ class cmd_svn_push(Command):
                  'rather than the one containing the working directory.',
             short_name='d',
             type=unicode,
-            )]
+            ),
+            Option("merged", help="Push merged (right hand side) revisions.")]
 
     def run(self, location=None, revision=None, remember=False, 
-            directory=None):
+            directory=None, merged=None):
         from bzrlib.bzrdir import BzrDir
         from bzrlib.branch import Branch
         from bzrlib.errors import NotBranchError, BzrCommandError
@@ -328,26 +399,98 @@ class cmd_svn_push(Command):
                 self.outf.write("Using saved location: %s\n" % display_url)
                 location = stored_loc
 
-        bzrdir = BzrDir.open(location)
-        if revision is not None:
-            if len(revision) > 1:
-                raise BzrCommandError(
-                    'bzr svn-push --revision takes exactly one revision' 
-                    ' identifier')
-            revision_id = revision[0].in_history(source_branch).rev_id
-        else:
-            revision_id = None
+        source_branch.lock_read()
         try:
-            target_branch = bzrdir.open_branch()
-            target_branch.pull(source_branch, revision_id)
-        except NotBranchError:
-            target_branch = bzrdir.import_branch(source_branch, revision_id)
+            bzrdir = BzrDir.open(location)
+            if revision is not None:
+                if len(revision) > 1:
+                    raise BzrCommandError(
+                        'bzr svn-push --revision takes exactly one revision' 
+                        ' identifier')
+                revision_id = revision[0].as_revision_id(source_branch)
+            else:
+                revision_id = None
+            try:
+                target_branch = bzrdir.open_branch()
+                target_branch.lock_write()
+                try:
+                    target_branch.pull(source_branch, stop_revision=revision_id, _push_merged=merged)
+                finally:
+                    target_branch.unlock()
+            except NotBranchError:
+                target_branch = bzrdir.import_branch(source_branch, revision_id, _push_merged=merged)
+        finally:
+            source_branch.unlock()
         # We successfully created the target, remember it
         if source_branch.get_push_location() is None or remember:
             source_branch.set_push_location(target_branch.base)
 
 register_command(cmd_svn_push)
 
+class cmd_dpush(Command):
+    """Push diffs into Subversion without any Bazaar-specific properties set.
+
+    This will afterwards rebase the local Bazaar branch on the Subversion 
+    branch unless the --no-rebase option is used, in which case 
+    the two branches will be out of sync. 
+    """
+    takes_args = ['location?']
+    takes_options = ['remember', Option('directory',
+            help='Branch to push from, '
+                 'rather than the one containing the working directory.',
+            short_name='d',
+            type=unicode,
+            ),
+            Option('no-rebase', help="Don't rebase after push")]
+
+    def run(self, location=None, remember=False, directory=None, 
+            no_rebase=False):
+        from bzrlib import urlutils
+        from bzrlib.bzrdir import BzrDir
+        from bzrlib.branch import Branch
+        from bzrlib.errors import BzrCommandError, NoWorkingTree
+        from bzrlib.workingtree import WorkingTree
+
+        from bzrlib.plugins.svn.commit import dpush
+
+        if directory is None:
+            directory = "."
+        try:
+            source_wt = WorkingTree.open_containing(directory)[0]
+            source_branch = source_wt.branch
+        except NoWorkingTree:
+            source_branch = Branch.open_containing(directory)[0]
+            source_wt = None
+        stored_loc = source_branch.get_push_location()
+        if location is None:
+            if stored_loc is None:
+                raise BzrCommandError("No push location known or specified.")
+            else:
+                display_url = urlutils.unescape_for_display(stored_loc,
+                        self.outf.encoding)
+                self.outf.write("Using saved location: %s\n" % display_url)
+                location = stored_loc
+
+        bzrdir = BzrDir.open(location)
+        target_branch = bzrdir.open_branch()
+        target_branch.lock_write()
+        revid_map = dpush(target_branch, source_branch)
+        # We successfully created the target, remember it
+        if source_branch.get_push_location() is None or remember:
+            source_branch.set_push_location(target_branch.base)
+        if not no_rebase:
+            _, old_last_revid = source_branch.last_revision_info()
+            new_last_revid = revid_map[old_last_revid]
+            if source_wt is not None:
+                source_wt.pull(target_branch, overwrite=True, 
+                               stop_revision=new_last_revid)
+            else:
+                source_branch.pull(target_branch, overwrite=True, 
+                                   stop_revision=new_last_revid)
+
+
+register_command(cmd_dpush)
+
 
 class cmd_svn_branching_scheme(Command):
     """Show or change the branching scheme for a Subversion repository.
@@ -365,10 +508,11 @@ class cmd_svn_branching_scheme(Command):
         from bzrlib.bzrdir import BzrDir
         from bzrlib.errors import BzrCommandError
         from bzrlib.msgeditor import edit_commit_message
-        from bzrlib.repository import Repository
         from bzrlib.trace import info
-        from repository import SvnRepository
-        from scheme import scheme_from_branch_list
+        from bzrlib.plugins.svn.repository import SvnRepository
+        from bzrlib.plugins.svn.mapping3.scheme import scheme_from_branch_list
+        from bzrlib.plugins.svn.mapping3 import (config_set_scheme, 
+            get_property_scheme, set_property_scheme)
         def scheme_str(scheme):
             if scheme is None:
                 return ""
@@ -378,18 +522,18 @@ class cmd_svn_branching_scheme(Command):
         if not isinstance(repos, SvnRepository):
             raise BzrCommandError("Not a Subversion repository: %s" % location)
         if repository_wide:
-            scheme = repos._get_property_scheme()
+            scheme = get_property_scheme(repos)
         else:
-            scheme = repos.get_scheme()
+            scheme = repos.get_mapping().scheme
         if set:
             schemestr = edit_commit_message("", 
                                             start_message=scheme_str(scheme))
             scheme = scheme_from_branch_list(
                 map(lambda x:x.strip("\n"), schemestr.splitlines()))
             if repository_wide:
-                repos.set_property_scheme(scheme)
+                set_property_scheme(repos, scheme)
             else:
-                repos.set_branching_scheme(scheme, mandatory=True)
+                config_set_scheme(repos, scheme, None, mandatory=True)
         elif scheme is not None:
             info(scheme_str(scheme))
 
@@ -418,7 +562,7 @@ register_command(cmd_svn_set_revprops)
 def test_suite():
     """Returns the testsuite for bzr-svn."""
     from unittest import TestSuite
-    import tests
+    from bzrlib.plugins.svn import tests
     suite = TestSuite()
     suite.addTest(tests.test_suite())
     return suite
@@ -430,6 +574,3 @@ if __name__ == '__main__':
 elif __name__ != 'bzrlib.plugins.svn':
     raise ImportError('The Subversion plugin must be installed as'
                       ' bzrlib.plugins.svn not %s' % __name__)
-else:
-    import os, sys
-    sys.path.append(os.path.dirname(os.path.abspath(__file__)))