Rev 3992: Command for opening Launchpad pages (Jonathan Lange) in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Tue Feb 10 00:15:56 GMT 2009


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 3992
revision-id: pqm at pqm.ubuntu.com-20090210001553-5qi19ufmh3sx18mq
parent: pqm at pqm.ubuntu.com-20090209232557-ll08rw7c9xe43dpe
parent: ian.clatworthy at canonical.com-20090209233358-87e0072zgnkomb6v
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2009-02-10 00:15:53 +0000
message:
  Command for opening Launchpad pages (Jonathan Lange)
added:
  bzrlib/plugins/launchpad/test_lp_open.py test_lp_open.py-20090125174355-hxrsxh3sj84225qu-1
modified:
  bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
  bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
  bzrlib/plugins/launchpad/test_lp_directory.py test_lp_indirect.py-20070126002743-oyle362tzv9cd8mi-1
  bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3991.1.1
    revision-id: ian.clatworthy at canonical.com-20090209233358-87e0072zgnkomb6v
    parent: pqm at pqm.ubuntu.com-20090209232557-ll08rw7c9xe43dpe
    parent: jml at canonical.com-20090125174401-tzky3d674jyo2odk
    committer: Ian Clatworthy <ian.clatworthy at canonical.com>
    branch nick: ianc-integration
    timestamp: Tue 2009-02-10 09:33:58 +1000
    message:
      Command for opening Launchpad pages (Jonathan Lange)
    added:
      bzrlib/plugins/launchpad/test_lp_open.py test_lp_open.py-20090125174355-hxrsxh3sj84225qu-1
    modified:
      bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_directory.py test_lp_indirect.py-20070126002743-oyle362tzv9cd8mi-1
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.10
    revision-id: jml at canonical.com-20090125174401-tzky3d674jyo2odk
    parent: jml at canonical.com-20090125174347-yfsnekjimnlsiy1l
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Sun 2009-01-25 15:44:01 -0200
    message:
      Blackbox tests, forgot to add these earlier.
    added:
      bzrlib/plugins/launchpad/test_lp_open.py test_lp_open.py-20090125174355-hxrsxh3sj84225qu-1
    ------------------------------------------------------------
    revno: 3955.3.9
    revision-id: jml at canonical.com-20090125174347-yfsnekjimnlsiy1l
    parent: jml at canonical.com-20090125170734-0mpnb6ovwsd2wv00
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Sun 2009-01-25 15:43:47 -0200
    message:
      Catch errors.
    modified:
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.8
    revision-id: jml at canonical.com-20090125170734-0mpnb6ovwsd2wv00
    parent: jml at canonical.com-20090124151018-5vygev25ufrjm25a
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Sun 2009-01-25 15:07:34 -0200
    message:
      Support lp URL shortcuts.
    modified:
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_directory.py test_lp_indirect.py-20070126002743-oyle362tzv9cd8mi-1
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.7
    revision-id: jml at canonical.com-20090124151018-5vygev25ufrjm25a
    parent: jml at canonical.com-20090124000001-l8ctacnsspxvnh42
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Sat 2009-01-24 13:10:18 -0200
    message:
      Test the launchpad-open command. Fix up some minor bugs.
    modified:
      bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.6
    revision-id: jml at canonical.com-20090124000001-l8ctacnsspxvnh42
    parent: jml at canonical.com-20090123235918-qgxnw89348dhyjgo
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 22:00:01 -0200
    message:
      Add a TODO.
    modified:
      bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
    ------------------------------------------------------------
    revno: 3955.3.5
    revision-id: jml at canonical.com-20090123235918-qgxnw89348dhyjgo
    parent: jml at canonical.com-20090123233648-s3ufv7z20lss0k3t
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 21:59:18 -0200
    message:
      Add an untested plugin, make the error handling a little nicer.
    modified:
      bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.4
    revision-id: jml at canonical.com-20090123233648-s3ufv7z20lss0k3t
    parent: jml at canonical.com-20090123232938-efhx2xe8swjkw5t7
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 21:36:48 -0200
    message:
      Some error cases, plus a docstring.
    modified:
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.3
    revision-id: jml at canonical.com-20090123232938-efhx2xe8swjkw5t7
    parent: jml at canonical.com-20090123232743-xet3q9p8fux1csw1
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 21:29:38 -0200
    message:
      Test a couple more cases.
    modified:
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.2
    revision-id: jml at canonical.com-20090123232743-xet3q9p8fux1csw1
    parent: jml at canonical.com-20090123232536-8itvi1bdejkd4gug
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 21:27:43 -0200
    message:
      Tighten up the code a little, changing the dev service to use https, 
      which a) works and b) is more realistic.
    modified:
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
    ------------------------------------------------------------
    revno: 3955.3.1
    revision-id: jml at canonical.com-20090123232536-8itvi1bdejkd4gug
    parent: pqm at pqm.ubuntu.com-20090123103145-yvo3icrif75vkt20
    committer: Jonathan Lange <jml at canonical.com>
    branch nick: open-in-launchpad
    timestamp: Fri 2009-01-23 21:25:36 -0200
    message:
      Start doing URL stuff, extracting the domain bit out of LaunchpadService,
      a little.
    modified:
      bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
      bzrlib/plugins/launchpad/test_lp_service.py test_lp_service.py-20080213034527-drf0ucr2x1js3onb-1
