Rev 5288: Resolve NEWS conflicts. in http://bazaar.launchpad.net/~lifeless/bzr/loomsupport

Robert Collins robertc at robertcollins.net
Mon Jun 14 22:41:34 BST 2010


At http://bazaar.launchpad.net/~lifeless/bzr/loomsupport

------------------------------------------------------------
revno: 5288 [merge]
revision-id: robertc at robertcollins.net-20100614214132-uozv55rswcinlva6
parent: robertc at robertcollins.net-20100614083211-b3e6tybqgir06g1v
parent: pqm at pqm.ubuntu.com-20100614175824-nq51rf1uetnut04t
committer: Robert Collins <robertc at robertcollins.net>
branch nick: loomsupport
timestamp: Tue 2010-06-15 09:41:32 +1200
message:
  Resolve NEWS conflicts.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/bundle/bundle_data.py   read_changeset.py-20050619171944-c0d95aa685537640
  bzrlib/log.py                  log.py-20050505065812-c40ce11702fe5fb1
  bzrlib/plugin.py               plugin.py-20050622060424-829b654519533d69
  bzrlib/recordcounter.py        recordcounter.py-20100512152727-q0kn8gah0tre0uqs-1
  bzrlib/tests/blackbox/test_log.py test_log.py-20060112090212-78f6ea560c868e24
  bzrlib/tests/per_branch/test_locking.py test_locking.py-20060707151933-tav3o2hpibwi53u4-4
  bzrlib/tests/test_log.py       testlog.py-20050728115707-1a514809d7d49309
  bzrlib/tests/test_plugins.py   plugins.py-20050622075746-32002b55e5e943e9
  bzrlib/tests/test_urlutils.py  test_urlutils.py-20060502192900-46b1f9579987cf9c
  bzrlib/urlutils.py             urlutils.py-20060502195429-e8a161ecf8fac004
=== modified file 'NEWS'
--- a/NEWS	2010-06-14 06:57:25 +0000
+++ b/NEWS	2010-06-14 21:41:32 +0000
@@ -14,6 +14,11 @@
 Compatibility Breaks
 ********************
 
+* URLs like ``foo:bar/baz`` are now always parsed as a URL with scheme "foo"
+  and path "bar/baz", even if bzr does not recognize "foo" as a known URL
+  scheme.  Previously these URLs would be treated as local paths.
+  (Gordon Tyler)
+
 New Features
 ************
 
