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