=== modified file 'bzrlib/plugins/launchpad/__init__.py'
--- a/bzrlib/plugins/launchpad/__init__.py	2008-03-06 21:16:38 +0000
+++ b/bzrlib/plugins/launchpad/__init__.py	2009-01-24 15:10:18 +0000
@@ -127,6 +127,37 @@
 register_command(cmd_register_branch)
 
 
+# XXX: Make notes to test this.
+class cmd_launchpad_open(Command):
+    """Open a Launchpad branch page in your web browser."""
+
+    aliases = ['lp-open']
+    takes_options = [
+        Option('dry-run',
+               'Do not actually open the browser. Just say the URL we would '
+               'use.'),
+        ]
+    takes_args = ['location?']
+
+    def run(self, location=None, dry_run=False):
+        from bzrlib.plugins.launchpad.lp_registration import LaunchpadService
+        from bzrlib.trace import note
+        import webbrowser
+        if location is None:
+            location = u'.'
+        branch = Branch.open(location)
+        branch_url = branch.get_public_branch()
+        if branch_url is None:
+            raise NoPublicBranch(branch)
+        service = LaunchpadService()
+        web_url = service.get_web_url_from_branch_url(branch_url)
+        note('Opening %s in web browser' % web_url)
+        if not dry_run:
+            webbrowser.open(web_url)
+
+register_command(cmd_launchpad_open)
+
+
 class cmd_launchpad_login(Command):
     """Show or set the Launchpad user ID.
 
@@ -182,8 +213,12 @@
     """Called by bzrlib to fetch tests for this plugin"""
     from unittest import TestSuite, TestLoader
     from bzrlib.plugins.launchpad import (
-         test_account, test_lp_directory, test_lp_service, test_register,
-         )
+        test_account,
+        test_lp_directory,
+        test_lp_open,
+        test_lp_service,
+        test_register,
+        )
 
     loader = TestLoader()
     suite = TestSuite()
@@ -191,6 +226,7 @@
         test_account,
         test_register,
         test_lp_directory,
+        test_lp_open,
         test_lp_service,
         ]:
         suite.addTests(loader.loadTestsFromModule(module))

=== modified file 'bzrlib/plugins/launchpad/lp_registration.py'
--- a/bzrlib/plugins/launchpad/lp_registration.py	2008-04-24 07:22:53 +0000
+++ b/bzrlib/plugins/launchpad/lp_registration.py	2009-01-25 17:43:47 +0000
@@ -21,6 +21,11 @@
 import urllib
 import xmlrpclib
 
+from bzrlib.lazy_import import lazy_import
+lazy_import(globals(), """
+from bzrlib import urlutils
+""")
+
 from bzrlib import (
     config,
     errors,
@@ -40,26 +45,40 @@
         errors.BzrError.__init__(self, lp_instance=lp_instance)
 
 
+class NotLaunchpadBranch(errors.BzrError):
+
+    _fmt = "%(url)s is not hosted on Launchpad."
+
+    def __init__(self, url):
+        errors.BzrError.__init__(self, url=url)
+
+
 class LaunchpadService(object):
     """A service to talk to Launchpad via XMLRPC.
 
     See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
     """
 
+    LAUNCHPAD_DOMAINS = {
+        'production': 'launchpad.net',
+        'edge': 'edge.launchpad.net',
+        'staging': 'staging.launchpad.net',
+        'demo': 'demo.launchpad.net',
+        'dev': 'launchpad.dev',
+        }
+
     # NB: these should always end in a slash to avoid xmlrpclib appending
     # '/RPC2'
+    LAUNCHPAD_INSTANCE = {}
+    for instance, domain in LAUNCHPAD_DOMAINS.iteritems():
+        LAUNCHPAD_INSTANCE[instance] = 'https://xmlrpc.%s/bazaar/' % domain
+
     # We use edge as the default because:
     # Beta users get redirected to it
     # All users can use it
     # There is a bug in the launchpad side where redirection causes an OOPS.
-    LAUNCHPAD_INSTANCE = {
-        'production': 'https://xmlrpc.launchpad.net/bazaar/',
-        'edge': 'https://xmlrpc.edge.launchpad.net/bazaar/',
-        'staging': 'https://xmlrpc.staging.launchpad.net/bazaar/',
-        'demo': 'https://xmlrpc.demo.launchpad.net/bazaar/',
-        'dev': 'http://xmlrpc.launchpad.dev/bazaar/',
-        }
-    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE['edge']
+    DEFAULT_INSTANCE = 'edge'
+    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE[DEFAULT_INSTANCE]
 
     transport = None
     registrant_email = None
@@ -160,6 +179,40 @@
                         % (self.service_url, e.errcode, e.errmsg))
         return result
 
+    @property
+    def domain(self):
+        if self._lp_instance is None:
+            instance = self.DEFAULT_INSTANCE
+        else:
+            instance = self._lp_instance
+        return self.LAUNCHPAD_DOMAINS[instance]
+
+    def get_web_url_from_branch_url(self, branch_url, _request_factory=None):
+        """Get the Launchpad web URL for the given branch URL.
+
+        :raise errors.InvalidURL: if 'branch_url' cannot be identified as a
+            Launchpad branch URL.
+        :return: The URL of the branch on Launchpad.
+        """
+        scheme, hostinfo, path = urlsplit(branch_url)[:3]
+        if _request_factory is None:
+            _request_factory = ResolveLaunchpadPathRequest
+        if scheme == 'lp':
+            resolve = _request_factory(path)
+            try:
+                result = resolve.submit(self)
+            except xmlrpclib.Fault, fault:
+                raise errors.InvalidURL(branch_url, str(fault))
+            branch_url = result['urls'][0]
+            path = urlsplit(branch_url)[2]
+        else:
+            domains = (
+                'bazaar.%s' % domain
+                for domain in self.LAUNCHPAD_DOMAINS.itervalues())
+            if hostinfo not in domains:
+                raise NotLaunchpadBranch(branch_url)
+        return urlutils.join('https://code.%s' % self.domain, path)
+
 
 class BaseRequest(object):
     """Base request for talking to a XMLRPC server."""

=== modified file 'bzrlib/plugins/launchpad/test_lp_directory.py'
--- a/bzrlib/plugins/launchpad/test_lp_directory.py	2008-10-17 14:21:20 +0000
+++ b/bzrlib/plugins/launchpad/test_lp_directory.py	2009-01-25 17:07:34 +0000
@@ -24,7 +24,6 @@
 from bzrlib.branch import Branch
 from bzrlib.directory_service import directories
 from bzrlib.tests import (
-    TestCase,
     TestCaseInTempDir,
     TestCaseWithMemoryTransport
 )

=== added file 'bzrlib/plugins/launchpad/test_lp_open.py'
--- a/bzrlib/plugins/launchpad/test_lp_open.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/launchpad/test_lp_open.py	2009-01-25 17:44:01 +0000
@@ -0,0 +1,59 @@
+# Copyright (C) 2009 Canonical Ltd
+#
+# 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
+
+"""Tests for the launchpad-open command."""
+
+from bzrlib.osutils import abspath
+
+from bzrlib.tests import TestCaseWithTransport
+
+
+class TestLaunchpadOpen(TestCaseWithTransport):
+
+    def run_open(self, location, retcode=0):
+        out, err = self.run_bzr(
+            ['launchpad-open', '--dry-run', location], retcode=retcode)
+        return err.splitlines()
+
+    def test_non_branch(self):
+        # Running lp-open on a non-branch prints a simple error.
+        self.assertEqual(
+            ['bzr: ERROR: Not a branch: "%s/".' % abspath('.')],
+            self.run_open('.', retcode=3))
+
+    def test_no_public_location(self):
+        self.make_branch('not-public')
+        self.assertEqual(
+            ['bzr: ERROR: There is no public branch set for "%s/".'
+             % abspath('not-public')],
+            self.run_open('not-public', retcode=3))
+
+    def test_non_launchpad_branch(self):
+        branch = self.make_branch('non-lp')
+        url = 'http://example.com/non-lp'
+        branch.set_public_branch(url)
+        self.assertEqual(
+            ['bzr: ERROR: %s is not hosted on Launchpad.' % url],
+            self.run_open('non-lp', retcode=3))
+
+    def test_launchpad_branch(self):
+        branch = self.make_branch('lp')
+        branch.set_public_branch(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            ['Opening https://code.edge.launchpad.net/~foo/bar/baz in web '
+             'browser'],
+            self.run_open('lp'))

=== modified file 'bzrlib/plugins/launchpad/test_lp_service.py'
--- a/bzrlib/plugins/launchpad/test_lp_service.py	2008-02-13 03:45:58 +0000
+++ b/bzrlib/plugins/launchpad/test_lp_service.py	2009-01-25 17:43:47 +0000
@@ -17,10 +17,13 @@
 """Tests for selection of the right Launchpad service by environment"""
 
 import os
+import xmlrpclib
 
+from bzrlib import errors
+from bzrlib.plugins.launchpad.lp_registration import (
+    InvalidLaunchpadInstance, LaunchpadService, NotLaunchpadBranch)
+from bzrlib.plugins.launchpad.test_lp_directory import FakeResolveFactory
 from bzrlib.tests import TestCase
-from bzrlib.plugins.launchpad.lp_registration import (
-    InvalidLaunchpadInstance, LaunchpadService)
 
 
 class LaunchpadServiceTests(TestCase):
@@ -58,7 +61,7 @@
 
     def test_dev_service(self):
         service = LaunchpadService(lp_instance='dev')
-        self.assertEqual('http://xmlrpc.launchpad.dev/bazaar/',
+        self.assertEqual('https://xmlrpc.launchpad.dev/bazaar/',
                          service.service_url)
 
     def test_demo_service(self):
@@ -84,3 +87,107 @@
         service = LaunchpadService(lp_instance='staging')
         self.assertEqual('http://example.com/',
                          service.service_url)
+
+
+class TestURLInference(TestCase):
+    """Test the way we infer Launchpad web pages from branch URLs."""
+
+    def test_default_bzr_ssh_url(self):
+        service = LaunchpadService()
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.edge.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_product_bzr_ssh_url(self):
+        service = LaunchpadService(lp_instance='production')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_sftp_branch_url(self):
+        service = LaunchpadService(lp_instance='production')
+        web_url = service.get_web_url_from_branch_url(
+            'sftp://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_staging_branch_url(self):
+        service = LaunchpadService(lp_instance='production')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.staging.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_non_launchpad_url(self):
+        service = LaunchpadService()
+        error = self.assertRaises(
+            NotLaunchpadBranch, service.get_web_url_from_branch_url,
+            'bzr+ssh://example.com/~foo/bar/baz')
+        self.assertEqual(
+            'bzr+ssh://example.com/~foo/bar/baz is not hosted on Launchpad.',
+            str(error))
+
+    def test_dodgy_launchpad_url(self):
+        service = LaunchpadService()
+        self.assertRaises(
+            NotLaunchpadBranch, service.get_web_url_from_branch_url,
+            'bzr+ssh://launchpad.net/~foo/bar/baz')
+
+    def test_lp_branch_url(self):
+        service = LaunchpadService(lp_instance='production')
+        factory = FakeResolveFactory(
+            self, '~foo/bar/baz',
+            dict(urls=['http://bazaar.launchpad.net/~foo/bar/baz']))
+        web_url = service.get_web_url_from_branch_url(
+            'lp:~foo/bar/baz', factory)
+        self.assertEqual(
+            'https://code.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_lp_branch_shortcut(self):
+        service = LaunchpadService()
+        factory = FakeResolveFactory(
+            self, 'foo',
+            dict(urls=['http://bazaar.launchpad.net/~foo/bar/baz']))
+        web_url = service.get_web_url_from_branch_url('lp:foo', factory)
+        self.assertEqual(
+            'https://code.edge.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_lp_branch_fault(self):
+        service = LaunchpadService()
+        factory = FakeResolveFactory(self, 'foo', None)
+        def submit(service):
+            raise xmlrpclib.Fault(42, 'something went wrong')
+        factory.submit = submit
+        self.assertRaises(
+            errors.InvalidURL, service.get_web_url_from_branch_url, 'lp:foo',
+            factory)
+
+    def test_staging_url(self):
+        service = LaunchpadService(lp_instance='staging')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.staging.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_edge_url(self):
+        service = LaunchpadService(lp_instance='edge')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.edge.launchpad.net/~foo/bar/baz', web_url)
+
+    def test_dev_url(self):
+        service = LaunchpadService(lp_instance='dev')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.launchpad.dev/~foo/bar/baz', web_url)
+
+    def test_demo_url(self):
+        service = LaunchpadService(lp_instance='demo')
+        web_url = service.get_web_url_from_branch_url(
+            'bzr+ssh://bazaar.launchpad.net/~foo/bar/baz')
+        self.assertEqual(
+            'https://code.demo.launchpad.net/~foo/bar/baz', web_url)




More information about the bazaar-commits mailing list