Rev 4907: (jml) Add an lp-mirror command to request that Launchpad mirror a in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Dec 18 09:05:17 GMT 2009


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

------------------------------------------------------------
revno: 4907 [merge]
revision-id: pqm at pqm.ubuntu.com-20091218090513-kzwkjw7rdf7bahqi
parent: pqm at pqm.ubuntu.com-20091217110447-6gebnkhfriqm2xia
parent: jml at canonical.com-20091217043506-puovqz1gv0986fgl
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2009-12-18 09:05:13 +0000
message:
  (jml) Add an lp-mirror command to request that Launchpad mirror a
  	branch now. Add an API for interacting with launchpadlib using Bazaar.
added:
  bzrlib/plugins/launchpad/lp_api.py lp_api.py-20090704082908-79il6zl4gugwl3wz-1
  bzrlib/plugins/launchpad/test_lp_api.py test_lp_api.py-20091217012733-8sgahbhjn35ilrbu-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
  bzrlib/plugins/launchpad/lp_directory.py lp_indirect.py-20070126012204-de5rugwlt22c7u7e-1
  bzrlib/plugins/launchpad/lp_registration.py lp_registration.py-20060315190948-daa617eafe3a8d48
  bzrlib/win32utils.py           win32console.py-20051021033308-123c6c929d04973d
=== modified file 'NEWS'
--- a/NEWS	2009-12-17 10:01:25 +0000
+++ b/NEWS	2009-12-18 09:05:13 +0000
@@ -120,6 +120,12 @@
   lengths. 
   (Vincent Ladeuil)
 
+* The new command ``bzr lp-mirror`` will request that Launchpad update its
+  mirror of a local branch. This command will only function if launchpadlib
+  is installed.
+  (Jonathan Lange)
+
+
 Bug Fixes
 *********
 
@@ -223,6 +229,11 @@
 
 * ``bzrlib.textui`` (vestigial module) removed.  (Martin Pool)
 
+* The Launchpad plugin now has a function ``login`` which will log in to
+  Launchpad with launchpadlib, and ``load_branch`` which will return the
+  Launchpad Branch object corresponding to a given Bazaar Branch object.
+  (Jonathan Lange)
+
 Internals
 *********
 

=== modified file 'bzrlib/plugins/launchpad/__init__.py'
--- a/bzrlib/plugins/launchpad/__init__.py	2009-12-07 15:47:05 +0000
+++ b/bzrlib/plugins/launchpad/__init__.py	2009-12-17 04:35:06 +0000
@@ -36,15 +36,12 @@
 from bzrlib.directory_service import directories
 from bzrlib.errors import (
     BzrCommandError,
+    DependencyNotPresent,
     InvalidURL,
     NoPublicBranch,
     NotBranchError,
     )
 from bzrlib.help_topics import topic_registry
-from bzrlib.plugins.launchpad.lp_registration import (
-    LaunchpadService,
-    NotLaunchpadBranch,
-    )
 
 
 class cmd_register_branch(Command):
@@ -111,8 +108,8 @@
             link_bug=None,
             dry_run=False):
         from bzrlib.plugins.launchpad.lp_registration import (
-            LaunchpadService, BranchRegistrationRequest, BranchBugLinkRequest,
-            DryRunLaunchpadService)
+            BranchRegistrationRequest, BranchBugLinkRequest,
+            DryRunLaunchpadService, LaunchpadService)
         if public_url is None:
             try:
                 b = _mod_branch.Branch.open_containing('.')[0]
@@ -147,9 +144,9 @@
             # Run on service entirely in memory
             service = DryRunLaunchpadService()
         service.gather_user_credentials()
-        branch_object_url = rego.submit(service)
+        rego.submit(service)
         if link_bug:
-            link_bug_url = linko.submit(service)
+            linko.submit(service)
         print 'Branch registered.'
 
 register_command(cmd_register_branch)
@@ -181,6 +178,8 @@
             yield branch_url
 
     def _get_web_url(self, service, location):
+        from bzrlib.plugins.launchpad.lp_registration import (
+            NotLaunchpadBranch)
         for branch_url in self._possible_locations(location):
             try:
                 return service.get_web_url_from_branch_url(branch_url)
@@ -189,6 +188,8 @@
         raise NotLaunchpadBranch(branch_url)
 
     def run(self, location=None, dry_run=False):
+        from bzrlib.plugins.launchpad.lp_registration import (
+            LaunchpadService)
         if location is None:
             location = u'.'
         web_url = self._get_web_url(LaunchpadService(), location)
@@ -226,6 +227,7 @@
         ]
 
     def run(self, name=None, no_check=False, verbose=False):
+        # This is totally separate from any launchpadlib login system.
         from bzrlib.plugins.launchpad import account
         check_account = not no_check
 
@@ -255,6 +257,26 @@
 register_command(cmd_launchpad_login)
 
 