@@ -28,20 +33,27 @@
   leading to faster init for directories with existing content.
   (Martin [gz], Parth Malwankar, #501307)
 
+* ``bzr log --exclude-common-ancestry`` is now taken into account for
+  linear ancetries. (Vincent Ladeuil, #575631)
+
+* Ensure that wrong path specifications in ``BZR_PLUGINS_AT`` display
+  proper error messages. (Vincent Ladeuil, #591215)
+
 * Explicitly removing ``--profile-imports`` option from parsed command-line
   arguments on Windows, because bzr script does the same.
   (Alexander Belchenko, #588277)
 
 * Fetching was slightly confused about the best code to use and was
   using a new code path for all branches, resulting in more lookups than
-  necessary on old branches.
-  (Robert Collins, #593515)
+  necessary on old branches. (Robert Collins, #593515)
 
 * Final fix for 'no help for command' issue. We now show a clean message
   when a command has no help, document how to set help more clearly, and
   test that all commands available to the test suite have help.
   (Robert Collins, #177500)
 
+* Relative imports in plugins are now handled correctly when using
+  BZR_PLUGINS_AT. (Vincent Ladeuil, #588959)
 
 Improvements
 ************
@@ -77,6 +89,8 @@
 Internals
 *********
 
+* Improved ``bzrlib.urlutils`` to handle lp:foo/bar URLs. (Gordon Tyler)
+
 Testing
 *******
 

=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2010-06-14 08:32:11 +0000
+++ b/bzrlib/branch.py	2010-06-14 21:41:32 +0000
@@ -198,6 +198,13 @@
         return self.supports_tags() and self.tags.get_tag_dict()
 
     def get_config(self):
+        """Get a bzrlib.config.BranchConfig for this Branch.
+
+        This can then be used to get and set configuration options for the
+        branch.
+
+        :return: A bzrlib.config.BranchConfig.
+        """
         return BranchConfig(self)
 
     def _get_config(self):

=== modified file 'bzrlib/bundle/bundle_data.py'
--- a/bzrlib/bundle/bundle_data.py	2009-09-20 22:12:36 +0000
+++ b/bzrlib/bundle/bundle_data.py	2010-06-09 22:48:52 +0000
@@ -331,7 +331,7 @@
                 try:
                     name, value = info_item.split(':', 1)
                 except ValueError:
-                    raise 'Value %r has no colon' % info_item
+                    raise ValueError('Value %r has no colon' % info_item)
                 if name == 'last-changed':
                     last_changed = value
                 elif name == 'executable':

=== modified file 'bzrlib/log.py'
--- a/bzrlib/log.py	2010-05-11 08:44:59 +0000
+++ b/bzrlib/log.py	2010-06-08 09:50:27 +0000
@@ -522,7 +522,7 @@
     elif not generate_merge_revisions:
         # If we only want to see linear revisions, we can iterate ...
         iter_revs = _generate_flat_revisions(branch, start_rev_id, end_rev_id,
-                                             direction)
+                                             direction, exclude_common_ancestry)
         if direction == 'forward':
             iter_revs = reversed(iter_revs)
     else:
@@ -544,8 +544,11 @@
         return [(rev_id, revno_str, 0)]
 
 
-def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction):
-    result = _linear_view_revisions(branch, start_rev_id, end_rev_id)
+def _generate_flat_revisions(branch, start_rev_id, end_rev_id, direction,
+                             exclude_common_ancestry=False):
+    result = _linear_view_revisions(
+        branch, start_rev_id, end_rev_id,
+        exclude_common_ancestry=exclude_common_ancestry)
     # If a start limit was given and it's not obviously an
     # ancestor of the end limit, check it before outputting anything
     if direction == 'forward' or (start_rev_id
@@ -572,7 +575,7 @@
     if delayed_graph_generation:
         try:
             for rev_id, revno, depth in  _linear_view_revisions(
-                branch, start_rev_id, end_rev_id):
+                branch, start_rev_id, end_rev_id, exclude_common_ancestry):
                 if _has_merges(branch, rev_id):
                     # The end_rev_id can be nested down somewhere. We need an
                     # explicit ancestry check. There is an ambiguity here as we
@@ -643,14 +646,17 @@
     return True
 
 
-def _linear_view_revisions(branch, start_rev_id, end_rev_id):
+def _linear_view_revisions(branch, start_rev_id, end_rev_id,
+                           exclude_common_ancestry=False):
     """Calculate a sequence of revisions to view, newest to oldest.
 
     :param start_rev_id: the lower revision-id
     :param end_rev_id: the upper revision-id
+    :param exclude_common_ancestry: Whether the start_rev_id should be part of
+        the iterated revisions.
     :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples.
     :raises _StartNotLinearAncestor: if a start_rev_id is specified but
-      is not found walking the left-hand history
+        is not found walking the left-hand history
     """
     br_revno, br_rev_id = branch.last_revision_info()
     repo = branch.repository
@@ -667,7 +673,8 @@
             revno = branch.revision_id_to_dotted_revno(revision_id)
             revno_str = '.'.join(str(n) for n in revno)
             if not found_start and revision_id == start_rev_id:
-                yield revision_id, revno_str, 0
+                if not exclude_common_ancestry:
+                    yield revision_id, revno_str, 0
                 found_start = True
                 break
             else:

=== modified file 'bzrlib/plugin.py'
--- a/bzrlib/plugin.py	2010-04-06 07:22:31 +0000
+++ b/bzrlib/plugin.py	2010-06-11 08:02:42 +0000
@@ -81,6 +81,33 @@
     return path.rstrip("\\/")
 
 
+def _get_specific_plugin_paths(paths):
+    """Returns the plugin paths from a string describing the associations.
+
+    :param paths: A string describing the paths associated with the plugins.
+
+    :returns: A list of (plugin name, path) tuples.
+
+    For example, if paths is my_plugin@/test/my-test:her_plugin@/production/her,
+    [('my_plugin', '/test/my-test'), ('her_plugin', '/production/her')] 
+    will be returned.
+
+    Note that ':' in the example above depends on the os.
+    """
+    if not paths:
+        return []
+    specs = []
+    for spec in paths.split(os.pathsep):
+        try:
+            name, path = spec.split('@')
+        except ValueError:
+            raise errors.BzrCommandError(
+                '"%s" is not a valid <plugin_name>@<plugin_path> description '
+                % spec)
+        specs.append((name, path))
+    return specs
+
+
 def set_plugins_path(path=None):
     """Set the path for plugins to be loaded from.
 
@@ -98,10 +125,8 @@
         for name in disabled_plugins.split(os.pathsep):
             PluginImporter.blacklist.add('bzrlib.plugins.' + name)
     # Set up a the specific paths for plugins
-    specific_plugins = os.environ.get('BZR_PLUGINS_AT', None)
-    if specific_plugins is not None:
-        for spec in specific_plugins.split(os.pathsep):
-            plugin_name, plugin_path = spec.split('@')
+    for plugin_name, plugin_path in _get_specific_plugin_paths(os.environ.get(
+            'BZR_PLUGINS_AT', None)):
             PluginImporter.specific_paths[
                 'bzrlib.plugins.%s' % plugin_name] = plugin_path
     return path
@@ -562,7 +587,6 @@
         # We are called only for specific paths
         plugin_path = self.specific_paths[fullname]
         loading_path = None
-        package = False
         if os.path.isdir(plugin_path):
             for suffix, mode, kind in imp.get_suffixes():
                 if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
@@ -570,8 +594,12 @@
                     continue
                 init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
                 if os.path.isfile(init_path):
-                    loading_path = init_path
-                    package = True
+                    # We've got a module here and load_module needs specific
+                    # parameters.
+                    loading_path = plugin_path
+                    suffix = ''
+                    mode = ''
+                    kind = imp.PKG_DIRECTORY
                     break
         else:
             for suffix, mode, kind in imp.get_suffixes():
@@ -581,17 +609,18 @@
         if loading_path is None:
             raise ImportError('%s cannot be loaded from %s'
                               % (fullname, plugin_path))
-        f = open(loading_path, mode)
+        if kind is imp.PKG_DIRECTORY:
+            f = None
+        else:
+            f = open(loading_path, mode)
         try:
             mod = imp.load_module(fullname, f, loading_path,
                                   (suffix, mode, kind))
-            if package:
-                # The plugin can contain modules, so be ready
-                mod.__path__ = [plugin_path]
             mod.__package__ = fullname
             return mod
         finally:
-            f.close()
+            if f is not None:
+                f.close()
 
 
 # Install a dedicated importer for plugins requiring special handling

=== modified file 'bzrlib/recordcounter.py'
--- a/bzrlib/recordcounter.py	2010-05-19 13:30:09 +0000
+++ b/bzrlib/recordcounter.py	2010-06-12 02:58:42 +0000
@@ -31,10 +31,10 @@
 
         # Users of RecordCounter instance update progress bar every
         # _STEP_ records. We choose are reasonably high number to keep
-        # display updates from being two frequent. This is an odd number
+        # display updates from being too frequent. This is an odd number
         # to ensure that the last digit of the records fetched in
         # fetches vs estimate ratio changes periodically.
-        self.STEP = 71
+        self.STEP = 7
 
     def is_initialized(self):
         return self.initialized

=== modified file 'bzrlib/tests/blackbox/test_log.py'
--- a/bzrlib/tests/blackbox/test_log.py	2010-04-14 12:30:17 +0000
+++ b/bzrlib/tests/blackbox/test_log.py	2010-06-08 08:24:39 +0000
@@ -159,6 +159,14 @@
         self.assertLogRevnos(['-c1'], ['1'])
 
 
+class TestLogExcludeCommonAncestry(TestLogWithLogCatcher):
+
+    def test_exclude_common_ancestry_simple_revnos(self):
+        self.make_linear_branch()
+        self.assertLogRevnos(['-r1..3', '--exclude-common-ancestry'],
+                             ['3', '2'])
+
+
 class TestLogMergedLinearAncestry(TestLogWithLogCatcher):
 
     def setUp(self):
@@ -167,6 +175,18 @@
         # stop calling run_bzr, there is no point) --vila 100118.
         builder = branchbuilder.BranchBuilder(self.get_transport())
         builder.start_series()
+        # 1
+        # | \
+        # 2  1.1.1
+        # | / |
+        # 3  1.1.2
+        # |   |
+        # |  1.1.3
+        # | / |
+        # 4  1.1.4
+        # | /
+        # 5
+
         # mainline
         builder.build_snapshot('1', None, [
             ('add', ('', 'root-id', 'directory', ''))])

=== modified file 'bzrlib/tests/per_branch/test_locking.py'
--- a/bzrlib/tests/per_branch/test_locking.py	2010-05-20 03:20:04 +0000
+++ b/bzrlib/tests/per_branch/test_locking.py	2010-06-11 07:59:43 +0000
@@ -533,6 +533,7 @@
     def test_lock_write_raises_in_lock_read(self):
         branch = self.make_branch('b')
         branch.lock_read()
+        self.addCleanup(branch.unlock)
         err = self.assertRaises(errors.ReadOnlyError, branch.lock_write)
 
     def test_lock_and_unlock_leaves_repo_unlocked(self):

=== modified file 'bzrlib/tests/test_log.py'
--- a/bzrlib/tests/test_log.py	2010-05-05 08:18:32 +0000
+++ b/bzrlib/tests/test_log.py	2010-06-08 09:50:27 +0000
@@ -1724,13 +1724,13 @@
         return br
 
     def assertLogRevnos(self, expected_revnos, b, start, end,
-                        exclude_common_ancestry):
+                        exclude_common_ancestry, generate_merge_revisions=True):
         # FIXME: the layering in log makes it hard to test intermediate levels,
         # I wish adding filters with their parameters were easier...
         # -- vila 20100413
         iter_revs = log._calc_view_revisions(
             b, start, end, direction='reverse',
-            generate_merge_revisions=True,
+            generate_merge_revisions=generate_merge_revisions,
             exclude_common_ancestry=exclude_common_ancestry)
         self.assertEqual(expected_revnos,
                          [revid for revid, revno, depth in iter_revs])
@@ -1738,11 +1738,19 @@
     def test_merge_sorted_exclude_ancestry(self):
         b = self.make_branch_with_alternate_ancestries()
         self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2', '1'],
-                             b, '1', '3', False)
+                             b, '1', '3', exclude_common_ancestry=False)
         # '2' is part of the '3' ancestry but not part of '1.1.1' ancestry so
         # it should be mentioned even if merge_sort order will make it appear
         # after 1.1.1
         self.assertLogRevnos(['3', '1.1.2', '1.2.1', '2'],
-                             b, '1.1.1', '3', True)
+                             b, '1.1.1', '3', exclude_common_ancestry=True)
 
+    def test_merge_sorted_simple_revnos_exclude_ancestry(self):
+        b = self.make_branch_with_alternate_ancestries()
+        self.assertLogRevnos(['3', '2'],
+                             b, '1', '3', exclude_common_ancestry=True,
+                             generate_merge_revisions=False)
+        self.assertLogRevnos(['3', '1.1.2', '1.2.1', '1.1.1', '2'],
+                             b, '1', '3', exclude_common_ancestry=True,
+                             generate_merge_revisions=True)
 

=== modified file 'bzrlib/tests/test_plugins.py'
--- a/bzrlib/tests/test_plugins.py	2010-05-16 13:49:19 +0000
+++ b/bzrlib/tests/test_plugins.py	2010-06-11 08:02:42 +0000
@@ -27,6 +27,7 @@
 
 import bzrlib
 from bzrlib import (
+    errors,
     osutils,
     plugin,
     plugins,
@@ -79,6 +80,16 @@
         if getattr(bzrlib.plugins, name, None) is not None:
             delattr(bzrlib.plugins, name)
 
+    def _unregister_plugin_submodule(self, plugin_name, submodule_name):
+        """Remove the submodule from sys.modules and the bzrlib namespace."""
+        py_name = 'bzrlib.plugins.%s.%s' % (plugin_name, submodule_name)
+        if py_name in sys.modules:
+            del sys.modules[py_name]
+        plugin = getattr(bzrlib.plugins, plugin_name, None)
+        if plugin is not None:
+            if getattr(plugin, submodule_name, None) is not None:
+                delattr(plugin, submodule_name)
+
     def assertPluginUnknown(self, name):
         self.failIf(getattr(bzrlib.plugins, name, None) is not None)
         self.failIf('bzrlib.plugins.%s' % name in sys.modules)
@@ -798,14 +809,35 @@
         self.assertLength(0, self.warnings)
 
 
+class TestLoadPluginAtSyntax(tests.TestCase):
+
+    def _get_paths(self, paths):
+        return plugin._get_specific_plugin_paths(paths)
+
+    def test_empty(self):
+        self.assertEquals([], self._get_paths(None))
+        self.assertEquals([], self._get_paths(''))
+
+    def test_one_path(self):
+        self.assertEquals([('b', 'man')], self._get_paths('b at man'))
+
+    def test_bogus_path(self):
+        # We need a '@'
+        self.assertRaises(errors.BzrCommandError, self._get_paths, 'batman')
+        # Too much '@' isn't good either
+        self.assertRaises(errors.BzrCommandError, self._get_paths,
+                          'batman at mobile@cave')
+        # An empty description probably indicates a problem
+        self.assertRaises(errors.BzrCommandError, self._get_paths,
+                          os.pathsep.join(['batman at cave', '', 'robin at mobile']))
+
+
 class TestLoadPluginAt(tests.TestCaseInTempDir, TestPluginMixin):
 
     def setUp(self):
         super(TestLoadPluginAt, self).setUp()
         # Make sure we don't pollute the plugins namespace
         self.overrideAttr(plugins, '__path__')
-        # Be paranoid in case a test fail
-        self.addCleanup(self._unregister_plugin, 'test_foo')
         # Reset the flag that protect against double loading
         self.overrideAttr(plugin, '_loaded', False)
         # Create the same plugin in two directories
@@ -813,6 +845,8 @@
         # The "normal" directory, we use 'standard' instead of 'plugins' to
         # avoid depending on the precise naming.
         self.create_plugin_package('test_foo', dir='standard/test_foo')
+        # All the tests will load the 'test_foo' plugin from various locations
+        self.addCleanup(self._unregister_plugin, 'test_foo')
 
     def assertTestFooLoadedFrom(self, path):
         self.assertPluginKnown('test_foo')
@@ -860,6 +894,8 @@
     def test_submodule_loading(self):
         # We create an additional directory under the one for test_foo
         self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
+        self.addCleanup(self._unregister_plugin_submodule,
+                        'test_foo', 'test_bar')
         osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo at non-standard-dir')
         plugin.set_plugins_path(['standard'])
         import bzrlib.plugins.test_foo
@@ -869,6 +905,22 @@
         self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
                                   bzrlib.plugins.test_foo.test_bar.__file__)
 
+    def test_relative_submodule_loading(self):
+        self.create_plugin_package('test_foo', dir='another-dir', source='''
+import test_bar
+''')
+        # We create an additional directory under the one for test_foo
+        self.create_plugin_package('test_bar', dir='another-dir/test_bar')
+        self.addCleanup(self._unregister_plugin_submodule,
+                        'test_foo', 'test_bar')
+        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo at another-dir')
+        plugin.set_plugins_path(['standard'])
+        import bzrlib.plugins.test_foo
+        self.assertEqual('bzrlib.plugins.test_foo',
+                         bzrlib.plugins.test_foo.__package__)
+        self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
+                                  bzrlib.plugins.test_foo.test_bar.__file__)
+
     def test_loading_from___init__only(self):
         # We rename the existing __init__.py file to ensure that we don't load
         # a random file

=== modified file 'bzrlib/tests/test_urlutils.py'
--- a/bzrlib/tests/test_urlutils.py	2010-05-27 22:10:42 +0000
+++ b/bzrlib/tests/test_urlutils.py	2010-06-08 01:31:34 +0000
@@ -156,7 +156,7 @@
         # Weird stuff
         # Can't have slashes or colons in the scheme
         test_one('/path/to/://foo', None)
-        test_one('path:path://foo', None)
+        test_one('scheme:stuff://foo', ('scheme', 'stuff://foo'))
         # Must have more than one character for scheme
         test_one('C://foo', None)
         test_one('ab://foo', ('ab', 'foo'))
@@ -210,6 +210,8 @@
         test('http://foo/bar/baz', 'http://foo', 'bar/baz')
         test('http://foo/baz', 'http://foo', 'bar/../baz')
         test('http://foo/baz', 'http://foo/bar/', '../baz')
+        test('lp:foo/bar', 'lp:foo', 'bar')
+        test('lp:foo/bar/baz', 'lp:foo', 'bar/baz')
 
         # Absolute paths
         test('http://foo', 'http://foo') # abs url with nothing is preserved.
@@ -219,6 +221,9 @@
         test('http://bar/', 'http://foo', 'http://bar/')
         test('http://bar/a', 'http://foo', 'http://bar/a')
         test('http://bar/a/', 'http://foo', 'http://bar/a/')
+        test('lp:bar', 'http://foo', 'lp:bar')
+        test('lp:bar', 'lp:foo', 'lp:bar')
+        test('file:///stuff', 'lp:foo', 'file:///stuff')
 
         # From a base path
         test('file:///foo', 'file:///', 'foo')

=== modified file 'bzrlib/urlutils.py'
--- a/bzrlib/urlutils.py	2010-06-02 05:03:31 +0000
+++ b/bzrlib/urlutils.py	2010-06-14 17:58:24 +0000
@@ -101,7 +101,7 @@
     first_path_slash = path.find('/')
     if first_path_slash == -1:
         return len(scheme), None
-    return len(scheme), first_path_slash+len(scheme)+3
+    return len(scheme), first_path_slash+m.start('path')
 
 
 def join(base, *args):
@@ -118,67 +118,26 @@
     """
     if not args:
         return base
-    match = _url_scheme_re.match(base)
-    scheme = None
-    if match:
-        scheme = match.group('scheme')
-        path = match.group('path').split('/')
-        if path[-1:] == ['']:
-            # Strip off a trailing slash
-            # This helps both when we are at the root, and when
-            # 'base' has an extra slash at the end
-            path = path[:-1]
-    else:
-        path = base.split('/')
-
-    if scheme is not None and len(path) >= 1:
-        host = path[:1]
-        # the path should be represented as an abs path.
-        # we know this must be absolute because of the presence of a URL scheme.
-        remove_root = True
-        path = [''] + path[1:]
-    else:
-        # create an empty host, but dont alter the path - this might be a
-        # relative url fragment.
-        host = []
-        remove_root = False
-
+    scheme_end, path_start = _find_scheme_and_separator(base)
+    if scheme_end is None and path_start is None:
+        path_start = 0
+    elif path_start is None:
+        path_start = len(base)
+    path = base[path_start:]
     for arg in args:
-        match = _url_scheme_re.match(arg)
-        if match:
-            # Absolute URL
-            scheme = match.group('scheme')
-            # this skips .. normalisation, making http://host/../../..
-            # be rather strange.
-            path = match.group('path').split('/')
-            # set the host and path according to new absolute URL, discarding
-            # any previous values.
-            # XXX: duplicates mess from earlier in this function.  This URL
-            # manipulation code needs some cleaning up.
-            if scheme is not None and len(path) >= 1:
-                host = path[:1]
-                path = path[1:]
-                # url scheme implies absolute path.
-                path = [''] + path
-            else:
-                # no url scheme we take the path as is.
-                host = []
+        arg_scheme_end, arg_path_start = _find_scheme_and_separator(arg)
+        if arg_scheme_end is None and arg_path_start is None:
+            arg_path_start = 0
+        elif arg_path_start is None:
+            arg_path_start = len(arg)
+        if arg_scheme_end is not None:
+            base = arg
+            path = arg[arg_path_start:]
+            scheme_end = arg_scheme_end
+            path_start = arg_path_start
         else:
-            path = '/'.join(path)
             path = joinpath(path, arg)
-            path = path.split('/')
-    if remove_root and path[0:1] == ['']:
-        del path[0]
-    if host:
-        # Remove the leading slash from the path, so long as it isn't also the
-        # trailing slash, which we want to keep if present.
-        if path and path[0] == '' and len(path) > 1:
-            del path[0]
-        path = host + path
-
-    if scheme is None:
-        return '/'.join(path)
-    return scheme + '://' + '/'.join(path)
+    return base[:path_start] + path
 
 
 def joinpath(base, *args):
@@ -303,7 +262,7 @@
     MIN_ABS_FILEURL_LENGTH = WIN32_MIN_ABS_FILEURL_LENGTH
 
 
-_url_scheme_re = re.compile(r'^(?P<scheme>[^:/]{2,})://(?P<path>.*)$')
+_url_scheme_re = re.compile(r'^(?P<scheme>[^:/]{2,}):(//)?(?P<path>.*)$')
 _url_hex_escapes_re = re.compile(r'(%[0-9a-fA-F]{2})')
 
 
@@ -339,18 +298,18 @@
     :param url: Either a hybrid URL or a local path
     :return: A normalized URL which only includes 7-bit ASCII characters.
     """
-    m = _url_scheme_re.match(url)
-    if not m:
+    scheme_end, path_start = _find_scheme_and_separator(url)
+    if scheme_end is None:
         return local_path_to_url(url)
-    scheme = m.group('scheme')
-    path = m.group('path')
+    prefix = url[:path_start]
+    path = url[path_start:]
     if not isinstance(url, unicode):
         for c in url:
             if c not in _url_safe_characters:
                 raise errors.InvalidURL(url, 'URLs can only contain specific'
                                             ' safe characters (not %r)' % c)
         path = _url_hex_escapes_re.sub(_unescape_safe_chars, path)
-        return str(scheme + '://' + ''.join(path))
+        return str(prefix + ''.join(path))
 
     # We have a unicode (hybrid) url
     path_chars = list(path)
@@ -362,7 +321,7 @@
                 ['%%%02X' % ord(c) for c in path_chars[i].encode('utf-8')])
     path = ''.join(path_chars)
     path = _url_hex_escapes_re.sub(_unescape_safe_chars, path)
-    return str(scheme + '://' + path)
+    return str(prefix + path)
 
 
 def relative_url(base, other):




More information about the bazaar-commits mailing list