Merge lp:~jelmer/ubuntu/maverick/bzr/2.2.5 into lp:ubuntu/maverick-updates/bzr

Proposed by Jelmer Vernooij on 2011-09-27
Status: Merged
Merge reported by: Colin Watson
Merged at revision: not available
Proposed branch: lp:~jelmer/ubuntu/maverick/bzr/2.2.5
Merge into: lp:ubuntu/maverick-updates/bzr
Diff against target: 1694 lines (+1330/-32)
24 files modified
Makefile (+1/-1)
NEWS (+107/-1)
bzr (+2/-2)
bzrlib/__init__.py (+3/-3)
bzrlib/groupcompress.py (+2/-2)
bzrlib/help_topics/en/configuration.txt (+3/-1)
bzrlib/knit.py (+1/-1)
bzrlib/merge.py (+8/-2)
bzrlib/plugins/launchpad/__init__.py (+64/-4)
bzrlib/plugins/launchpad/lp_api_lite.py (+285/-0)
bzrlib/plugins/launchpad/test_lp_api_lite.py (+557/-0)
bzrlib/repofmt/groupcompress_repo.py (+13/-4)
bzrlib/tests/per_repository_reference/__init__.py (+1/-0)
bzrlib/tests/per_repository_reference/test_graph.py (+45/-0)
bzrlib/tests/test_config.py (+27/-1)
bzrlib/tests/test_conflicts.py (+64/-0)
bzrlib/tests/test_repository.py (+101/-1)
bzrlib/transport/http/_pycurl.py (+5/-5)
bzrlib/util/configobj/configobj.py (+4/-2)
bzrlib/versionedfile.py (+13/-0)
debian/changelog (+10/-0)
debian/watch (+1/-1)
doc/en/whats-new/whats-new-in-2.2.txt (+4/-1)
setup.py (+9/-0)
To merge this branch: bzr merge lp:~jelmer/ubuntu/maverick/bzr/2.2.5
Reviewer Review Type Date Requested Status
Colin Watson 2011-09-27 Approve on 2012-01-27
Ubuntu Sponsors Team 2012-01-23 Pending
Review via email: mp+77153@code.launchpad.net

This proposal supersedes a proposal from 2011-09-27.

Description of the change

bzr microrelease 2.2.5