+# XXX: cmd_launchpad_mirror is untested
+class cmd_launchpad_mirror(Command):
+    """Ask Launchpad to mirror a branch now."""
+
+    aliases = ['lp-mirror']
+    takes_args = ['location?']
+
+    def run(self, location='.'):
+        from bzrlib.plugins.launchpad import lp_api
+        from bzrlib.plugins.launchpad.lp_registration import LaunchpadService
+        branch = _mod_branch.Branch.open(location)
+        service = LaunchpadService()
+        launchpad = lp_api.login(service)
+        lp_branch = lp_api.load_branch(launchpad, branch)
+        lp_branch.requestMirror()
+
+
+register_command(cmd_launchpad_mirror)
+
+
 def _register_directory():
     directories.register_lazy('lp:', 'bzrlib.plugins.launchpad.lp_directory',
                               'LaunchpadDirectory',
@@ -266,6 +288,7 @@
     testmod_names = [
         'test_account',
         'test_register',
+        'test_lp_api',
         'test_lp_directory',
         'test_lp_login',
         'test_lp_open',

=== added file 'bzrlib/plugins/launchpad/lp_api.py'
--- a/bzrlib/plugins/launchpad/lp_api.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/launchpad/lp_api.py	2009-12-17 03:40:41 +0000
@@ -0,0 +1,135 @@
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Tools for dealing with the Launchpad API."""
+
+# Importing this module will be expensive, since it imports launchpadlib and
+# its dependencies. However, our plan is to only load this module when it is
+# needed by a command that uses it.
+
+
+import os
+
+from bzrlib import (
+    config,
+    errors,
+    osutils,
+    )
+from bzrlib.plugins.launchpad.lp_registration import (
+    InvalidLaunchpadInstance,
+    NotLaunchpadBranch,
+    )
+
+try:
+    import launchpadlib
+except ImportError, e:
+    raise errors.DependencyNotPresent('launchpadlib', e)
+
+from launchpadlib.launchpad import (
+    EDGE_SERVICE_ROOT,
+    STAGING_SERVICE_ROOT,
+    Launchpad,
+    )
+
+
+# Declare the minimum version of launchpadlib that we need in order to work.
+# 1.5.1 is the version of launchpadlib packaged in Ubuntu 9.10, the most
+# recent Ubuntu release at the time of writing.
+MINIMUM_LAUNCHPADLIB_VERSION = (1, 5, 1)
+
+
+def get_cache_directory():
+    """Return the directory to cache launchpadlib objects in."""
+    return osutils.pathjoin(config.config_dir(), 'launchpad')
+
+
+def parse_launchpadlib_version(version_number):
+    """Parse a version number of the style used by launchpadlib."""
+    return tuple(map(int, version_number.split('.')))
+
+
+def check_launchpadlib_compatibility():
+    """Raise an error if launchpadlib has the wrong version number."""
+    installed_version = parse_launchpadlib_version(launchpadlib.__version__)
+    if installed_version < MINIMUM_LAUNCHPADLIB_VERSION:
+        raise errors.IncompatibleAPI(
+            'launchpadlib', MINIMUM_LAUNCHPADLIB_VERSION,
+            installed_version, installed_version)
+
+
+LAUNCHPAD_API_URLS = {
+    'production': 'https://api.launchpad.net/beta/',
+    'edge': EDGE_SERVICE_ROOT,
+    'staging': STAGING_SERVICE_ROOT,
+    'dev': 'https://api.launchpad.dev/beta/',
+    }
+
+
+def _get_api_url(service):
+    """Return the root URL of the Launchpad API.
+
+    e.g. For the 'edge' Launchpad service, this function returns
+    launchpadlib.launchpad.EDGE_SERVICE_ROOT.
+
+    :param service: A `LaunchpadService` object.
+    :return: A URL as a string.
+    """
+    if service._lp_instance is None:
+        lp_instance = service.DEFAULT_INSTANCE
+    else:
+        lp_instance = service._lp_instance
+    try:
+        return LAUNCHPAD_API_URLS[lp_instance]
+    except KeyError:
+        raise InvalidLaunchpadInstance(lp_instance)
+
+
+def login(service, timeout=None, proxy_info=None):
+    """Log in to the Launchpad API.
+
+    :return: The root `Launchpad` object from launchpadlib.
+    """
+    cache_directory = get_cache_directory()
+    launchpad = Launchpad.login_with(
+        'bzr', _get_api_url(service), cache_directory, timeout=timeout,
+        proxy_info=proxy_info)
+    # XXX: Work-around a minor security bug in launchpadlib 1.5.1, which would
+    # create this directory with default umask.
+    os.chmod(cache_directory, 0700)
+    return launchpad
+
+
+def load_branch(launchpad, branch):
+    """Return the launchpadlib Branch object corresponding to 'branch'.
+
+    :param launchpad: The root `Launchpad` object from launchpadlib.
+    :param branch: A `bzrlib.branch.Branch`.
+    :raise NotLaunchpadBranch: If we cannot determine the Launchpad URL of
+        `branch`.
+    :return: A launchpadlib Branch object.
+    """
+    # XXX: This duplicates the "What are possible URLs for the branch that
+    # Launchpad might recognize" logic found in cmd_lp_open.
+
+    # XXX: This makes multiple roundtrips to Launchpad for what is
+    # conceptually a single operation -- get me the branches that match these
+    # URLs. Unfortunately, Launchpad's support for such operations is poor, so
+    # we have to allow multiple roundtrips.
+    for url in branch.get_public_branch(), branch.get_push_location():
+        lp_branch = launchpad.branches.getByUrl(url=url)
+        if lp_branch:
+            return lp_branch
+    raise NotLaunchpadBranch(url)

=== modified file 'bzrlib/plugins/launchpad/lp_directory.py'
--- a/bzrlib/plugins/launchpad/lp_directory.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/plugins/launchpad/lp_directory.py	2009-07-03 14:24:23 +0000
@@ -63,15 +63,9 @@
                  _request_factory=ResolveLaunchpadPathRequest,
                  _lp_login=None):
         """Resolve the base URL for this transport."""
+        service = LaunchpadService.for_url(url)
         result = urlsplit(url)
-        # Perform an XMLRPC request to resolve the path
-        lp_instance = result[1]
-        if lp_instance == '':
-            lp_instance = None
-        elif lp_instance not in LaunchpadService.LAUNCHPAD_INSTANCE:
-            raise errors.InvalidURL(path=url)
         resolve = _request_factory(result[2].strip('/'))
-        service = LaunchpadService(lp_instance=lp_instance)
         try:
             result = resolve.submit(service)
         except xmlrpclib.Fault, fault:
@@ -79,7 +73,7 @@
                 path=url, extra=fault.faultString)
 
         if 'launchpad' in debug.debug_flags:
-            trace.mutter("resolve_lp_path(%r) == %r", path, result)
+            trace.mutter("resolve_lp_path(%r) == %r", url, result)
 
         if _lp_login is None:
             _lp_login = get_lp_login()

=== modified file 'bzrlib/plugins/launchpad/lp_registration.py'
--- a/bzrlib/plugins/launchpad/lp_registration.py	2009-11-05 18:32:31 +0000
+++ b/bzrlib/plugins/launchpad/lp_registration.py	2009-12-09 09:20:42 +0000
@@ -21,18 +21,15 @@
 import urllib
 import xmlrpclib
 
-from bzrlib.lazy_import import lazy_import
-lazy_import(globals(), """
-from bzrlib import urlutils
-""")
-
 from bzrlib import (
     config,
     errors,
+    urlutils,
     __version__ as _bzrlib_version,
     )
 from bzrlib.transport.http import _urllib2_wrappers
 
+
 # for testing, do
 '''
 export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
@@ -123,7 +120,6 @@
                     % (_bzrlib_version, xmlrpclib.__version__)
         self.transport = transport
 
-
     @property
     def service_url(self):
         """Return the http or https url for the xmlrpc server.
@@ -141,6 +137,17 @@
         else:
             return self.DEFAULT_SERVICE_URL
 
+    @classmethod
+    def for_url(cls, url, **kwargs):
+        """Return the Launchpad service corresponding to the given URL."""
+        result = urlsplit(url)
+        lp_instance = result[1]
+        if lp_instance == '':
+            lp_instance = None
+        elif lp_instance not in cls.LAUNCHPAD_INSTANCE:
+            raise errors.InvalidURL(path=url)
+        return cls(lp_instance=lp_instance, **kwargs)
+
     def get_proxy(self, authenticated):
         """Return the proxy for XMLRPC requests."""
         if authenticated:
@@ -216,13 +223,7 @@
             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.
-        """
+    def _guess_branch_path(self, branch_url, _request_factory=None):
         scheme, hostinfo, path = urlsplit(branch_url)[:3]
         if _request_factory is None:
             _request_factory = ResolveLaunchpadPathRequest
@@ -240,6 +241,16 @@
                 for domain in self.LAUNCHPAD_DOMAINS.itervalues())
             if hostinfo not in domains:
                 raise NotLaunchpadBranch(branch_url)
+        return path.lstrip('/')
+
+    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.
+        """
+        path = self._guess_branch_path(branch_url, _request_factory)
         return urlutils.join('https://code.%s' % self.domain, path)
 
 

=== added file 'bzrlib/plugins/launchpad/test_lp_api.py'
--- a/bzrlib/plugins/launchpad/test_lp_api.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/launchpad/test_lp_api.py	2009-12-17 04:35:06 +0000
@@ -0,0 +1,101 @@
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from bzrlib import commands, config, errors, osutils
+from bzrlib.plugins.launchpad import cmd_launchpad_mirror
+from bzrlib.tests import (
+    ModuleAvailableFeature,
+    TestCase,
+    TestCaseWithTransport,
+    )
+
+
+launchpadlib_feature = ModuleAvailableFeature('launchpadlib')
+
+
+class TestDependencyManagement(TestCase):
+    """Tests for managing the dependency on launchpadlib."""
+
+    _test_needs_features = [launchpadlib_feature]
+
+    def setUp(self):
+        TestCase.setUp(self)
+        from bzrlib.plugins.launchpad import lp_api
+        self.lp_api = lp_api
+
+    def patch(self, obj, name, value):
+        """Temporarily set the 'name' attribute of 'obj' to 'value'."""
+        real_value = getattr(obj, name)
+        setattr(obj, name, value)
+        self.addCleanup(setattr, obj, name, real_value)
+
+    def test_get_launchpadlib_version(self):
+        # parse_launchpadlib_version returns a tuple of a version number of
+        # the style used by launchpadlib.
+        version_info = self.lp_api.parse_launchpadlib_version('1.5.1')
+        self.assertEqual((1, 5, 1), version_info)
+
+    def test_supported_launchpadlib_version(self):
+        # If the installed version of launchpadlib is greater than the minimum
+        # required version of launchpadlib, check_launchpadlib_compatibility
+        # doesn't raise an error.
+        launchpadlib = launchpadlib_feature.module
+        self.patch(launchpadlib, '__version__', '1.5.1')
+        self.lp_api.MINIMUM_LAUNCHPADLIB_VERSION = (1, 5, 1)
+        # Doesn't raise an exception.
+        self.lp_api.check_launchpadlib_compatibility()
+
+    def test_unsupported_launchpadlib_version(self):
+        # If the installed version of launchpadlib is less than the minimum
+        # required version of launchpadlib, check_launchpadlib_compatibility
+        # raises an IncompatibleAPI error.
+        launchpadlib = launchpadlib_feature.module
+        self.patch(launchpadlib, '__version__', '1.5.0')
+        self.lp_api.MINIMUM_LAUNCHPADLIB_VERSION = (1, 5, 1)
+        self.assertRaises(
+            errors.IncompatibleAPI,
+            self.lp_api.check_launchpadlib_compatibility)
+
+
+class TestCacheDirectory(TestCase):
+    """Tests for get_cache_directory."""
+
+    _test_needs_features = [launchpadlib_feature]
+
+    def test_get_cache_directory(self):
+        # get_cache_directory returns the path to a directory inside the
+        # Bazaar configuration directory.
+        from bzrlib.plugins.launchpad import lp_api
+        expected_path = osutils.pathjoin(config.config_dir(), 'launchpad')
+        self.assertEqual(expected_path, lp_api.get_cache_directory())
+
+
+class TestLaunchpadMirror(TestCaseWithTransport):
+    """Tests for the 'bzr lp-mirror' command."""
+
+    # Testing the lp-mirror command is quite hard, since it must talk to a
+    # Launchpad server. Here, we just test that the command exists.
+
+    _test_needs_features = [launchpadlib_feature]
+
+    def test_command_exists(self):
+        out, err = self.run_bzr(['launchpad-mirror', '--help'], retcode=0)
+        self.assertEqual('', err)
+
+    def test_alias_exists(self):
+        out, err = self.run_bzr(['lp-mirror', '--help'], retcode=0)
+        self.assertEqual('', err)

=== modified file 'bzrlib/win32utils.py'
--- a/bzrlib/win32utils.py	2009-12-02 16:21:42 +0000
+++ b/bzrlib/win32utils.py	2009-12-16 06:38:15 +0000
@@ -22,9 +22,7 @@
 import glob
 import os
 import re
-import shlex
 import struct
-import StringIO
 import sys
 
 
@@ -392,7 +390,6 @@
 
 
 def _ensure_unicode(s):
-    from bzrlib import osutils
     if s and type(s) != unicode:
         from bzrlib import osutils
         s = s.decode(osutils.get_user_encoding())
@@ -648,7 +645,7 @@
         prototype = ctypes.WINFUNCTYPE(POINTER(LPCWSTR), LPCWSTR, POINTER(INT))
         command_line = GetCommandLine()
         # Skip the first argument, since we only care about parameters
-        argv = _command_line_to_argv(GetCommandLine())[1:]
+        argv = _command_line_to_argv(command_line)[1:]
         if getattr(sys, 'frozen', None) is None:
             # Invoked via 'python.exe' which takes the form:
             #   python.exe [PYTHON_OPTIONS] C:\Path\bzr [BZR_OPTIONS]




More information about the bazaar-commits mailing list