Rev 6033: (jameinel) Bug #609187: warn the user if a package-import branch doesn't in file:///home/pqm/archives/thelove/bzr/%2Btrunk/
Canonical.com Patch Queue Manager
pqm at pqm.ubuntu.com
Mon Jul 18 18:33:40 UTC 2011
At file:///home/pqm/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 6033 [merge]
revision-id: pqm at pqm.ubuntu.com-20110718183335-rall83p4yxjdd900
parent: pqm at pqm.ubuntu.com-20110718172033-l9zmherfao7q80yp
parent: john at arbash-meinel.com-20110718165156-f519epfjwqnq1juw
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2011-07-18 18:33:35 +0000
message:
(jameinel) Bug #609187: warn the user if a package-import branch doesn't
have the most recent source package. (John A Meinel)
added:
bzrlib/plugins/launchpad/lp_api_lite.py lp_api_lite.py-20110712150258-dfa3tq91bz14r5ww-1
bzrlib/plugins/launchpad/test_lp_api_lite.py test_lp_api_lite.py-20110713114529-lurqfgc09yifvs3u-1
modified:
bzrlib/plugins/launchpad/__init__.py __init__.py-20060315182712-2d5feebd2a1032dc
doc/en/release-notes/bzr-2.5.txt bzr2.5.txt-20110708125756-587p0hpw7oke4h05-1
=== modified file 'bzrlib/plugins/launchpad/__init__.py'
--- a/bzrlib/plugins/launchpad/__init__.py 2011-04-05 01:12:15 +0000
+++ b/bzrlib/plugins/launchpad/__init__.py 2011-07-18 16:51:56 +0000
@@ -40,19 +40,24 @@
# see http://wiki.bazaar.canonical.com/Specs/BranchRegistrationTool
+import time
+
# Since we are a built-in plugin we share the bzrlib version
-from bzrlib import version_info
from bzrlib.lazy_import import lazy_import
lazy_import(globals(), """
from bzrlib import (
- branch as _mod_branch,
ui,
trace,
)
""")
-from bzrlib import bzrdir
+from bzrlib import (
+ branch as _mod_branch,
+ bzrdir,
+ lazy_regex,
+ version_info,
+ )
from bzrlib.commands import (
Command,
register_command,
@@ -462,12 +467,88 @@
_register_directory()
+# This is kept in __init__ so that we don't load lp_api_lite unless the branch
+# actually matches. That way we can avoid importing extra dependencies like
+# json.
+_package_branch = lazy_regex.lazy_compile(
+ r'bazaar.launchpad.net.*?/'
+ r'(?P<user>~[^/]+/)?(?P<archive>ubuntu|debian)/(?P<series>[^/]+/)?'
+ r'(?P<project>[^/]+)(?P<branch>/[^/]+)?'
+ )
+
+def _get_package_branch_info(url):
+ """Determine the packaging information for this URL.
+
+ :return: If this isn't a packaging branch, return None. If it is, return
+ (archive, series, project)
+ """
+ m = _package_branch.search(url)
+ if m is None:
+ return
+ archive, series, project, user = m.group('archive', 'series',
+ 'project', 'user')
+ if series is not None:
+ # series is optional, so the regex includes the extra '/', we don't
+ # want to send that on (it causes Internal Server Errors.)
+ series = series.strip('/')
+ if user is not None:
+ user = user.strip('~/')
+ if user != 'ubuntu-branches':
+ return None
+ return archive, series, project
+
+
+def _check_is_up_to_date(the_branch):
+ info = _get_package_branch_info(the_branch.base)
+ if info is None:
+ return
+ archive, series, project = info
+ from bzrlib.plugins.launchpad import lp_api_lite
+ t = time.time()
+ latest_pub = lp_api_lite.LatestPublication(archive, series, project)
+ latest_ver = latest_pub.get_latest_version()
+ t_latest_ver = time.time() - t
+ trace.mutter('LatestPublication.get_latest_version took %.3fs'
+ % (t_latest_ver,))
+ if latest_ver is None:
+ trace.note('Could not find a published version for packaging branch:\n'
+ ' %s' % (the_branch.base,))
+ return
+ t = time.time()
+ tags = the_branch.tags.get_tag_dict()
+ t_tag_dict = time.time() - t
+ trace.mutter('LatestPublication get_tag_dict took: %.3fs' % (t_tag_dict,))
+ if latest_ver in tags:
+ trace.note('Found most recent published version: %s'
+ ' in packaging branch:\n %s'
+ % (latest_ver, the_branch.base))
+ else:
+ place = archive.title()
+ if series is not None:
+ place = '%s/%s' % (place, series.title())
+ best_tag = lp_api_lite.get_most_recent_tag(tags, the_branch)
+ if best_tag is None:
+ best_message = ''
+ else:
+ best_message = '\nThe most recent tag found is %s' % (best_tag,)
+ trace.warning(
+ 'Packaging branch is not up-to-date. The most recent published\n'
+ 'version in %s is %s, but it is not in the branch tags for:\n %s%s'
+ % (place, latest_ver, the_branch.base, best_message))
+
+def _register_hooks():
+ _mod_branch.Branch.hooks.install_named_hook('open',
+ _check_is_up_to_date, 'package-branch-up-to-date')
+
+
+_register_hooks()
def load_tests(basic_tests, module, loader):
testmod_names = [
'test_account',
'test_register',
'test_lp_api',
+ 'test_lp_api_lite',
'test_lp_directory',
'test_lp_login',
'test_lp_open',
=== added file 'bzrlib/plugins/launchpad/lp_api_lite.py'
--- a/bzrlib/plugins/launchpad/lp_api_lite.py 1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/launchpad/lp_api_lite.py 2011-07-18 16:51:56 +0000
@@ -0,0 +1,189 @@
+# Copyright (C) 2011 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 without using launchpadlib.
+
+The api itself is a RESTful interface, so we can make HTTP queries directly.
+loading launchpadlib itself has a fairly high overhead (just calling
+Launchpad.login_anonymously() takes a 500ms once the WADL is cached, and 5+s to
+get the WADL.
+"""
+
+try:
+ # Use simplejson if available, much faster, and can be easily installed in
+ # older versions of python
+ import simplejson as json
+except ImportError:
+ # Is present since python 2.6
+ try:
+ import json
+ except ImportError:
+ json = None
+
+import urllib
+import urllib2
+
+from bzrlib import (
+ revision,
+ trace,
+ )
+
+
+class LatestPublication(object):
+ """Encapsulate how to find the latest publication for a given project."""
+
+ LP_API_ROOT = 'https://api.launchpad.net/1.0'
+
+ def __init__(self, archive, series, project):
+ self._archive = archive
+ self._project = project
+ self._setup_series_and_pocket(series)
+
+ def _setup_series_and_pocket(self, series):
+ """Parse the 'series' info into a series and a pocket.
+
+ eg::
+ _setup_series_and_pocket('natty-proposed')
+ => _series == 'natty'
+ _pocket == 'Proposed'
+ """
+ self._series = series
+ self._pocket = None
+ if self._series is not None and '-' in self._series:
+ self._series, self._pocket = self._series.split('-', 1)
+ self._pocket = self._pocket.title()
+ else:
+ self._pocket = 'Release'
+
+ def _archive_URL(self):
+ """Return the Launchpad 'Archive' URL that we will query.
+ This is everything in the URL except the query parameters.
+ """
+ return '%s/%s/+archive/primary' % (self.LP_API_ROOT, self._archive)
+
+ def _publication_status(self):
+ """Handle the 'status' field.
+ It seems that Launchpad tracks all 'debian' packages as 'Pending', while
+ for 'ubuntu' we care about the 'Published' packages.
+ """
+ if self._archive == 'debian':
+ # Launchpad only tracks debian packages as "Pending", it doesn't mark
+ # them Published
+ return 'Pending'
+ return 'Published'
+
+ def _query_params(self):
+ """Get the parameters defining our query.
+ This defines the actions we are making against the archive.
+ :return: A dict of query parameters.
+ """
+ params = {'ws.op': 'getPublishedSources',
+ 'exact_match': 'true',
+ # If we need to use "" shouldn't we quote the project somehow?
+ 'source_name': '"%s"' % (self._project,),
+ 'status': self._publication_status(),
+ # We only need the latest one, the results seem to be properly
+ # most-recent-debian-version sorted
+ 'ws.size': '1',
+ }
+ if self._series is not None:
+ params['distro_series'] = '/%s/%s' % (self._archive, self._series)
+ if self._pocket is not None:
+ params['pocket'] = self._pocket
+ return params
+
+ def _query_URL(self):
+ """Create the full URL that we need to query, including parameters."""
+ params = self._query_params()
+ # We sort to give deterministic results for testing
+ encoded = urllib.urlencode(sorted(params.items()))
+ return '%s?%s' % (self._archive_URL(), encoded)
+
+ def _get_lp_info(self):
+ """Place an actual HTTP query against the Launchpad service."""
+ if json is None:
+ return None
+ query_URL = self._query_URL()
+ try:
+ req = urllib2.Request(query_URL)
+ response = urllib2.urlopen(req)
+ json_info = response.read()
+ # TODO: We haven't tested the HTTPError
+ except (urllib2.URLError, urllib2.HTTPError), e:
+ trace.mutter('failed to place query to %r' % (query_URL,))
+ trace.log_exception_quietly()
+ return None
+ return json_info
+
+ def _parse_json_info(self, json_info):
+ """Parse the json response from Launchpad into objects."""
+ if json is None:
+ return None
+ try:
+ return json.loads(json_info)
+ except Exception:
+ trace.mutter('Failed to parse json info: %r' % (json_info,))
+ trace.log_exception_quietly()
+ return None
+
+ def get_latest_version(self):
+ """Get the latest published version for the given package."""
+ json_info = self._get_lp_info()
+ if json_info is None:
+ return None
+ info = self._parse_json_info(json_info)
+ if info is None:
+ return None
+ try:
+ entries = info['entries']
+ if len(entries) == 0:
+ return None
+ return entries[0]['source_package_version']
+ except KeyError:
+ trace.log_exception_quietly()
+ return None
+
+
+def get_latest_publication(archive, series, project):
+ """Get the most recent publication for a given project.
+
+ :param archive: Either 'ubuntu' or 'debian'
+ :param series: Something like 'natty', 'sid', etc. Can be set as None. Can
+ also include a pocket such as 'natty-proposed'.
+ :param project: Something like 'bzr'
+ :return: A version string indicating the most-recent version published in
+ Launchpad. Might return None if there is an error.
+ """
+ lp = LatestPublication(archive, series, project)
+ return lp.get_latest_version()
+
+
+def get_most_recent_tag(tag_dict, the_branch):
+ """Get the most recent revision that has been tagged."""
+ # Note: this assumes that a given rev won't get tagged multiple times. But
+ # it should be valid for the package importer branches that we care
+ # about
+ reverse_dict = dict((rev, tag) for tag, rev in tag_dict.iteritems())
+ the_branch.lock_read()
+ try:
+ last_rev = the_branch.last_revision()
+ graph = the_branch.repository.get_graph()
+ stop_revisions = (None, revision.NULL_REVISION)
+ for rev_id in graph.iter_lefthand_ancestry(last_rev, stop_revisions):
+ if rev_id in reverse_dict:
+ return reverse_dict[rev_id]
+ finally:
+ the_branch.unlock()
=== added file 'bzrlib/plugins/launchpad/test_lp_api_lite.py'
--- a/bzrlib/plugins/launchpad/test_lp_api_lite.py 1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/launchpad/test_lp_api_lite.py 2011-07-18 16:51:56 +0000
@@ -0,0 +1,350 @@
+# Copyright (C) 2011 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 without using launchpadlib.
+"""
+
+import socket
+
+from bzrlib import tests
+from bzrlib.plugins import launchpad
+from bzrlib.plugins.launchpad import lp_api_lite
+
+class _JSONParserFeature(tests.Feature):
+
+ def _probe(self):
+ return lp_api_lite.json is not None
+
+ def feature_name(self):
+ return 'simplejson or json'
+
+JSONParserFeature = _JSONParserFeature()
+
+_example_response = r"""
+{
+ "total_size": 2,
+ "start": 0,
+ "next_collection_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary?distro_series=%2Fubuntu%2Flucid&exact_match=true&source_name=%22bzr%22&status=Published&ws.op=getPublishedSources&ws.start=1&ws.size=1",
+ "entries": [
+ {
+ "package_creator_link": "https://api.launchpad.net/1.0/~maxb",
+ "package_signer_link": "https://api.launchpad.net/1.0/~jelmer",
+ "source_package_name": "bzr",
+ "removal_comment": null,
+ "display_name": "bzr 2.1.4-0ubuntu1 in lucid",
+ "date_made_pending": null,
+ "source_package_version": "2.1.4-0ubuntu1",
+ "date_superseded": null,
+ "http_etag": "\"9ba966152dec474dc0fe1629d0bbce2452efaf3b-5f4c3fbb3eaf26d502db4089777a9b6a0537ffab\"",
+ "self_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/1750327",
+ "distro_series_link": "https://api.launchpad.net/1.0/ubuntu/lucid",
+ "component_name": "main",
+ "status": "Published",
+ "date_removed": null,
+ "pocket": "Updates",
+ "date_published": "2011-05-30T06:09:58.653984+00:00",
+ "removed_by_link": null,
+ "section_name": "devel",
+ "resource_type_link": "https://api.launchpad.net/1.0/#source_package_publishing_history",
+ "archive_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary",
+ "package_maintainer_link": "https://api.launchpad.net/1.0/~ubuntu-devel-discuss-lists",
+ "date_created": "2011-05-30T05:19:12.233621+00:00",
+ "scheduled_deletion_date": null
+ }
+ ]
+}"""
+
+_no_versions_response = '{"total_size": 0, "start": 0, "entries": []}'
+
+
+class TestLatestPublication(tests.TestCase):
+
+ def make_latest_publication(self, archive='ubuntu', series='natty',
+ project='bzr'):
+ return lp_api_lite.LatestPublication(archive, series, project)
+
+ def test_init(self):
+ latest_pub = self.make_latest_publication()
+ self.assertEqual('ubuntu', latest_pub._archive)
+ self.assertEqual('natty', latest_pub._series)
+ self.assertEqual('bzr', latest_pub._project)
+ self.assertEqual('Release', latest_pub._pocket)
+
+ def test__archive_URL(self):
+ latest_pub = self.make_latest_publication()
+ self.assertEqual(
+ 'https://api.launchpad.net/1.0/ubuntu/+archive/primary',
+ latest_pub._archive_URL())
+
+ def test__publication_status_for_ubuntu(self):
+ latest_pub = self.make_latest_publication()
+ self.assertEqual('Published', latest_pub._publication_status())
+
+ def test__publication_status_for_debian(self):
+ latest_pub = self.make_latest_publication(archive='debian')
+ self.assertEqual('Pending', latest_pub._publication_status())
+
+ def test_pocket(self):
+ latest_pub = self.make_latest_publication(series='natty-proposed')
+ self.assertEqual('natty', latest_pub._series)
+ self.assertEqual('Proposed', latest_pub._pocket)
+
+ def test_series_None(self):
+ latest_pub = self.make_latest_publication(series=None)
+ self.assertEqual('ubuntu', latest_pub._archive)
+ self.assertEqual(None, latest_pub._series)
+ self.assertEqual('bzr', latest_pub._project)
+ self.assertEqual('Release', latest_pub._pocket)
+
+ def test__query_params(self):
+ latest_pub = self.make_latest_publication()
+ self.assertEqual({'ws.op': 'getPublishedSources',
+ 'exact_match': 'true',
+ 'source_name': '"bzr"',
+ 'status': 'Published',
+ 'ws.size': '1',
+ 'distro_series': '/ubuntu/natty',
+ 'pocket': 'Release',
+ }, latest_pub._query_params())
+
+ def test__query_params_no_series(self):
+ latest_pub = self.make_latest_publication(series=None)
+ self.assertEqual({'ws.op': 'getPublishedSources',
+ 'exact_match': 'true',
+ 'source_name': '"bzr"',
+ 'status': 'Published',
+ 'ws.size': '1',
+ 'pocket': 'Release',
+ }, latest_pub._query_params())
+
+ def test__query_params_pocket(self):
+ latest_pub = self.make_latest_publication(series='natty-proposed')
+ self.assertEqual({'ws.op': 'getPublishedSources',
+ 'exact_match': 'true',
+ 'source_name': '"bzr"',
+ 'status': 'Published',
+ 'ws.size': '1',
+ 'distro_series': '/ubuntu/natty',
+ 'pocket': 'Proposed',
+ }, latest_pub._query_params())
+
+ def test__query_URL(self):
+ latest_pub = self.make_latest_publication()
+ # we explicitly sort params, so we can be sure this URL matches exactly
+ self.assertEqual(
+ 'https://api.launchpad.net/1.0/ubuntu/+archive/primary'
+ '?distro_series=%2Fubuntu%2Fnatty&exact_match=true'
+ '&pocket=Release&source_name=%22bzr%22&status=Published'
+ '&ws.op=getPublishedSources&ws.size=1',
+ latest_pub._query_URL())
+
+ def DONT_test__gracefully_handle_failed_rpc_connection(self):
+ # TODO: This test kind of sucks. We intentionally create an arbitrary
+ # port and don't listen to it, because we want the request to fail.
+ # However, it seems to take 1s for it to timeout. Is there a way
+ # to make it fail faster?
+ latest_pub = self.make_latest_publication()
+ s = socket.socket()
+ s.bind(('127.0.0.1', 0))
+ addr, port = s.getsockname()
+ latest_pub.LP_API_ROOT = 'http://%s:%s/' % (addr, port)
+ s.close()
+ self.assertIs(None, latest_pub._get_lp_info())
+
+ def DONT_test__query_launchpad(self):
+ # TODO: This is a test that we are making a valid request against
+ # launchpad. This seems important, but it is slow, requires net
+ # access, and requires launchpad to be up and running. So for
+ # now, it is commented out for production tests.
+ latest_pub = self.make_latest_publication()
+ json_txt = latest_pub._get_lp_info()
+ self.assertIsNot(None, json_txt)
+ if lp_api_lite.json is None:
+ # We don't have a way to parse the text
+ return
+ # The content should be a valid json result
+ content = lp_api_lite.json.loads(json_txt)
+ entries = content['entries'] # It should have an 'entries' field.
+ # ws.size should mean we get 0 or 1, and there should be something
+ self.assertEqual(1, len(entries))
+ entry = entries[0]
+ self.assertEqual('bzr', entry['source_package_name'])
+ version = entry['source_package_version']
+ self.assertIsNot(None, version)
+
+ def test__get_lp_info_no_json(self):
+ # If we can't parse the json, we don't make the query.
+ self.overrideAttr(lp_api_lite, 'json', None)
+ latest_pub = self.make_latest_publication()
+ self.assertIs(None, latest_pub._get_lp_info())
+
+ def test__parse_json_info_no_module(self):
+ # If a json parsing module isn't available, we just return None here.
+ self.overrideAttr(lp_api_lite, 'json', None)
+ latest_pub = self.make_latest_publication()
+ self.assertIs(None, latest_pub._parse_json_info(_example_response))
+
+ def test__parse_json_example_response(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ content = latest_pub._parse_json_info(_example_response)
+ self.assertIsNot(None, content)
+ self.assertEqual(2, content['total_size'])
+ entries = content['entries']
+ self.assertEqual(1, len(entries))
+ entry = entries[0]
+ self.assertEqual('bzr', entry['source_package_name'])
+ self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"])
+
+ def test__parse_json_not_json(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ self.assertIs(None, latest_pub._parse_json_info('Not_valid_json'))
+
+ def test_get_latest_version_no_response(self):
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: None
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_no_json(self):
+ self.overrideAttr(lp_api_lite, 'json', None)
+ latest_pub = self.make_latest_publication()
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_invalid_json(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: "not json"
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_no_versions(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: _no_versions_response
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_missing_entries(self):
+ # Launchpad's no-entries response does have an empty entries value.
+ # However, lets test that we handle other failures without tracebacks
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: '{}'
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_invalid_entries(self):
+ # Make sure we sanely handle a json response we don't understand
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}'
+ self.assertEqual(None, latest_pub.get_latest_version())
+
+ def test_get_latest_version_example(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ latest_pub._get_lp_info = lambda: _example_response
+ self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version())
+
+ def DONT_test_get_latest_version_from_launchpad(self):
+ self.requireFeature(JSONParserFeature)
+ latest_pub = self.make_latest_publication()
+ self.assertIsNot(None, latest_pub.get_latest_version())
+
+
+class TestIsUpToDate(tests.TestCase):
+
+ def assertPackageBranchRe(self, url, user, archive, series, project):
+ m = launchpad._package_branch.search(url)
+ if m is None:
+ self.fail('package_branch regex did not match url: %s' % (url,))
+ self.assertEqual(
+ (user, archive, series, project),
+ m.group('user', 'archive', 'series', 'project'))
+
+ def assertNotPackageBranch(self, url):
+ self.assertIs(None, launchpad._get_package_branch_info(url))
+
+ def assertBranchInfo(self, url, archive, series, project):
+ self.assertEqual((archive, series, project),
+ launchpad._get_package_branch_info(url))
+
+ def test_package_branch_regex(self):
+ self.assertPackageBranchRe(
+ 'http://bazaar.launchpad.net/+branch/ubuntu/foo',
+ None, 'ubuntu', None, 'foo')
+ self.assertPackageBranchRe(
+ 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
+ None, 'ubuntu', 'natty/', 'foo')
+ self.assertPackageBranchRe(
+ 'sftp://bazaar.launchpad.net/+branch/debian/foo',
+ None, 'debian', None, 'foo')
+ self.assertPackageBranchRe(
+ 'http://bazaar.launchpad.net/+branch/debian/sid/foo',
+ None, 'debian', 'sid/', 'foo')
+ self.assertPackageBranchRe(
+ 'http://bazaar.launchpad.net/+branch'
+ '/~ubuntu-branches/ubuntu/natty/foo/natty',
+ '~ubuntu-branches/', 'ubuntu', 'natty/', 'foo')
+ self.assertPackageBranchRe(
+ 'http://bazaar.launchpad.net/+branch'
+ '/~user/ubuntu/natty/foo/test',
+ '~user/', 'ubuntu', 'natty/', 'foo')
+
+ def test_package_branch_doesnt_match(self):
+ self.assertNotPackageBranch('http://example.com/ubuntu/foo')
+ self.assertNotPackageBranch(
+ 'http://bazaar.launchpad.net/+branch/bzr')
+ self.assertNotPackageBranch(
+ 'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev')
+ # Not a packaging branch because ~user isn't ~ubuntu-branches
+ self.assertNotPackageBranch(
+ 'http://bazaar.launchpad.net/+branch'
+ '/~user/ubuntu/natty/foo/natty')
+
+ def test__get_package_branch_info(self):
+ self.assertBranchInfo(
+ 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
+ 'ubuntu', 'natty', 'foo')
+ self.assertBranchInfo(
+ 'bzr+ssh://bazaar.launchpad.net/+branch'
+ '/~ubuntu-branches/ubuntu/natty/foo/natty',
+ 'ubuntu', 'natty', 'foo')
+ self.assertBranchInfo(
+ 'http://bazaar.launchpad.net/+branch'
+ '/~ubuntu-branches/debian/sid/foo/sid',
+ 'debian', 'sid', 'foo')
+
+
+class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport):
+
+ def make_simple_builder(self):
+ builder = self.make_branch_builder('tip')
+ builder.build_snapshot('A', [], [
+ ('add', ('', 'root-id', 'directory', None))])
+ b = builder.get_branch()
+ b.tags.set_tag('tip-1.0', 'A')
+ return builder, b, b.tags.get_tag_dict()
+
+ def test_get_most_recent_tag_tip(self):
+ builder, b, tag_dict = self.make_simple_builder()
+ self.assertEqual('tip-1.0',
+ lp_api_lite.get_most_recent_tag(tag_dict, b))
+
+ def test_get_most_recent_tag_older(self):
+ builder, b, tag_dict = self.make_simple_builder()
+ builder.build_snapshot('B', ['A'], [])
+ self.assertEqual('B', b.last_revision())
+ self.assertEqual('tip-1.0',
+ lp_api_lite.get_most_recent_tag(tag_dict, b))
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- a/doc/en/release-notes/bzr-2.5.txt 2011-07-18 15:09:20 +0000
+++ b/doc/en/release-notes/bzr-2.5.txt 2011-07-18 18:33:35 +0000
@@ -20,15 +20,21 @@
.. New commands, options, etc that users may wish to try out.
+* Accessing a packaging branch on Launchpad (eg, ``lp:ubuntu/bzr``) now
+ checks to see if the most recent published source package version for
+ that project is present in the branch tags. This should help developers
+ trust whether the packaging branch is up-to-date and can be used for new
+ changes. (John Arbash Meinel, #609187)
+
+* Add a config option gpg_signature_key for setting which GPG key
+ should be used to sign commits. Also default to using the gpg user
+ identity which matches user_email() as set by whoami.
+
* bzr log -m now matches message, author, committer and bugs instead
of just matching the message. --message keeps its original meaning,
while --match-message, --match-author, --match-committer and
--match-bugs match each of those fields.
-* Add a config option gpg_signature_key for setting which GPG key
- should be used to sign commits. Also default to using the gpg user
- identity which matches user_email() as set by whoami.
-
Improvements
************
More information about the bazaar-commits
mailing list