This fixes a couple of bugs that might be affecting users:

 * stacking is not fully transitive (715000)
 * merge failing with NoFinalPath (#805809)

To post a comment you must log in.
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2010-08-07 00:54:52 +0000
3+++ Makefile 2011-09-27 11:59:22 +0000
4@@ -40,7 +40,7 @@
5
6 check-nodocs: extensions
7 # Generate a stream for PQM to watch.
8- $(PYTHON) -Werror -O ./bzr selftest --subunit $(tests) | tee selftest.log
9+ $(PYTHON) -Werror -Wignore::ImportWarning -O ./bzr selftest --subunit $(tests) | tee selftest.log
10 # Check that there were no errors reported.
11 subunit-stats < selftest.log
12
13
14=== modified file 'NEWS'
15--- NEWS 2011-04-07 15:30:17 +0000
16+++ NEWS 2011-09-27 11:59:22 +0000
17@@ -5,6 +5,98 @@
18 .. contents:: List of Releases
19 :depth: 1
20
21+This is a bugfix release. One regression introduced in 2.2b1 has been fixed
22+for some rare conflict resolutions. Also a warning is now emmitted when
23+branching an out-of-date ubuntu packaging branch. Upgrading is recommended
24+for all users on earlier 2.2 releases.
25+
26+bzr 2.2.5
27+#########
28+
29+:2.2.5: 2011-09-01
30+
31+Compatibility Breaks
32+********************
33+
34+None.
35+
36+New Features
37+************
38+
39+None.
40+
41+Bug Fixes
42+*********
43+
44+* Correctly handle ``bzr log`` and `get_known_graph_ancestry` on a
45+ doubly-stacked branch.
46+ (James Westby, Martin Pool, #715000)
47+
48+* Don't crash while merging and encountering obscure path conflicts
49+ involving different root-ids. (Vincent Ladeuil, #805809)
50+
51+Internals
52+*********
53+
54+* Fixed bug in the bundled copy of ConfigObj with quoting of triple quotes
55+ in the value string. Fix suggested by ConfigObj's author Michael Foord.
56+ (Alexander Belchenko, #710410)
57+
58+bzr 2.1.5
59+#########
60+
61+:2.1.5: NOT RELEASED YET
62+
63+Compatibility Breaks
64+********************
65+
66+New Features
67+************
68+
69+Bug Fixes
70+*********
71+
72+* Accessing a packaging branch on Launchpad (eg, ``lp:ubuntu/bzr``) now
73+ checks to see if the most recent published source package version for
74+ that project is present in the branch tags. This should help developers
75+ trust whether the packaging branch is up-to-date and can be used for new
76+ changes. The level of verbosity is controlled by the config item
77+ ``launchpad.packaging_verbosity``. It can be set to one of
78+
79+ off
80+ disable all checks
81+
82+
83+ minimal
84+ only display if the branch is out-of-date
85+
86+ short
87+ also display single-line up-to-date and missing,
88+
89+
90+ all
91+ (default) display multi-line content for all states
92+
93+
94+ (John Arbash Meinel, #609187, #812928)
95+
96+
97+Improvements
98+************
99+
100+Documentation
101+*************
102+
103+API Changes
104+***********
105+
106+Internals
107+*********
108+
109+Testing
110+*******
111+
112+
113 bzr 2.2.4
114 #########
115
116@@ -251,7 +343,12 @@
117 bzr 2.1.4
118 #########
119
120-:2.1.4: NOT RELEASED YET
121+:2.1.4: 2011-05-16
122+
123+The fourth release in our 2.1 series addresses some user-inconvenience bugs.
124+None are critical, but upgrading is recommended for all users on earlier 2.1
125+releases.
126+
127
128 Compatibility Breaks
129 ********************
130@@ -265,6 +362,8 @@
131 New Features
132 ************
133
134+None.
135+
136 Bug Fixes
137 *********
138
139@@ -403,6 +502,13 @@
140 * Avoid UnicodeDecodeError in ``bzr add`` with multiple files under a non-ascii
141 path on windows from symlink support addition. (Martin [gz], #686611)
142
143+* Avoid spurious ValueErrors when autopacking a subset of the repository,
144+ and seeing a revision without its related inventory
145+ (John Arbash Meinel, #437003)
146+
147+* Fix activity reporting for pycurl when using https with some
148+ implementations of curl. (Vincent Ladeuil, #614713)
149+
150 Improvements
151 ************
152
153
154=== modified file 'bzr'
155--- bzr 2011-04-07 15:30:17 +0000
156+++ bzr 2011-09-27 11:59:22 +0000
157@@ -1,6 +1,6 @@
158 #! /usr/bin/env python
159
160-# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Canonical Ltd
161+# Copyright (C) 2005-2011 Canonical Ltd
162 #
163 # This program is free software; you can redistribute it and/or modify
164 # it under the terms of the GNU General Public License as published by
165@@ -23,7 +23,7 @@
166 import warnings
167
168 # update this on each release
169-_script_version = (2, 2, 4)
170+_script_version = (2, 2, 5)
171
172 try:
173 version_info = sys.version_info
174
175=== modified file 'bzrlib/__init__.py'
176--- bzrlib/__init__.py 2011-04-07 15:30:17 +0000
177+++ bzrlib/__init__.py 2011-09-27 11:59:22 +0000
178@@ -1,4 +1,4 @@
179-# Copyright (C) 2005-2010 Canonical Ltd
180+# Copyright (C) 2005-2011 Canonical Ltd
181 #
182 # This program is free software; you can redistribute it and/or modify
183 # it under the terms of the GNU General Public License as published by
184@@ -43,7 +43,7 @@
185 IGNORE_FILENAME = ".bzrignore"
186
187
188-__copyright__ = "Copyright 2005-2010 Canonical Ltd."
189+__copyright__ = "Copyright 2005-2011 Canonical Ltd."
190
191 # same format as sys.version_info: "A tuple containing the five components of
192 # the version number: major, minor, micro, releaselevel, and serial. All
193@@ -52,7 +52,7 @@
194 # Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
195 # releaselevel of 'dev' for unreleased under-development code.
196
197-version_info = (2, 2, 4, 'final', 0)
198+version_info = (2, 2, 5, 'final', 0)
199
200 # API compatibility version
201 api_minimum_version = (2, 2, 0)
202
203=== modified file 'bzrlib/groupcompress.py'
204--- bzrlib/groupcompress.py 2010-05-27 21:58:49 +0000
205+++ bzrlib/groupcompress.py 2011-09-27 11:59:22 +0000
206@@ -1,4 +1,4 @@
207-# Copyright (C) 2008, 2009, 2010 Canonical Ltd
208+# Copyright (C) 2008-2011 Canonical Ltd
209 #
210 # This program is free software; you can redistribute it and/or modify
211 # it under the terms of the GNU General Public License as published by
212@@ -1293,7 +1293,7 @@
213 # KnitVersionedFiles.get_known_graph_ancestry, but they don't share
214 # ancestry.
215 parent_map, missing_keys = self._index.find_ancestry(keys)
216- for fallback in self._fallback_vfs:
217+ for fallback in self._transitive_fallbacks():
218 if not missing_keys:
219 break
220 (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
221
222=== modified file 'bzrlib/help_topics/en/configuration.txt'
223--- bzrlib/help_topics/en/configuration.txt 2010-08-07 00:54:52 +0000
224+++ bzrlib/help_topics/en/configuration.txt 2011-09-27 11:59:22 +0000
225@@ -19,7 +19,9 @@
226 BZR_PROGRESS_BAR
227 ~~~~~~~~~~~~~~~~
228
229-Override the progress display. Possible values are "none", "dots", "tty"
230+Override the progress display. Possible values are "none" or "text". If
231+the value is "none" then no progress bar is displayed. The value "text" draws
232+the ordinary command line progress bar.
233
234 BZR_SIGQUIT_PDB
235 ~~~~~~~~~~~~~~~
236
237=== modified file 'bzrlib/knit.py'
238--- bzrlib/knit.py 2010-08-07 00:54:52 +0000
239+++ bzrlib/knit.py 2011-09-27 11:59:22 +0000
240@@ -1195,7 +1195,7 @@
241 def get_known_graph_ancestry(self, keys):
242 """Get a KnownGraph instance with the ancestry of keys."""
243 parent_map, missing_keys = self._index.find_ancestry(keys)
244- for fallback in self._fallback_vfs:
245+ for fallback in self._transitive_fallbacks():
246 if not missing_keys:
247 break
248 (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
249
250=== modified file 'bzrlib/merge.py'
251--- bzrlib/merge.py 2010-08-07 00:54:52 +0000
252+++ bzrlib/merge.py 2011-09-27 11:59:22 +0000
253@@ -1624,8 +1624,14 @@
254 if other_parent is None or other_name is None:
255 other_path = '<deleted>'
256 else:
257- parent_path = fp.get_path(
258- self.tt.trans_id_file_id(other_parent))
259+ if other_parent == self.other_tree.get_root_id():
260+ # The tree transform doesn't know about the other root,
261+ # so we special case here to avoid a NoFinalPath
262+ # exception
263+ parent_path = ''
264+ else:
265+ parent_path = fp.get_path(
266+ self.tt.trans_id_file_id(other_parent))
267 other_path = osutils.pathjoin(parent_path, other_name)
268 c = _mod_conflicts.Conflict.factory(
269 'path conflict', path=this_path,
270
271=== modified file 'bzrlib/plugins/launchpad/__init__.py'
272--- bzrlib/plugins/launchpad/__init__.py 2010-08-07 00:54:52 +0000
273+++ bzrlib/plugins/launchpad/__init__.py 2011-09-27 11:59:22 +0000
274@@ -21,13 +21,9 @@
275
276 # see http://bazaar-vcs.org/Specs/BranchRegistrationTool
277
278-# Since we are a built-in plugin we share the bzrlib version
279-from bzrlib import version_info
280-
281 from bzrlib.lazy_import import lazy_import
282 lazy_import(globals(), """
283 from bzrlib import (
284- branch as _mod_branch,
285 trace,
286 )
287 """)
288@@ -37,6 +33,12 @@
289 Command,
290 register_command,
291 )
292+from bzrlib import (
293+ branch as _mod_branch,
294+ lazy_regex,
295+ # Since we are a built-in plugin we share the bzrlib version
296+ version_info,
297+ )
298 from bzrlib.directory_service import directories
299 from bzrlib.errors import (
300 BzrCommandError,
301@@ -354,12 +356,70 @@
302 'Launchpad-based directory service',)
303 _register_directory()
304
305+# This is kept in __init__ so that we don't load lp_api_lite unless the branch
306+# actually matches. That way we can avoid importing extra dependencies like
307+# json.
308+_package_branch = lazy_regex.lazy_compile(
309+ r'bazaar.launchpad.net.*?/'
310+ r'(?P<user>~[^/]+/)?(?P<archive>ubuntu|debian)/(?P<series>[^/]+/)?'
311+ r'(?P<project>[^/]+)(?P<branch>/[^/]+)?'
312+ )
313+
314+def _get_package_branch_info(url):
315+ """Determine the packaging information for this URL.
316+
317+ :return: If this isn't a packaging branch, return None. If it is, return
318+ (archive, series, project)
319+ """
320+ if url is None:
321+ return None
322+ m = _package_branch.search(url)
323+ if m is None:
324+ return None
325+ archive, series, project, user = m.group('archive', 'series',
326+ 'project', 'user')
327+ if series is not None:
328+ # series is optional, so the regex includes the extra '/', we don't
329+ # want to send that on (it causes Internal Server Errors.)
330+ series = series.strip('/')
331+ if user is not None:
332+ user = user.strip('~/')
333+ if user != 'ubuntu-branches':
334+ return None
335+ return archive, series, project
336+
337+
338+def _check_is_up_to_date(the_branch):
339+ info = _get_package_branch_info(the_branch.base)
340+ if info is None:
341+ return
342+ c = the_branch.get_config()
343+ verbosity = c.get_user_option('launchpad.packaging_verbosity')
344+ if verbosity is not None:
345+ verbosity = verbosity.lower()
346+ if verbosity == 'off':
347+ trace.mutter('not checking %s because verbosity is turned off'
348+ % (the_branch.base,))
349+ return
350+ archive, series, project = info
351+ from bzrlib.plugins.launchpad import lp_api_lite
352+ latest_pub = lp_api_lite.LatestPublication(archive, series, project)
353+ lp_api_lite.report_freshness(the_branch, verbosity, latest_pub)
354+
355+
356+def _register_hooks():
357+ _mod_branch.Branch.hooks.install_named_hook('open',
358+ _check_is_up_to_date, 'package-branch-up-to-date')
359+
360+
361+_register_hooks()
362
363 def load_tests(basic_tests, module, loader):
364 testmod_names = [
365 'test_account',
366 'test_register',
367 'test_lp_api',
368+ 'test_lp_api_lite',
369 'test_lp_directory',
370 'test_lp_login',
371 'test_lp_open',
372
373=== added file 'bzrlib/plugins/launchpad/lp_api_lite.py'
374--- bzrlib/plugins/launchpad/lp_api_lite.py 1970-01-01 00:00:00 +0000
375+++ bzrlib/plugins/launchpad/lp_api_lite.py 2011-09-27 11:59:22 +0000
376@@ -0,0 +1,285 @@
377+# Copyright (C) 2011 Canonical Ltd
378+#
379+# This program is free software; you can redistribute it and/or modify
380+# it under the terms of the GNU General Public License as published by
381+# the Free Software Foundation; either version 2 of the License, or
382+# (at your option) any later version.
383+#
384+# This program is distributed in the hope that it will be useful,
385+# but WITHOUT ANY WARRANTY; without even the implied warranty of
386+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
387+# GNU General Public License for more details.
388+#
389+# You should have received a copy of the GNU General Public License
390+# along with this program; if not, write to the Free Software
391+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
392+
393+"""Tools for dealing with the Launchpad API without using launchpadlib.
394+
395+The api itself is a RESTful interface, so we can make HTTP queries directly.
396+loading launchpadlib itself has a fairly high overhead (just calling
397+Launchpad.login_anonymously() takes a 500ms once the WADL is cached, and 5+s to
398+get the WADL.
399+"""
400+
401+try:
402+ # Use simplejson if available, much faster, and can be easily installed in
403+ # older versions of python
404+ import simplejson as json
405+except ImportError:
406+ # Is present since python 2.6
407+ try:
408+ import json
409+ except ImportError:
410+ json = None
411+
412+import time
413+import urllib
414+import urllib2
415+
416+from bzrlib import (
417+ revision,
418+ trace,
419+ )
420+
421+
422+class LatestPublication(object):
423+ """Encapsulate how to find the latest publication for a given project."""
424+
425+ LP_API_ROOT = 'https://api.launchpad.net/1.0'
426+
427+ def __init__(self, archive, series, project):
428+ self._archive = archive
429+ self._project = project
430+ self._setup_series_and_pocket(series)
431+
432+ def _setup_series_and_pocket(self, series):
433+ """Parse the 'series' info into a series and a pocket.
434+
435+ eg::
436+ _setup_series_and_pocket('natty-proposed')
437+ => _series == 'natty'
438+ _pocket == 'Proposed'
439+ """
440+ self._series = series
441+ self._pocket = None
442+ if self._series is not None and '-' in self._series:
443+ self._series, self._pocket = self._series.split('-', 1)
444+ self._pocket = self._pocket.title()
445+ else:
446+ self._pocket = 'Release'
447+
448+ def _archive_URL(self):
449+ """Return the Launchpad 'Archive' URL that we will query.
450+ This is everything in the URL except the query parameters.
451+ """
452+ return '%s/%s/+archive/primary' % (self.LP_API_ROOT, self._archive)
453+
454+ def _publication_status(self):
455+ """Handle the 'status' field.
456+ It seems that Launchpad tracks all 'debian' packages as 'Pending', while
457+ for 'ubuntu' we care about the 'Published' packages.
458+ """
459+ if self._archive == 'debian':
460+ # Launchpad only tracks debian packages as "Pending", it doesn't mark
461+ # them Published
462+ return 'Pending'
463+ return 'Published'
464+
465+ def _query_params(self):
466+ """Get the parameters defining our query.
467+ This defines the actions we are making against the archive.
468+ :return: A dict of query parameters.
469+ """
470+ params = {'ws.op': 'getPublishedSources',
471+ 'exact_match': 'true',
472+ # If we need to use "" shouldn't we quote the project somehow?
473+ 'source_name': '"%s"' % (self._project,),
474+ 'status': self._publication_status(),
475+ # We only need the latest one, the results seem to be properly
476+ # most-recent-debian-version sorted
477+ 'ws.size': '1',
478+ }
479+ if self._series is not None:
480+ params['distro_series'] = '/%s/%s' % (self._archive, self._series)
481+ if self._pocket is not None:
482+ params['pocket'] = self._pocket
483+ return params
484+
485+ def _query_URL(self):
486+ """Create the full URL that we need to query, including parameters."""
487+ params = self._query_params()
488+ # We sort to give deterministic results for testing
489+ encoded = urllib.urlencode(sorted(params.items()))
490+ return '%s?%s' % (self._archive_URL(), encoded)
491+
492+ def _get_lp_info(self):
493+ """Place an actual HTTP query against the Launchpad service."""
494+ if json is None:
495+ return None
496+ query_URL = self._query_URL()
497+ try:
498+ req = urllib2.Request(query_URL)
499+ response = urllib2.urlopen(req)
500+ json_info = response.read()
501+ # TODO: We haven't tested the HTTPError
502+ except (urllib2.URLError, urllib2.HTTPError), e:
503+ trace.mutter('failed to place query to %r' % (query_URL,))
504+ trace.log_exception_quietly()
505+ return None
506+ return json_info
507+
508+ def _parse_json_info(self, json_info):
509+ """Parse the json response from Launchpad into objects."""
510+ if json is None:
511+ return None
512+ try:
513+ return json.loads(json_info)
514+ except Exception:
515+ trace.mutter('Failed to parse json info: %r' % (json_info,))
516+ trace.log_exception_quietly()
517+ return None
518+
519+ def get_latest_version(self):
520+ """Get the latest published version for the given package."""
521+ json_info = self._get_lp_info()
522+ if json_info is None:
523+ return None
524+ info = self._parse_json_info(json_info)
525+ if info is None:
526+ return None
527+ try:
528+ entries = info['entries']
529+ if len(entries) == 0:
530+ return None
531+ return entries[0]['source_package_version']
532+ except KeyError:
533+ trace.log_exception_quietly()
534+ return None
535+
536+ def place(self):
537+ """Text-form for what location this represents.
538+
539+ Example::
540+ ubuntu, natty => Ubuntu Natty
541+ ubuntu, natty-proposed => Ubuntu Natty Proposed
542+ :return: A string representing the location we are checking.
543+ """
544+ place = self._archive
545+ if self._series is not None:
546+ place = '%s %s' % (place, self._series)
547+ if self._pocket is not None and self._pocket != 'Release':
548+ place = '%s %s' % (place, self._pocket)
549+ return place.title()
550+
551+
552+def get_latest_publication(archive, series, project):
553+ """Get the most recent publication for a given project.
554+
555+ :param archive: Either 'ubuntu' or 'debian'
556+ :param series: Something like 'natty', 'sid', etc. Can be set as None. Can
557+ also include a pocket such as 'natty-proposed'.
558+ :param project: Something like 'bzr'
559+ :return: A version string indicating the most-recent version published in
560+ Launchpad. Might return None if there is an error.
561+ """
562+ lp = LatestPublication(archive, series, project)
563+ return lp.get_latest_version()
564+
565+
566+def get_most_recent_tag(tag_dict, the_branch):
567+ """Get the most recent revision that has been tagged."""
568+ # Note: this assumes that a given rev won't get tagged multiple times. But
569+ # it should be valid for the package importer branches that we care
570+ # about
571+ reverse_dict = dict((rev, tag) for tag, rev in tag_dict.iteritems())
572+ the_branch.lock_read()
573+ try:
574+ history = the_branch.repository.iter_reverse_revision_history(
575+ the_branch.last_revision())
576+ for rev_id in history:
577+ if rev_id in reverse_dict:
578+ return reverse_dict[rev_id]
579+ finally:
580+ the_branch.unlock()
581+
582+
583+def _get_newest_versions(the_branch, latest_pub):
584+ """Get information about how 'fresh' this packaging branch is.
585+
586+ :param the_branch: The Branch to check
587+ :param latest_pub: The LatestPublication used to check most recent
588+ published version.
589+ :return: (latest_ver, branch_latest_ver)
590+ """
591+ t = time.time()
592+ latest_ver = latest_pub.get_latest_version()
593+ t_latest_ver = time.time() - t
594+ trace.mutter('LatestPublication.get_latest_version took: %.3fs'
595+ % (t_latest_ver,))
596+ if latest_ver is None:
597+ return None, None
598+ t = time.time()
599+ tags = the_branch.tags.get_tag_dict()
600+ t_tag_dict = time.time() - t
601+ trace.mutter('LatestPublication.get_tag_dict took: %.3fs' % (t_tag_dict,))
602+ if latest_ver in tags:
603+ # branch might have a newer tag, but we don't really care
604+ return latest_ver, latest_ver
605+ else:
606+ best_tag = get_most_recent_tag(tags, the_branch)
607+ return latest_ver, best_tag
608+
609+
610+def _report_freshness(latest_ver, branch_latest_ver, place, verbosity,
611+ report_func):
612+ """Report if the branch is up-to-date."""
613+ if latest_ver is None:
614+ if verbosity == 'all':
615+ report_func('Most recent %s version: MISSING' % (place,))
616+ elif verbosity == 'short':
617+ report_func('%s is MISSING a version' % (place,))
618+ return
619+ elif latest_ver == branch_latest_ver:
620+ if verbosity == 'minimal':
621+ return
622+ elif verbosity == 'short':
623+ report_func('%s is CURRENT in %s' % (latest_ver, place))
624+ else:
625+ report_func('Most recent %s version: %s\n'
626+ 'Packaging branch status: CURRENT'
627+ % (place, latest_ver))
628+ else:
629+ if verbosity in ('minimal', 'short'):
630+ if branch_latest_ver is None:
631+ branch_latest_ver = 'Branch'
632+ report_func('%s is OUT-OF-DATE, %s has %s'
633+ % (branch_latest_ver, place, latest_ver))
634+ else:
635+ report_func('Most recent %s version: %s\n'
636+ 'Packaging branch version: %s\n'
637+ 'Packaging branch status: OUT-OF-DATE'
638+ % (place, latest_ver, branch_latest_ver))
639+
640+
641+def report_freshness(the_branch, verbosity, latest_pub):
642+ """Report to the user how up-to-date the packaging branch is.
643+
644+ :param the_branch: A Branch object
645+ :param verbosity: Can be one of:
646+ off: Do not print anything, and skip all checks.
647+ all: Print all information that we have in a verbose manner, this
648+ includes misses, etc.
649+ short: Print information, but only one-line summaries
650+ minimal: Only print a one-line summary when the package branch is
651+ out-of-date
652+ :param latest_pub: A LatestPublication instance
653+ """
654+ if verbosity == 'off':
655+ return
656+ if verbosity is None:
657+ verbosity = 'all'
658+ latest_ver, branch_ver = _get_newest_versions(the_branch, latest_pub)
659+ place = latest_pub.place()
660+ _report_freshness(latest_ver, branch_ver, place, verbosity,
661+ trace.note)
662
663=== added file 'bzrlib/plugins/launchpad/test_lp_api_lite.py'
664--- bzrlib/plugins/launchpad/test_lp_api_lite.py 1970-01-01 00:00:00 +0000
665+++ bzrlib/plugins/launchpad/test_lp_api_lite.py 2011-09-27 11:59:22 +0000
666@@ -0,0 +1,557 @@
667+# Copyright (C) 2011 Canonical Ltd
668+#
669+# This program is free software; you can redistribute it and/or modify
670+# it under the terms of the GNU General Public License as published by
671+# the Free Software Foundation; either version 2 of the License, or
672+# (at your option) any later version.
673+#
674+# This program is distributed in the hope that it will be useful,
675+# but WITHOUT ANY WARRANTY; without even the implied warranty of
676+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
677+# GNU General Public License for more details.
678+#
679+# You should have received a copy of the GNU General Public License
680+# along with this program; if not, write to the Free Software
681+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
682+
683+"""Tools for dealing with the Launchpad API without using launchpadlib.
684+"""
685+
686+import doctest
687+import socket
688+
689+from bzrlib import tests
690+from bzrlib.plugins import launchpad
691+from bzrlib.plugins.launchpad import lp_api_lite
692+
693+from testtools.matchers import DocTestMatches
694+
695+
696+class _JSONParserFeature(tests.Feature):
697+
698+ def _probe(self):
699+ return lp_api_lite.json is not None
700+
701+ def feature_name(self):
702+ return 'simplejson or json'
703+
704+JSONParserFeature = _JSONParserFeature()
705+
706+_example_response = r"""
707+{
708+ "total_size": 2,
709+ "start": 0,
710+ "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",
711+ "entries": [
712+ {
713+ "package_creator_link": "https://api.launchpad.net/1.0/~maxb",
714+ "package_signer_link": "https://api.launchpad.net/1.0/~jelmer",
715+ "source_package_name": "bzr",
716+ "removal_comment": null,
717+ "display_name": "bzr 2.1.4-0ubuntu1 in lucid",
718+ "date_made_pending": null,
719+ "source_package_version": "2.1.4-0ubuntu1",
720+ "date_superseded": null,
721+ "http_etag": "\"9ba966152dec474dc0fe1629d0bbce2452efaf3b-5f4c3fbb3eaf26d502db4089777a9b6a0537ffab\"",
722+ "self_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/1750327",
723+ "distro_series_link": "https://api.launchpad.net/1.0/ubuntu/lucid",
724+ "component_name": "main",
725+ "status": "Published",
726+ "date_removed": null,
727+ "pocket": "Updates",
728+ "date_published": "2011-05-30T06:09:58.653984+00:00",
729+ "removed_by_link": null,
730+ "section_name": "devel",
731+ "resource_type_link": "https://api.launchpad.net/1.0/#source_package_publishing_history",
732+ "archive_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary",
733+ "package_maintainer_link": "https://api.launchpad.net/1.0/~ubuntu-devel-discuss-lists",
734+ "date_created": "2011-05-30T05:19:12.233621+00:00",
735+ "scheduled_deletion_date": null
736+ }
737+ ]
738+}"""
739+
740+_no_versions_response = '{"total_size": 0, "start": 0, "entries": []}'
741+
742+
743+class TestLatestPublication(tests.TestCase):
744+
745+ def make_latest_publication(self, archive='ubuntu', series='natty',
746+ project='bzr'):
747+ return lp_api_lite.LatestPublication(archive, series, project)
748+
749+ def assertPlace(self, place, archive, series, project):
750+ lp = lp_api_lite.LatestPublication(archive, series, project)
751+ self.assertEqual(place, lp.place())
752+
753+ def test_init(self):
754+ latest_pub = self.make_latest_publication()
755+ self.assertEqual('ubuntu', latest_pub._archive)
756+ self.assertEqual('natty', latest_pub._series)
757+ self.assertEqual('bzr', latest_pub._project)
758+ self.assertEqual('Release', latest_pub._pocket)
759+
760+ def test__archive_URL(self):
761+ latest_pub = self.make_latest_publication()
762+ self.assertEqual(
763+ 'https://api.launchpad.net/1.0/ubuntu/+archive/primary',
764+ latest_pub._archive_URL())
765+
766+ def test__publication_status_for_ubuntu(self):
767+ latest_pub = self.make_latest_publication()
768+ self.assertEqual('Published', latest_pub._publication_status())
769+
770+ def test__publication_status_for_debian(self):
771+ latest_pub = self.make_latest_publication(archive='debian')
772+ self.assertEqual('Pending', latest_pub._publication_status())
773+
774+ def test_pocket(self):
775+ latest_pub = self.make_latest_publication(series='natty-proposed')
776+ self.assertEqual('natty', latest_pub._series)
777+ self.assertEqual('Proposed', latest_pub._pocket)
778+
779+ def test_series_None(self):
780+ latest_pub = self.make_latest_publication(series=None)
781+ self.assertEqual('ubuntu', latest_pub._archive)
782+ self.assertEqual(None, latest_pub._series)
783+ self.assertEqual('bzr', latest_pub._project)
784+ self.assertEqual('Release', latest_pub._pocket)
785+
786+ def test__query_params(self):
787+ latest_pub = self.make_latest_publication()
788+ self.assertEqual({'ws.op': 'getPublishedSources',
789+ 'exact_match': 'true',
790+ 'source_name': '"bzr"',
791+ 'status': 'Published',
792+ 'ws.size': '1',
793+ 'distro_series': '/ubuntu/natty',
794+ 'pocket': 'Release',
795+ }, latest_pub._query_params())
796+
797+ def test__query_params_no_series(self):
798+ latest_pub = self.make_latest_publication(series=None)
799+ self.assertEqual({'ws.op': 'getPublishedSources',
800+ 'exact_match': 'true',
801+ 'source_name': '"bzr"',
802+ 'status': 'Published',
803+ 'ws.size': '1',
804+ 'pocket': 'Release',
805+ }, latest_pub._query_params())
806+
807+ def test__query_params_pocket(self):
808+ latest_pub = self.make_latest_publication(series='natty-proposed')
809+ self.assertEqual({'ws.op': 'getPublishedSources',
810+ 'exact_match': 'true',
811+ 'source_name': '"bzr"',
812+ 'status': 'Published',
813+ 'ws.size': '1',
814+ 'distro_series': '/ubuntu/natty',
815+ 'pocket': 'Proposed',
816+ }, latest_pub._query_params())
817+
818+ def test__query_URL(self):
819+ latest_pub = self.make_latest_publication()
820+ # we explicitly sort params, so we can be sure this URL matches exactly
821+ self.assertEqual(
822+ 'https://api.launchpad.net/1.0/ubuntu/+archive/primary'
823+ '?distro_series=%2Fubuntu%2Fnatty&exact_match=true'
824+ '&pocket=Release&source_name=%22bzr%22&status=Published'
825+ '&ws.op=getPublishedSources&ws.size=1',
826+ latest_pub._query_URL())
827+
828+ def DONT_test__gracefully_handle_failed_rpc_connection(self):
829+ # TODO: This test kind of sucks. We intentionally create an arbitrary
830+ # port and don't listen to it, because we want the request to fail.
831+ # However, it seems to take 1s for it to timeout. Is there a way
832+ # to make it fail faster?
833+ latest_pub = self.make_latest_publication()
834+ s = socket.socket()
835+ s.bind(('127.0.0.1', 0))
836+ addr, port = s.getsockname()
837+ latest_pub.LP_API_ROOT = 'http://%s:%s/' % (addr, port)
838+ s.close()
839+ self.assertIs(None, latest_pub._get_lp_info())
840+
841+ def DONT_test__query_launchpad(self):
842+ # TODO: This is a test that we are making a valid request against
843+ # launchpad. This seems important, but it is slow, requires net
844+ # access, and requires launchpad to be up and running. So for
845+ # now, it is commented out for production tests.
846+ latest_pub = self.make_latest_publication()
847+ json_txt = latest_pub._get_lp_info()
848+ self.assertIsNot(None, json_txt)
849+ if lp_api_lite.json is None:
850+ # We don't have a way to parse the text
851+ return
852+ # The content should be a valid json result
853+ content = lp_api_lite.json.loads(json_txt)
854+ entries = content['entries'] # It should have an 'entries' field.
855+ # ws.size should mean we get 0 or 1, and there should be something
856+ self.assertEqual(1, len(entries))
857+ entry = entries[0]
858+ self.assertEqual('bzr', entry['source_package_name'])
859+ version = entry['source_package_version']
860+ self.assertIsNot(None, version)
861+
862+ def disableJSON(self):
863+ orig = lp_api_lite.json
864+ def cleanup():
865+ lp_api_lite.json = orig
866+ self.addCleanup(cleanup)
867+ lp_api_lite.json = None
868+
869+ def test__get_lp_info_no_json(self):
870+ # If we can't parse the json, we don't make the query.
871+ self.disableJSON()
872+ latest_pub = self.make_latest_publication()
873+ self.assertIs(None, latest_pub._get_lp_info())
874+
875+ def test__parse_json_info_no_module(self):
876+ # If a json parsing module isn't available, we just return None here.
877+ self.disableJSON()
878+ latest_pub = self.make_latest_publication()
879+ self.assertIs(None, latest_pub._parse_json_info(_example_response))
880+
881+ def test__parse_json_example_response(self):
882+ self.requireFeature(JSONParserFeature)
883+ latest_pub = self.make_latest_publication()
884+ content = latest_pub._parse_json_info(_example_response)
885+ self.assertIsNot(None, content)
886+ self.assertEqual(2, content['total_size'])
887+ entries = content['entries']
888+ self.assertEqual(1, len(entries))
889+ entry = entries[0]
890+ self.assertEqual('bzr', entry['source_package_name'])
891+ self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"])
892+
893+ def test__parse_json_not_json(self):
894+ self.requireFeature(JSONParserFeature)
895+ latest_pub = self.make_latest_publication()
896+ self.assertIs(None, latest_pub._parse_json_info('Not_valid_json'))
897+
898+ def test_get_latest_version_no_response(self):
899+ latest_pub = self.make_latest_publication()
900+ latest_pub._get_lp_info = lambda: None
901+ self.assertEqual(None, latest_pub.get_latest_version())
902+
903+ def test_get_latest_version_no_json(self):
904+ self.disableJSON()
905+ latest_pub = self.make_latest_publication()
906+ self.assertEqual(None, latest_pub.get_latest_version())
907+
908+ def test_get_latest_version_invalid_json(self):
909+ self.requireFeature(JSONParserFeature)
910+ latest_pub = self.make_latest_publication()
911+ latest_pub._get_lp_info = lambda: "not json"
912+ self.assertEqual(None, latest_pub.get_latest_version())
913+
914+ def test_get_latest_version_no_versions(self):
915+ self.requireFeature(JSONParserFeature)
916+ latest_pub = self.make_latest_publication()
917+ latest_pub._get_lp_info = lambda: _no_versions_response
918+ self.assertEqual(None, latest_pub.get_latest_version())
919+
920+ def test_get_latest_version_missing_entries(self):
921+ # Launchpad's no-entries response does have an empty entries value.
922+ # However, lets test that we handle other failures without tracebacks
923+ self.requireFeature(JSONParserFeature)
924+ latest_pub = self.make_latest_publication()
925+ latest_pub._get_lp_info = lambda: '{}'
926+ self.assertEqual(None, latest_pub.get_latest_version())
927+
928+ def test_get_latest_version_invalid_entries(self):
929+ # Make sure we sanely handle a json response we don't understand
930+ self.requireFeature(JSONParserFeature)
931+ latest_pub = self.make_latest_publication()
932+ latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}'
933+ self.assertEqual(None, latest_pub.get_latest_version())
934+
935+ def test_get_latest_version_example(self):
936+ self.requireFeature(JSONParserFeature)
937+ latest_pub = self.make_latest_publication()
938+ latest_pub._get_lp_info = lambda: _example_response
939+ self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version())
940+
941+ def DONT_test_get_latest_version_from_launchpad(self):
942+ self.requireFeature(JSONParserFeature)
943+ latest_pub = self.make_latest_publication()
944+ self.assertIsNot(None, latest_pub.get_latest_version())
945+
946+ def test_place(self):
947+ self.assertPlace('Ubuntu', 'ubuntu', None, 'bzr')
948+ self.assertPlace('Ubuntu Natty', 'ubuntu', 'natty', 'bzr')
949+ self.assertPlace('Ubuntu Natty Proposed', 'ubuntu', 'natty-proposed',
950+ 'bzr')
951+ self.assertPlace('Debian', 'debian', None, 'bzr')
952+ self.assertPlace('Debian Sid', 'debian', 'sid', 'bzr')
953+
954+
955+class TestIsUpToDate(tests.TestCase):
956+
957+ def assertPackageBranchRe(self, url, user, archive, series, project):
958+ m = launchpad._package_branch.search(url)
959+ if m is None:
960+ self.fail('package_branch regex did not match url: %s' % (url,))
961+ self.assertEqual(
962+ (user, archive, series, project),
963+ m.group('user', 'archive', 'series', 'project'))
964+
965+ def assertNotPackageBranch(self, url):
966+ self.assertIs(None, launchpad._get_package_branch_info(url))
967+
968+ def assertBranchInfo(self, url, archive, series, project):
969+ self.assertEqual((archive, series, project),
970+ launchpad._get_package_branch_info(url))
971+
972+ def test_package_branch_regex(self):
973+ self.assertPackageBranchRe(
974+ 'http://bazaar.launchpad.net/+branch/ubuntu/foo',
975+ None, 'ubuntu', None, 'foo')
976+ self.assertPackageBranchRe(
977+ 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
978+ None, 'ubuntu', 'natty/', 'foo')
979+ self.assertPackageBranchRe(
980+ 'sftp://bazaar.launchpad.net/+branch/debian/foo',
981+ None, 'debian', None, 'foo')
982+ self.assertPackageBranchRe(
983+ 'http://bazaar.launchpad.net/+branch/debian/sid/foo',
984+ None, 'debian', 'sid/', 'foo')
985+ self.assertPackageBranchRe(
986+ 'http://bazaar.launchpad.net/+branch'
987+ '/~ubuntu-branches/ubuntu/natty/foo/natty',
988+ '~ubuntu-branches/', 'ubuntu', 'natty/', 'foo')
989+ self.assertPackageBranchRe(
990+ 'http://bazaar.launchpad.net/+branch'
991+ '/~user/ubuntu/natty/foo/test',
992+ '~user/', 'ubuntu', 'natty/', 'foo')
993+
994+ def test_package_branch_doesnt_match(self):
995+ self.assertNotPackageBranch('http://example.com/ubuntu/foo')
996+ self.assertNotPackageBranch(
997+ 'http://bazaar.launchpad.net/+branch/bzr')
998+ self.assertNotPackageBranch(
999+ 'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev')
1000+ # Not a packaging branch because ~user isn't ~ubuntu-branches
1001+ self.assertNotPackageBranch(
1002+ 'http://bazaar.launchpad.net/+branch'
1003+ '/~user/ubuntu/natty/foo/natty')
1004+ # Older versions of bzr-svn/hg/git did not set Branch.base until after
1005+ # they called Branch.__init__().
1006+ self.assertNotPackageBranch(None)
1007+
1008+ def test__get_package_branch_info(self):
1009+ self.assertBranchInfo(
1010+ 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
1011+ 'ubuntu', 'natty', 'foo')
1012+ self.assertBranchInfo(
1013+ 'bzr+ssh://bazaar.launchpad.net/+branch'
1014+ '/~ubuntu-branches/ubuntu/natty/foo/natty',
1015+ 'ubuntu', 'natty', 'foo')
1016+ self.assertBranchInfo(
1017+ 'http://bazaar.launchpad.net/+branch'
1018+ '/~ubuntu-branches/debian/sid/foo/sid',
1019+ 'debian', 'sid', 'foo')
1020+
1021+
1022+class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport):
1023+
1024+ def make_simple_builder(self):
1025+ builder = self.make_branch_builder('tip')
1026+ builder.build_snapshot('A', None, [
1027+ ('add', ('', 'root-id', 'directory', None))])
1028+ b = builder.get_branch()
1029+ b.tags.set_tag('tip-1.0', 'A')
1030+ return builder, b, b.tags.get_tag_dict()
1031+
1032+ def test_get_most_recent_tag_tip(self):
1033+ builder, b, tag_dict = self.make_simple_builder()
1034+ self.assertEqual('tip-1.0',
1035+ lp_api_lite.get_most_recent_tag(tag_dict, b))
1036+
1037+ def test_get_most_recent_tag_older(self):
1038+ builder, b, tag_dict = self.make_simple_builder()
1039+ builder.build_snapshot('B', ['A'], [])
1040+ self.assertEqual('B', b.last_revision())
1041+ self.assertEqual('tip-1.0',
1042+ lp_api_lite.get_most_recent_tag(tag_dict, b))
1043+
1044+
1045+class StubLatestPublication(object):
1046+
1047+ def __init__(self, latest):
1048+ self.called = False
1049+ self.latest = latest
1050+
1051+ def get_latest_version(self):
1052+ self.called = True
1053+ return self.latest
1054+
1055+ def place(self):
1056+ return 'Ubuntu Natty'
1057+
1058+
1059+class TestReportFreshness(tests.TestCaseWithMemoryTransport):
1060+
1061+ def setUp(self):
1062+ super(TestReportFreshness, self).setUp()
1063+ builder = self.make_branch_builder('tip')
1064+ builder.build_snapshot('A', None, [
1065+ ('add', ('', 'root-id', 'directory', None))])
1066+ self.branch = builder.get_branch()
1067+
1068+ def assertFreshnessReports(self, verbosity, latest_version, content):
1069+ """Assert that lp_api_lite.report_freshness reports the given content.
1070+
1071+ :param verbosity: The reporting level
1072+ :param latest_version: The version reported by StubLatestPublication
1073+ :param content: The expected content. This should be in DocTest form.
1074+ """
1075+ orig_log_len = len(self.get_log())
1076+ lp_api_lite.report_freshness(self.branch, verbosity,
1077+ StubLatestPublication(latest_version))
1078+ new_content = self.get_log()[orig_log_len:]
1079+ # Strip out lines that have LatestPublication.get_* because those are
1080+ # timing related lines. While interesting to log for now, they aren't
1081+ # something we want to be testing
1082+ new_content = new_content.split('\n')
1083+ for i in range(2):
1084+ if (len(new_content) > 0
1085+ and 'LatestPublication.get_' in new_content[0]):
1086+ new_content = new_content[1:]
1087+ new_content = '\n'.join(new_content)
1088+ self.assertThat(new_content,
1089+ DocTestMatches(content,
1090+ doctest.ELLIPSIS | doctest.REPORT_UDIFF))
1091+
1092+ def test_verbosity_off_skips_check(self):
1093+ # We force _get_package_branch_info so that we know it would otherwise
1094+ # try to connect to launcphad
1095+ orig_gpbi = launchpad._get_package_branch_info
1096+ orig_lp = lp_api_lite.LatestPublication
1097+ def cleanup():
1098+ launchpad._get_package_branch_info = orig_gpbi
1099+ lp_api_lite.LatestPublication = orig_lp
1100+ self.addCleanup(cleanup)
1101+ launchpad._get_package_branch_info = lambda x: ('ubuntu', 'natty', 'bzr')
1102+ lp_api_lite.LatestPublication = lambda *args: self.fail('Tried to query launchpad')
1103+ c = self.branch.get_config()
1104+ c.set_user_option('launchpad.packaging_verbosity', 'off')
1105+ orig_log_len = len(self.get_log())
1106+ launchpad._check_is_up_to_date(self.branch)
1107+ new_content = self.get_log()[orig_log_len:]
1108+ self.assertContainsRe(new_content,
1109+ 'not checking memory.*/tip/ because verbosity is turned off')
1110+
1111+ def test_verbosity_off(self):
1112+ latest_pub = StubLatestPublication('1.0-1ubuntu2')
1113+ lp_api_lite.report_freshness(self.branch, 'off', latest_pub)
1114+ self.assertFalse(latest_pub.called)
1115+
1116+ def test_verbosity_all_out_of_date_smoke(self):
1117+ self.branch.tags.set_tag('1.0-1ubuntu1', 'A')
1118+ self.assertFreshnessReports('all', '1.0-1ubuntu2',
1119+ ' INFO Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
1120+ 'Packaging branch version: 1.0-1ubuntu1\n'
1121+ 'Packaging branch status: OUT-OF-DATE\n')
1122+
1123+
1124+class Test_GetNewestVersions(tests.TestCaseWithMemoryTransport):
1125+
1126+ def setUp(self):
1127+ super(Test_GetNewestVersions, self).setUp()
1128+ builder = self.make_branch_builder('tip')
1129+ builder.build_snapshot('A', None, [
1130+ ('add', ('', 'root-id', 'directory', None))])
1131+ self.branch = builder.get_branch()
1132+
1133+ def assertLatestVersions(self, latest_branch_version, pub_version):
1134+ if latest_branch_version is not None:
1135+ self.branch.tags.set_tag(latest_branch_version, 'A')
1136+ latest_pub = StubLatestPublication(pub_version)
1137+ self.assertEqual((pub_version, latest_branch_version),
1138+ lp_api_lite._get_newest_versions(self.branch, latest_pub))
1139+
1140+ def test_no_tags(self):
1141+ self.assertLatestVersions(None, '1.0-1ubuntu2')
1142+
1143+ def test_out_of_date(self):
1144+ self.assertLatestVersions('1.0-1ubuntu1', '1.0-1ubuntu2')
1145+
1146+ def test_up_to_date(self):
1147+ self.assertLatestVersions('1.0-1ubuntu2', '1.0-1ubuntu2')
1148+
1149+ def test_missing(self):
1150+ self.assertLatestVersions(None, None)
1151+
1152+
1153+class Test_ReportFreshness(tests.TestCase):
1154+
1155+ def assertReportedFreshness(self, verbosity, latest_ver, branch_latest_ver,
1156+ content, place='Ubuntu Natty'):
1157+ """Assert that lp_api_lite.report_freshness reports the given content.
1158+ """
1159+ reported = []
1160+ def report_func(value):
1161+ reported.append(value)
1162+ lp_api_lite._report_freshness(latest_ver, branch_latest_ver, place,
1163+ verbosity, report_func)
1164+ new_content = '\n'.join(reported)
1165+ self.assertThat(new_content,
1166+ DocTestMatches(content,
1167+ doctest.ELLIPSIS | doctest.REPORT_UDIFF))
1168+
1169+ def test_verbosity_minimal_no_tags(self):
1170+ self.assertReportedFreshness('minimal', '1.0-1ubuntu2', None,
1171+ 'Branch is OUT-OF-DATE, Ubuntu Natty has 1.0-1ubuntu2\n')
1172+
1173+ def test_verbosity_minimal_out_of_date(self):
1174+ self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu1',
1175+ '1.0-1ubuntu1 is OUT-OF-DATE,'
1176+ ' Ubuntu Natty has 1.0-1ubuntu2\n')
1177+
1178+ def test_verbosity_minimal_up_to_date(self):
1179+ self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu2',
1180+ '')
1181+
1182+ def test_verbosity_minimal_missing(self):
1183+ self.assertReportedFreshness('minimal', None, None,
1184+ '')
1185+
1186+ def test_verbosity_short_out_of_date(self):
1187+ self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu1',
1188+ '1.0-1ubuntu1 is OUT-OF-DATE,'
1189+ ' Ubuntu Natty has 1.0-1ubuntu2\n')
1190+
1191+ def test_verbosity_short_up_to_date(self):
1192+ self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu2',
1193+ '1.0-1ubuntu2 is CURRENT in Ubuntu Natty')
1194+
1195+ def test_verbosity_short_missing(self):
1196+ self.assertReportedFreshness('short', None, None,
1197+ 'Ubuntu Natty is MISSING a version')
1198+
1199+ def test_verbosity_all_no_tags(self):
1200+ self.assertReportedFreshness('all', '1.0-1ubuntu2', None,
1201+ 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
1202+ 'Packaging branch version: None\n'
1203+ 'Packaging branch status: OUT-OF-DATE\n')
1204+
1205+ def test_verbosity_all_out_of_date(self):
1206+ self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu1',
1207+ 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
1208+ 'Packaging branch version: 1.0-1ubuntu1\n'
1209+ 'Packaging branch status: OUT-OF-DATE\n')
1210+
1211+ def test_verbosity_all_up_to_date(self):
1212+ self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu2',
1213+ 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
1214+ 'Packaging branch status: CURRENT\n')
1215+
1216+ def test_verbosity_all_missing(self):
1217+ self.assertReportedFreshness('all', None, None,
1218+ 'Most recent Ubuntu Natty version: MISSING\n')
1219+
1220+ def test_verbosity_None_is_all(self):
1221+ self.assertReportedFreshness(None, '1.0-1ubuntu2', '1.0-1ubuntu2',
1222+ 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
1223+ 'Packaging branch status: CURRENT\n')
1224
1225=== modified file 'bzrlib/repofmt/groupcompress_repo.py'
1226--- bzrlib/repofmt/groupcompress_repo.py 2010-08-07 00:54:52 +0000
1227+++ bzrlib/repofmt/groupcompress_repo.py 2011-09-27 11:59:22 +0000
1228@@ -1,4 +1,4 @@
1229-# Copyright (C) 2008, 2009, 2010 Canonical Ltd
1230+# Copyright (C) 2008-2011 Canonical Ltd
1231 #
1232 # This program is free software; you can redistribute it and/or modify
1233 # it under the terms of the GNU General Public License as published by
1234@@ -419,9 +419,18 @@
1235 inventory_keys = source_vf.keys()
1236 missing_inventories = set(self.revision_keys).difference(inventory_keys)
1237 if missing_inventories:
1238- missing_inventories = sorted(missing_inventories)
1239- raise ValueError('We are missing inventories for revisions: %s'
1240- % (missing_inventories,))
1241+ # Go back to the original repo, to see if these are really missing
1242+ # https://bugs.launchpad.net/bzr/+bug/437003
1243+ # If we are packing a subset of the repo, it is fine to just have
1244+ # the data in another Pack file, which is not included in this pack
1245+ # operation.
1246+ inv_index = self._pack_collection.repo.inventories._index
1247+ pmap = inv_index.get_parent_map(missing_inventories)
1248+ really_missing = missing_inventories.difference(pmap)
1249+ if really_missing:
1250+ missing_inventories = sorted(really_missing)
1251+ raise ValueError('We are missing inventories for revisions: %s'
1252+ % (missing_inventories,))
1253 self._copy_stream(source_vf, target_vf, inventory_keys,
1254 'inventories', self._get_filtered_inv_stream, 2)
1255
1256
1257=== modified file 'bzrlib/tests/per_repository_reference/__init__.py'
1258--- bzrlib/tests/per_repository_reference/__init__.py 2010-08-07 00:54:52 +0000
1259+++ bzrlib/tests/per_repository_reference/__init__.py 2011-09-27 11:59:22 +0000
1260@@ -117,6 +117,7 @@
1261 'bzrlib.tests.per_repository_reference.test_fetch',
1262 'bzrlib.tests.per_repository_reference.test_get_record_stream',
1263 'bzrlib.tests.per_repository_reference.test_get_rev_id_for_revno',
1264+ 'bzrlib.tests.per_repository_reference.test_graph',
1265 'bzrlib.tests.per_repository_reference.test_initialize',
1266 'bzrlib.tests.per_repository_reference.test_unlock',
1267 ]
1268
1269=== added file 'bzrlib/tests/per_repository_reference/test_graph.py'
1270--- bzrlib/tests/per_repository_reference/test_graph.py 1970-01-01 00:00:00 +0000
1271+++ bzrlib/tests/per_repository_reference/test_graph.py 2011-09-27 11:59:22 +0000
1272@@ -0,0 +1,45 @@
1273+# Copyright (C) 2011 Canonical Ltd
1274+#
1275+# This program is free software; you can redistribute it and/or modify
1276+# it under the terms of the GNU General Public License as published by
1277+# the Free Software Foundation; either version 2 of the License, or
1278+# (at your option) any later version.
1279+#
1280+# This program is distributed in the hope that it will be useful,
1281+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1282+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1283+# GNU General Public License for more details.
1284+#
1285+# You should have received a copy of the GNU General Public License
1286+# along with this program; if not, write to the Free Software
1287+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1288+
1289+
1290+"""Tests for graph operations on stacked repositories."""
1291+
1292+
1293+from bzrlib.tests.per_repository import TestCaseWithRepository
1294+
1295+
1296+class TestGraph(TestCaseWithRepository):
1297+
1298+ def test_get_known_graph_ancestry_stacked(self):
1299+ """get_known_graph_ancestry works correctly on stacking.
1300+
1301+ See <https://bugs.launchpad.net/bugs/715000>.
1302+ """
1303+ branch_a, branch_b, branch_c, revid_1 = self.make_double_stacked_branches()
1304+ for br in [branch_c]:
1305+ self.assertEquals(
1306+ [revid_1],
1307+ br.repository.get_known_graph_ancestry([revid_1]).topo_sort())
1308+
1309+ def make_double_stacked_branches(self):
1310+ wt_a = self.make_branch_and_tree('a')
1311+ branch_a = wt_a.branch
1312+ branch_b = self.make_branch('b')
1313+ branch_b.set_stacked_on_url('../a')
1314+ branch_c = self.make_branch('c')
1315+ branch_c.set_stacked_on_url('../b')
1316+ revid_1 = wt_a.commit('first commit')
1317+ return branch_a, branch_b, branch_c, revid_1
1318
1319=== modified file 'bzrlib/tests/test_config.py'
1320--- bzrlib/tests/test_config.py 2010-08-07 00:54:52 +0000
1321+++ bzrlib/tests/test_config.py 2011-09-27 11:59:22 +0000
1322@@ -241,11 +241,37 @@
1323 """
1324 co = config.ConfigObj()
1325 co['test'] = 'foo#bar'
1326- lines = co.write()
1327+ outfile = StringIO()
1328+ co.write(outfile=outfile)
1329+ lines = outfile.getvalue().splitlines()
1330 self.assertEqual(lines, ['test = "foo#bar"'])
1331 co2 = config.ConfigObj(lines)
1332 self.assertEqual(co2['test'], 'foo#bar')
1333
1334+ def test_triple_quotes(self):
1335+ # Bug #710410: if the value string has triple quotes
1336+ # then ConfigObj versions up to 4.7.2 will quote them wrong
1337+ # and won't able to read them back
1338+ triple_quotes_value = '''spam
1339+""" that's my spam """
1340+eggs'''
1341+ co = config.ConfigObj()
1342+ co['test'] = triple_quotes_value
1343+ # While writing this test another bug in ConfigObj has been found:
1344+ # method co.write() without arguments produces list of lines
1345+ # one option per line, and multiline values are not split
1346+ # across multiple lines,
1347+ # and that breaks the parsing these lines back by ConfigObj.
1348+ # This issue only affects test, but it's better to avoid
1349+ # `co.write()` construct at all.
1350+ # [bialix 20110222] bug report sent to ConfigObj's author
1351+ outfile = StringIO()
1352+ co.write(outfile=outfile)
1353+ output = outfile.getvalue()
1354+ # now we're trying to read it back
1355+ co2 = config.ConfigObj(StringIO(output))
1356+ self.assertEquals(triple_quotes_value, co2['test'])
1357+
1358
1359 erroneous_config = """[section] # line 1
1360 good=good # line 2
1361
1362=== modified file 'bzrlib/tests/test_conflicts.py'
1363--- bzrlib/tests/test_conflicts.py 2011-04-07 15:30:17 +0000
1364+++ bzrlib/tests/test_conflicts.py 2011-09-27 11:59:22 +0000
1365@@ -1049,6 +1049,70 @@
1366 """)
1367
1368
1369+class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1370+
1371+ def test_bug_805809(self):
1372+ self.run_script("""
1373+$ bzr init trunk
1374+Created a standalone tree (format: 2a)
1375+$ cd trunk
1376+$ echo trunk >file
1377+$ bzr add
1378+adding file
1379+$ bzr commit -m 'create file on trunk'
1380+2>Committing to: .../trunk/
1381+2>added file
1382+2>Committed revision 1.
1383+# Create a debian branch based on trunk
1384+$ cd ..
1385+$ bzr branch trunk -r 1 debian
1386+2>Branched 1 revision(s).
1387+$ cd debian
1388+$ mkdir dir
1389+$ bzr add
1390+adding dir
1391+$ bzr mv file dir
1392+file => dir/file
1393+$ bzr commit -m 'rename file to dir/file for debian'
1394+2>Committing to: .../debian/
1395+2>added dir
1396+2>renamed file => dir/file
1397+2>Committed revision 2.
1398+# Create an experimental branch with a new root-id
1399+$ cd ..
1400+$ bzr init experimental
1401+$ cd experimental
1402+# merge debian even without a common ancestor
1403+$ bzr merge ../debian -r0..2
1404+2>+N dir/
1405+2>+N dir/file
1406+2>All changes applied successfully.
1407+$ bzr commit -m 'merging debian into experimental'
1408+2>Committing to: .../experimental/
1409+2>deleted
1410+2>modified dir
1411+2>Committed revision 1.
1412+# Create an ubuntu branch with yet another root-id
1413+$ cd ..
1414+$ bzr init ubuntu
1415+$ cd ubuntu
1416+# Also merge debian
1417+$ bzr merge ../debian -r0..2
1418+2>+N dir/
1419+2>+N dir/file
1420+2>All changes applied successfully.
1421+$ bzr commit -m 'merging debian'
1422+2>Committing to: .../ubuntu/
1423+2>deleted
1424+2>modified dir
1425+2>Committed revision 1.
1426+# Now try to merge experimental
1427+$ bzr merge ../experimental
1428+2>Path conflict: dir / dir
1429+2>1 conflicts encountered.
1430+""")
1431+
1432+
1433 class TestResolveActionOption(tests.TestCase):
1434
1435 def setUp(self):
1436
1437=== modified file 'bzrlib/tests/test_repository.py'
1438--- bzrlib/tests/test_repository.py 2010-08-07 00:54:52 +0000
1439+++ bzrlib/tests/test_repository.py 2011-09-27 11:59:22 +0000
1440@@ -1,4 +1,4 @@
1441-# Copyright (C) 2006-2010 Canonical Ltd
1442+# Copyright (C) 2006-2011 Canonical Ltd
1443 #
1444 # This program is free software; you can redistribute it and/or modify
1445 # it under the terms of the GNU General Public License as published by
1446@@ -1622,6 +1622,106 @@
1447 self.assertTrue(new_pack.signature_index._optimize_for_size)
1448
1449
1450+class TestGCCHKPacker(TestCaseWithTransport):
1451+
1452+ def make_abc_branch(self):
1453+ builder = self.make_branch_builder('source')
1454+ builder.start_series()
1455+ builder.build_snapshot('A', None, [
1456+ ('add', ('', 'root-id', 'directory', None)),
1457+ ('add', ('file', 'file-id', 'file', 'content\n')),
1458+ ])
1459+ builder.build_snapshot('B', ['A'], [
1460+ ('add', ('dir', 'dir-id', 'directory', None))])
1461+ builder.build_snapshot('C', ['B'], [
1462+ ('modify', ('file-id', 'new content\n'))])
1463+ builder.finish_series()
1464+ return builder.get_branch()
1465+
1466+ def make_branch_with_disjoint_inventory_and_revision(self):
1467+ """a repo with separate packs for a revisions Revision and Inventory.
1468+
1469+ There will be one pack file that holds the Revision content, and one
1470+ for the Inventory content.
1471+
1472+ :return: (repository,
1473+ pack_name_with_rev_A_Revision,
1474+ pack_name_with_rev_A_Inventory,
1475+ pack_name_with_rev_C_content)
1476+ """
1477+ b_source = self.make_abc_branch()
1478+ b_base = b_source.bzrdir.sprout('base', revision_id='A').open_branch()
1479+ b_stacked = b_base.bzrdir.sprout('stacked', stacked=True).open_branch()
1480+ b_stacked.lock_write()
1481+ self.addCleanup(b_stacked.unlock)
1482+ b_stacked.fetch(b_source, 'B')
1483+ # Now re-open the stacked repo directly (no fallbacks) so that we can
1484+ # fill in the A rev.
1485+ repo_not_stacked = b_stacked.bzrdir.open_repository()
1486+ repo_not_stacked.lock_write()
1487+ self.addCleanup(repo_not_stacked.unlock)
1488+ # Now we should have a pack file with A's inventory, but not its
1489+ # Revision
1490+ self.assertEqual([('A',), ('B',)],
1491+ sorted(repo_not_stacked.inventories.keys()))
1492+ self.assertEqual([('B',)],
1493+ sorted(repo_not_stacked.revisions.keys()))
1494+ stacked_pack_names = repo_not_stacked._pack_collection.names()
1495+ # We have a couple names here, figure out which has A's inventory
1496+ for name in stacked_pack_names:
1497+ pack = repo_not_stacked._pack_collection.get_pack_by_name(name)
1498+ keys = [n[1] for n in pack.inventory_index.iter_all_entries()]
1499+ if ('A',) in keys:
1500+ inv_a_pack_name = name
1501+ break
1502+ else:
1503+ self.fail('Could not find pack containing A\'s inventory')
1504+ repo_not_stacked.fetch(b_source.repository, 'A')
1505+ self.assertEqual([('A',), ('B',)],
1506+ sorted(repo_not_stacked.revisions.keys()))
1507+ new_pack_names = set(repo_not_stacked._pack_collection.names())
1508+ rev_a_pack_names = new_pack_names.difference(stacked_pack_names)
1509+ self.assertEqual(1, len(rev_a_pack_names))
1510+ rev_a_pack_name = list(rev_a_pack_names)[0]
1511+ # Now fetch 'C', so we have a couple pack files to join
1512+ repo_not_stacked.fetch(b_source.repository, 'C')
1513+ rev_c_pack_names = set(repo_not_stacked._pack_collection.names())
1514+ rev_c_pack_names = rev_c_pack_names.difference(new_pack_names)
1515+ self.assertEqual(1, len(rev_c_pack_names))
1516+ rev_c_pack_name = list(rev_c_pack_names)[0]
1517+ return (repo_not_stacked, rev_a_pack_name, inv_a_pack_name,
1518+ rev_c_pack_name)
1519+
1520+ def test_pack_with_distant_inventories(self):
1521+ # See https://bugs.launchpad.net/bzr/+bug/437003
1522+ # When repacking, it is possible to have an inventory in a different
1523+ # pack file than the associated revision. An autopack can then come
1524+ # along, and miss that inventory, and complain.
1525+ (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
1526+ ) = self.make_branch_with_disjoint_inventory_and_revision()
1527+ a_pack = repo._pack_collection.get_pack_by_name(rev_a_pack_name)
1528+ c_pack = repo._pack_collection.get_pack_by_name(rev_c_pack_name)
1529+ packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
1530+ [a_pack, c_pack], '.test-pack')
1531+ # This would raise ValueError in bug #437003, but should not raise an
1532+ # error once fixed.
1533+ packer.pack()
1534+
1535+ def test_pack_with_missing_inventory(self):
1536+ # Similar to test_pack_with_missing_inventory, but this time, we force
1537+ # the A inventory to actually be gone from the repository.
1538+ (repo, rev_a_pack_name, inv_a_pack_name, rev_c_pack_name
1539+ ) = self.make_branch_with_disjoint_inventory_and_revision()
1540+ inv_a_pack = repo._pack_collection.get_pack_by_name(inv_a_pack_name)
1541+ repo._pack_collection._remove_pack_from_memory(inv_a_pack)
1542+ packer = groupcompress_repo.GCCHKPacker(repo._pack_collection,
1543+ repo._pack_collection.all_packs(), '.test-pack')
1544+ e = self.assertRaises(ValueError, packer.pack)
1545+ packer.new_pack.abort()
1546+ self.assertContainsRe(str(e),
1547+ r"We are missing inventories for revisions: .*'A'")
1548+
1549+
1550 class TestCrossFormatPacks(TestCaseWithTransport):
1551
1552 def log_pack(self, hint=None):
1553
1554=== modified file 'bzrlib/transport/http/_pycurl.py'
1555--- bzrlib/transport/http/_pycurl.py 2011-04-07 15:30:17 +0000
1556+++ bzrlib/transport/http/_pycurl.py 2011-09-27 11:59:22 +0000
1557@@ -305,19 +305,19 @@
1558 url, 'Unable to handle http code %d%s' % (code,msg))
1559
1560 def _debug_cb(self, kind, text):
1561- if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN,
1562- pycurl.INFOTYPE_SSL_DATA_IN):
1563+ if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN):
1564 self._report_activity(len(text), 'read')
1565 if (kind == pycurl.INFOTYPE_HEADER_IN
1566 and 'http' in debug.debug_flags):
1567 mutter('< %s' % text)
1568- elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT,
1569- pycurl.INFOTYPE_SSL_DATA_OUT):
1570+ elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT):
1571 self._report_activity(len(text), 'write')
1572 if (kind == pycurl.INFOTYPE_HEADER_OUT
1573 and 'http' in debug.debug_flags):
1574 mutter('> %s' % text)
1575- elif kind == pycurl.INFOTYPE_TEXT and 'http' in debug.debug_flags:
1576+ elif (kind in (pycurl.INFOTYPE_TEXT, pycurl.INFOTYPE_SSL_DATA_IN,
1577+ pycurl.INFOTYPE_SSL_DATA_OUT)
1578+ and 'http' in debug.debug_flags):
1579 mutter('* %s' % text)
1580
1581 def _set_curl_options(self, curl):
1582
1583=== modified file 'bzrlib/util/configobj/configobj.py'
1584--- bzrlib/util/configobj/configobj.py 2009-05-16 13:51:08 +0000
1585+++ bzrlib/util/configobj/configobj.py 2011-09-27 11:59:22 +0000
1586@@ -1794,10 +1794,12 @@
1587 def _get_triple_quote(self, value):
1588 if (value.find('"""') != -1) and (value.find("'''") != -1):
1589 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1590+ # upstream version (up to version 4.7.2) has the bug with incorrect quoting;
1591+ # fixed in our copy based on the suggestion of ConfigObj's author
1592 if value.find('"""') == -1:
1593+ quot = tsquot
1594+ else:
1595 quot = tdquot
1596- else:
1597- quot = tsquot
1598 return quot
1599
1600
1601
1602=== modified file 'bzrlib/versionedfile.py'
1603--- bzrlib/versionedfile.py 2010-08-07 00:54:52 +0000
1604+++ bzrlib/versionedfile.py 2011-09-27 11:59:22 +0000
1605@@ -1090,6 +1090,19 @@
1606 def _extract_blocks(self, version_id, source, target):
1607 return None
1608
1609+ def _transitive_fallbacks(self):
1610+ """Return the whole stack of fallback versionedfiles.
1611+
1612+ This VersionedFiles may have a list of fallbacks, but it doesn't
1613+ necessarily know about the whole stack going down, and it can't know
1614+ at open time because they may change after the objects are opened.
1615+ """
1616+ all_fallbacks = []
1617+ for a_vfs in self._fallback_vfs:
1618+ all_fallbacks.append(a_vfs)
1619+ all_fallbacks.extend(a_vfs._transitive_fallbacks())
1620+ return all_fallbacks
1621+
1622
1623 class ThunkedVersionedFiles(VersionedFiles):
1624 """Storage for many versioned files thunked onto a 'VersionedFile' class.
1625
1626=== modified file 'debian/changelog'
1627--- debian/changelog 2011-04-07 15:30:17 +0000
1628+++ debian/changelog 2011-09-27 11:59:22 +0000
1629@@ -1,3 +1,13 @@
1630+bzr (2.2.5-0ubuntu1) maverick-proposed; urgency=low
1631+
1632+ * New upstream release.
1633+ + Fixes merge failing with NoFinalPath. LP: #805809
1634+ + Warns users when brancing from UDD branches that are out of date.
1635+ LP: #609187
1636+ + stacking is now fully transitive. LP: #715000
1637+
1638+ -- Jelmer Vernooij <jelmer@ubuntu.com> Tue, 27 Sep 2011 10:30:17 +0200
1639+
1640 bzr (2.2.4-0ubuntu1) maverick-proposed; urgency=low
1641
1642 [ Jelmer Vernooij ]
1643
1644=== modified file 'debian/watch'
1645--- debian/watch 2011-04-07 15:30:17 +0000
1646+++ debian/watch 2011-09-27 11:59:22 +0000
1647@@ -1,3 +1,3 @@
1648 version=3
1649 opts="uversionmangle=s/rc/~rc/;s/b/~b/;s/^/2.2./" \
1650-https://launchpad.net/bzr/+download http://launchpad.net/bzr/.*/bzr-2.2.(.+).tar.gz
1651+https://launchpad.net/bzr/2.2 http://launchpad.net/bzr/.*/bzr-2.2.(.+).tar.gz
1652
1653=== modified file 'doc/en/whats-new/whats-new-in-2.2.txt'
1654--- doc/en/whats-new/whats-new-in-2.2.txt 2011-04-07 15:30:17 +0000
1655+++ doc/en/whats-new/whats-new-in-2.2.txt 2011-09-27 11:59:22 +0000
1656@@ -36,7 +36,10 @@
1657 server and python-2.7 compatibility.
1658
1659 Bazaar 2.2.4 fixed a regression for some interactions with the launchpad
1660-server .
1661+server.
1662+
1663+Bazaar 2.2.5 fixed a regression in some rare conflict resolutions and warns
1664+when branching an out-of-date ubuntu packaging branch.
1665
1666 See the :doc:`../release-notes/index` for details.
1667
1668
1669=== modified file 'setup.py'
1670--- setup.py 2011-04-07 15:30:17 +0000
1671+++ setup.py 2011-09-27 11:59:22 +0000
1672@@ -468,6 +468,12 @@
1673 packages.append('sqlite3')
1674
1675
1676+def get_fastimport_py2exe_info(includes, excludes, packages):
1677+ # This is the python-fastimport package, not to be confused with the
1678+ # bzr-fastimport plugin.
1679+ packages.append('fastimport')
1680+
1681+
1682 if 'bdist_wininst' in sys.argv:
1683 def find_docs():
1684 docs = []
1685@@ -660,6 +666,9 @@
1686 if 'svn' in plugins:
1687 get_svn_py2exe_info(includes, excludes, packages)
1688
1689+ if 'fastimport' in plugins:
1690+ get_fastimport_py2exe_info(includes, excludes, packages)
1691+
1692 if "TBZR" in os.environ:
1693 # TORTOISE_OVERLAYS_MSI_WIN32 must be set to the location of the
1694 # TortoiseOverlays MSI installer file. It is in the TSVN svn repo and

Subscribers

People subscribed via source and target branches