Merge lp:~mbp/bzr/704195-plugin-warnings into lp:bzr/2.3

Proposed by Martin Pool
Status: Superseded
Proposed branch: lp:~mbp/bzr/704195-plugin-warnings
Merge into: lp:bzr/2.3
Diff against target: 1763 lines (+877/-133)
30 files modified
bzr (+1/-1)
bzrlib/__init__.py (+2/-2)
bzrlib/branch.py (+1/-1)
bzrlib/builtins.py (+3/-20)
bzrlib/crash.py (+2/-6)
bzrlib/fetch.py (+27/-9)
bzrlib/graph.py (+182/-2)
bzrlib/plugin.py (+51/-3)
bzrlib/plugins/launchpad/lp_propose.py (+10/-8)
bzrlib/remote.py (+30/-9)
bzrlib/repofmt/knitrepo.py (+19/-11)
bzrlib/repofmt/weaverepo.py (+20/-11)
bzrlib/repository.py (+94/-18)
bzrlib/smart/repository.py (+16/-0)
bzrlib/smart/server.py (+1/-3)
bzrlib/tests/blackbox/test_serve.py (+7/-10)
bzrlib/tests/per_branch/test_push.py (+15/-0)
bzrlib/tests/per_interrepository/test_interrepository.py (+9/-2)
bzrlib/tests/per_repository_reference/test_fetch.py (+40/-1)
bzrlib/tests/test_crash.py (+9/-0)
bzrlib/tests/test_plugins.py (+18/-4)
bzrlib/tests/test_remote.py (+59/-4)
bzrlib/tests/test_repository.py (+1/-1)
bzrlib/tests/test_server.py (+0/-6)
bzrlib/tests/test_smart.py (+66/-1)
doc/developers/fetch.txt (+86/-0)
doc/developers/index.txt (+1/-0)
doc/en/release-notes/bzr-2.3.txt (+10/-0)
doc/en/release-notes/bzr-2.4.txt (+64/-0)
doc/en/whats-new/whats-new-in-2.4.txt (+33/-0)
To merge this branch: bzr merge lp:~mbp/bzr/704195-plugin-warnings
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+46726@code.launchpad.net

This proposal has been superseded by a proposal from 2011-01-19.

Description of the change

This stops bzr spamming you about out-of-date plugins. Telling people on every single invocation is not helpful. Now we just tell you on in `bzr plugins`.

As a follow-on in bug 704238 we can try to load them anyhow.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzr'
2--- bzr 2010-11-26 17:59:08 +0000
3+++ bzr 2011-01-19 00:31:58 +0000
4@@ -23,7 +23,7 @@
5 import warnings
6
7 # update this on each release
8-_script_version = (2, 3, 0)
9+_script_version = (2, 4, 0)
10
11 try:
12 version_info = sys.version_info
13
14=== modified file 'bzrlib/__init__.py'
15--- bzrlib/__init__.py 2011-01-14 23:20:15 +0000
16+++ bzrlib/__init__.py 2011-01-19 00:31:58 +0000
17@@ -52,10 +52,10 @@
18 # Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
19 # releaselevel of 'dev' for unreleased under-development code.
20
21-version_info = (2, 3, 0, 'dev', 6)
22+version_info = (2, 4, 0, 'dev', 1)
23
24 # API compatibility version
25-api_minimum_version = (2, 3, 0)
26+api_minimum_version = (2, 4, 0)
27
28
29 def _format_version_tuple(version_info):
30
31=== modified file 'bzrlib/branch.py'
32--- bzrlib/branch.py 2011-01-12 01:01:53 +0000
33+++ bzrlib/branch.py 2011-01-19 00:31:58 +0000
34@@ -2664,7 +2664,7 @@
35 result.target_branch = target
36 result.old_revno, result.old_revid = target.last_revision_info()
37 self.update_references(target)
38- if result.old_revid != self.last_revision():
39+ if result.old_revid != stop_revision:
40 # We assume that during 'push' this repository is closer than
41 # the target.
42 graph = self.repository.get_graph(target.repository)
43
44=== modified file 'bzrlib/builtins.py'
45--- bzrlib/builtins.py 2011-01-12 01:01:53 +0000
46+++ bzrlib/builtins.py 2011-01-19 00:31:58 +0000
47@@ -4581,26 +4581,9 @@
48
49 @display_command
50 def run(self, verbose=False):
51- import bzrlib.plugin
52- from inspect import getdoc
53- result = []
54- for name, plugin in bzrlib.plugin.plugins().items():
55- version = plugin.__version__
56- if version == 'unknown':
57- version = ''
58- name_ver = '%s %s' % (name, version)
59- d = getdoc(plugin.module)
60- if d:
61- doc = d.split('\n')[0]
62- else:
63- doc = '(no description)'
64- result.append((name_ver, doc, plugin.path()))
65- for name_ver, doc, path in sorted(result):
66- self.outf.write("%s\n" % name_ver)
67- self.outf.write(" %s\n" % doc)
68- if verbose:
69- self.outf.write(" %s\n" % path)
70- self.outf.write("\n")
71+ from bzrlib import plugin
72+ self.outf.writelines(
73+ plugin.describe_loaded_plugins(show_paths=verbose))
74
75
76 class cmd_testament(Command):
77
78=== modified file 'bzrlib/crash.py'
79--- bzrlib/crash.py 2010-09-28 18:51:47 +0000
80+++ bzrlib/crash.py 2011-01-19 00:31:58 +0000
81@@ -1,4 +1,4 @@
82-# Copyright (C) 2009, 2010 Canonical Ltd
83+# Copyright (C) 2009, 2010, 2011 Canonical Ltd
84 #
85 # This program is free software; you can redistribute it and/or modify
86 # it under the terms of the GNU General Public License as published by
87@@ -254,11 +254,7 @@
88
89
90 def _format_plugin_list():
91- plugin_lines = []
92- for name, a_plugin in sorted(plugin.plugins().items()):
93- plugin_lines.append(" %-20s %s [%s]" %
94- (name, a_plugin.path(), a_plugin.__version__))
95- return '\n'.join(plugin_lines)
96+ return ''.join(plugin.describe_loaded_plugins(show_paths=True))
97
98
99 def _format_module_list():
100
101=== modified file 'bzrlib/fetch.py'
102--- bzrlib/fetch.py 2011-01-10 22:20:12 +0000
103+++ bzrlib/fetch.py 2011-01-19 00:31:58 +0000
104@@ -28,6 +28,7 @@
105 from bzrlib.lazy_import import lazy_import
106 lazy_import(globals(), """
107 from bzrlib import (
108+ graph,
109 tsort,
110 versionedfile,
111 )
112@@ -93,7 +94,8 @@
113 try:
114 pb.update("Finding revisions", 0, 2)
115 search = self._revids_to_fetch()
116- if search is None:
117+ mutter('fetching: %s', search)
118+ if search.is_empty():
119 return
120 pb.update("Fetching revisions", 1, 2)
121 self._fetch_everything_for_search(search)
122@@ -148,17 +150,33 @@
123 """Determines the exact revisions needed from self.from_repository to
124 install self._last_revision in self.to_repository.
125
126- If no revisions need to be fetched, then this just returns None.
127+ :returns: A SearchResult of some sort. (Possibly a
128+ PendingAncestryResult, EmptySearchResult, etc.)
129 """
130- if self._fetch_spec is not None:
131+ mutter("self._fetch_spec, self._last_revision: %r, %r",
132+ self._fetch_spec, self._last_revision)
133+ get_search_result = getattr(self._fetch_spec, 'get_search_result', None)
134+ if get_search_result is not None:
135+ mutter(
136+ 'resolving fetch_spec into search result: %s', self._fetch_spec)
137+ # This is EverythingNotInOther or a similar kind of fetch_spec.
138+ # Turn it into a search result.
139+ return get_search_result()
140+ elif self._fetch_spec is not None:
141+ # The fetch spec is already a concrete search result.
142 return self._fetch_spec
143- mutter('fetch up to rev {%s}', self._last_revision)
144- if self._last_revision is NULL_REVISION:
145+ elif self._last_revision == NULL_REVISION:
146+ # fetch_spec is None + last_revision is null => empty fetch.
147 # explicit limit of no revisions needed
148- return None
149- return self.to_repository.search_missing_revision_ids(
150- self.from_repository, self._last_revision,
151- find_ghosts=self.find_ghosts)
152+ return graph.EmptySearchResult()
153+ elif self._last_revision is not None:
154+ return graph.NotInOtherForRevs(self.to_repository,
155+ self.from_repository, [self._last_revision],
156+ find_ghosts=self.find_ghosts).get_search_result()
157+ else: # self._last_revision is None:
158+ return graph.EverythingNotInOther(self.to_repository,
159+ self.from_repository,
160+ find_ghosts=self.find_ghosts).get_search_result()
161
162
163 class Inter1and2Helper(object):
164
165=== modified file 'bzrlib/graph.py'
166--- bzrlib/graph.py 2011-01-10 22:20:12 +0000
167+++ bzrlib/graph.py 2011-01-19 00:31:58 +0000
168@@ -1536,7 +1536,57 @@
169 return revs, ghosts
170
171
172-class SearchResult(object):
173+class AbstractSearchResult(object):
174+
175+ def get_recipe(self):
176+ """Return a recipe that can be used to replay this search.
177+
178+ The recipe allows reconstruction of the same results at a later date.
179+
180+ :return: A tuple of (search_kind_str, *details). The details vary by
181+ kind of search result.
182+ """
183+ raise NotImplementedError(self.get_recipe)
184+
185+ def get_network_struct(self):
186+ """Return a tuple that can be transmitted via the HPSS protocol."""
187+ raise NotImplementedError(self.get_network_struct)
188+
189+ def get_keys(self):
190+ """Return the keys found in this search.
191+
192+ :return: A set of keys.
193+ """
194+ raise NotImplementedError(self.get_keys)
195+
196+ def is_empty(self):
197+ """Return false if the search lists 1 or more revisions."""
198+ raise NotImplementedError(self.is_empty)
199+
200+ def refine(self, seen, referenced):
201+ """Create a new search by refining this search.
202+
203+ :param seen: Revisions that have been satisfied.
204+ :param referenced: Revision references observed while satisfying some
205+ of this search.
206+ :return: A search result.
207+ """
208+ raise NotImplementedError(self.refine)
209+
210+
211+class AbstractSearch(object):
212+
213+ def get_search_result(self):
214+ """Construct a network-ready search result from this search description.
215+
216+ This may take some time to search repositories, etc.
217+
218+ :return: A search result.
219+ """
220+ raise NotImplementedError(self.get_search_result)
221+
222+
223+class SearchResult(AbstractSearchResult):
224 """The result of a breadth first search.
225
226 A SearchResult provides the ability to reconstruct the search or access a
227@@ -1557,6 +1607,19 @@
228 self._recipe = ('search', start_keys, exclude_keys, key_count)
229 self._keys = frozenset(keys)
230
231+ def __repr__(self):
232+ kind, start_keys, exclude_keys, key_count = self._recipe
233+ if len(start_keys) > 5:
234+ start_keys_repr = repr(list(start_keys)[:5])[:-1] + ', ...]'
235+ else:
236+ start_keys_repr = repr(start_keys)
237+ if len(exclude_keys) > 5:
238+ exclude_keys_repr = repr(list(exclude_keys)[:5])[:-1] + ', ...]'
239+ else:
240+ exclude_keys_repr = repr(exclude_keys)
241+ return '<%s %s:(%s, %s, %d)>' % (self.__class__.__name__,
242+ kind, start_keys_repr, exclude_keys_repr, key_count)
243+
244 def get_recipe(self):
245 """Return a recipe that can be used to replay this search.
246
247@@ -1580,6 +1643,12 @@
248 """
249 return self._recipe
250
251+ def get_network_struct(self):
252+ start_keys = ' '.join(self._recipe[1])
253+ stop_keys = ' '.join(self._recipe[2])
254+ count = str(self._recipe[3])
255+ return (self._recipe[0], '\n'.join((start_keys, stop_keys, count)))
256+
257 def get_keys(self):
258 """Return the keys found in this search.
259
260@@ -1617,7 +1686,7 @@
261 return SearchResult(pending_refs, exclude, count, keys)
262
263
264-class PendingAncestryResult(object):
265+class PendingAncestryResult(AbstractSearchResult):
266 """A search result that will reconstruct the ancestry for some graph heads.
267
268 Unlike SearchResult, this doesn't hold the complete search result in
269@@ -1634,6 +1703,14 @@
270 self.heads = frozenset(heads)
271 self.repo = repo
272
273+ def __repr__(self):
274+ if len(self.heads) > 5:
275+ heads_repr = repr(list(self.heads)[:5] + ', ...]')
276+ else:
277+ heads_repr = repr(self.heads)
278+ return '<%s heads:%s repo:%r>' % (
279+ self.__class__.__name__, heads_repr, self.repo)
280+
281 def get_recipe(self):
282 """Return a recipe that can be used to replay this search.
283
284@@ -1647,6 +1724,11 @@
285 """
286 return ('proxy-search', self.heads, set(), -1)
287
288+ def get_network_struct(self):
289+ parts = ['ancestry-of']
290+ parts.extend(self.heads)
291+ return parts
292+
293 def get_keys(self):
294 """See SearchResult.get_keys.
295
296@@ -1679,6 +1761,104 @@
297 return PendingAncestryResult(referenced - seen, self.repo)
298
299
300+class EmptySearchResult(AbstractSearchResult):
301+ """An empty search result."""
302+
303+ def is_empty(self):
304+ return True
305+
306+
307+class EverythingResult(AbstractSearchResult):
308+ """A search result that simply requests everything in the repository."""
309+
310+ def __init__(self, repo):
311+ self._repo = repo
312+
313+ def __repr__(self):
314+ return '%s(%r)' % (self.__class__.__name__, self._repo)
315+
316+ def get_recipe(self):
317+ raise NotImplementedError(self.get_recipe)
318+
319+ def get_network_struct(self):
320+ return ('everything',)
321+
322+ def get_keys(self):
323+ if 'evil' in debug.debug_flags:
324+ from bzrlib import remote
325+ if isinstance(self._repo, remote.RemoteRepository):
326+ # warn developers (not users) not to do this
327+ trace.mutter_callsite(
328+ 2, "EverythingResult(RemoteRepository).get_keys() is slow.")
329+ return self._repo.all_revision_ids()
330+
331+ def is_empty(self):
332+ # It's ok for this to wrongly return False: the worst that can happen
333+ # is that RemoteStreamSource will initiate a get_stream on an empty
334+ # repository. And almost all repositories are non-empty.
335+ return False
336+
337+ def refine(self, seen, referenced):
338+ heads = set(self._repo.all_revision_ids())
339+ heads.difference_update(seen)
340+ heads.update(referenced)
341+ return PendingAncestryResult(heads, self._repo)
342+
343+
344+class EverythingNotInOther(AbstractSearch):
345+ """Find all revisions in that are in one repo but not the other."""
346+
347+ def __init__(self, to_repo, from_repo, find_ghosts=False):
348+ self.to_repo = to_repo
349+ self.from_repo = from_repo
350+ self.find_ghosts = find_ghosts
351+
352+ def get_search_result(self):
353+ return self.to_repo.search_missing_revision_ids(
354+ self.from_repo, find_ghosts=self.find_ghosts)
355+
356+
357+class NotInOtherForRevs(AbstractSearch):
358+ """Find all revisions missing in one repo for a some specific heads."""
359+
360+ def __init__(self, to_repo, from_repo, required_ids, if_present_ids=None,
361+ find_ghosts=False):
362+ """Constructor.
363+
364+ :param required_ids: revision IDs of heads that must be found, or else
365+ the search will fail with NoSuchRevision. All revisions in their
366+ ancestry not already in the other repository will be included in
367+ the search result.
368+ :param if_present_ids: revision IDs of heads that may be absent in the
369+ source repository. If present, then their ancestry not already
370+ found in other will be included in the search result.
371+ """
372+ self.to_repo = to_repo
373+ self.from_repo = from_repo
374+ self.find_ghosts = find_ghosts
375+ self.required_ids = required_ids
376+ self.if_present_ids = if_present_ids
377+
378+ def __repr__(self):
379+ if len(self.required_ids) > 5:
380+ reqd_revs_repr = repr(list(self.required_ids)[:5])[:-1] + ', ...]'
381+ else:
382+ reqd_revs_repr = repr(self.required_ids)
383+ if self.if_present_ids and len(self.if_present_ids) > 5:
384+ ifp_revs_repr = repr(list(self.if_present_ids)[:5])[:-1] + ', ...]'
385+ else:
386+ ifp_revs_repr = repr(self.if_present_ids)
387+
388+ return "<%s from:%r to:%r find_ghosts:%r req'd:%r if-present:%r>" % (
389+ self.__class__.__name__, self.from_repo, self.to_repo,
390+ self.find_ghosts, reqd_revs_repr, ifp_revs_repr)
391+
392+ def get_search_result(self):
393+ return self.to_repo.search_missing_revision_ids(
394+ self.from_repo, revision_ids=self.required_ids,
395+ if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts)
396+
397+
398 def collapse_linear_regions(parent_map):
399 """Collapse regions of the graph that are 'linear'.
400
401
402=== modified file 'bzrlib/plugin.py'
403--- bzrlib/plugin.py 2010-06-11 08:02:42 +0000
404+++ bzrlib/plugin.py 2011-01-19 00:31:58 +0000
405@@ -1,4 +1,4 @@
406-# Copyright (C) 2005-2010 Canonical Ltd
407+# Copyright (C) 2005-2011 Canonical Ltd
408 #
409 # This program is free software; you can redistribute it and/or modify
410 # it under the terms of the GNU General Public License as published by
411@@ -63,6 +63,11 @@
412 _plugins_disabled = False
413
414
415+plugin_warnings = {}
416+# Map from plugin name, to list of string warnings about eg plugin
417+# dependencies.
418+
419+
420 def are_plugins_disabled():
421 return _plugins_disabled
422
423@@ -77,6 +82,40 @@
424 load_plugins([])
425
426
427+def describe_loaded_plugins(show_paths=False):
428+ """Generate text description of loaded plugins.
429+
430+ :param show_paths: If true,
431+ :returns: Iterator of text lines (including newlines.)
432+ """
433+ from inspect import getdoc
434+ unreported_warnings = plugin_warnings.copy()
435+ for name, plugin in sorted(plugins().items()):
436+ version = plugin.__version__
437+ if version == 'unknown':
438+ version = ''
439+ name_ver = '%s %s' % (name, version)
440+ d = getdoc(plugin.module)
441+ if d:
442+ doc = d.split('\n')[0]
443+ else:
444+ doc = '(no description)'
445+ yield ("%s\n" % name_ver)
446+ yield (" %s\n" % doc)
447+ if show_paths:
448+ yield (" %s\n" % plugin.path())
449+ if name in unreported_warnings:
450+ for line in unreported_warnings[name]:
451+ yield " ** " + line + '\n'
452+ del unreported_warnings[name]
453+ yield ("\n")
454+ for name in sorted(unreported_warnings.keys()):
455+ yield "%s (failed to load)\n" % name
456+ for line in unreported_warnings[name]:
457+ yield " ** " + line + '\n'
458+ yield '\n'
459+
460+
461 def _strip_trailing_sep(path):
462 return path.rstrip("\\/")
463
464@@ -327,6 +366,11 @@
465 return None, None, (None, None, None)
466
467
468+def record_plugin_warning(plugin_name, warning_message):
469+ trace.mutter(warning_message)
470+ plugin_warnings.setdefault(plugin_name, []).append(warning_message)
471+
472+
473 def _load_plugin_module(name, dir):
474 """Load plugin name from dir.
475
476@@ -340,10 +384,12 @@
477 except KeyboardInterrupt:
478 raise
479 except errors.IncompatibleAPI, e:
480- trace.warning("Unable to load plugin %r. It requested API version "
481+ warning_message = (
482+ "Unable to load plugin %r. It requested API version "
483 "%s of module %s but the minimum exported version is %s, and "
484 "the maximum is %s" %
485 (name, e.wanted, e.api, e.minimum, e.current))
486+ record_plugin_warning(name, warning_message)
487 except Exception, e:
488 trace.warning("%s" % e)
489 if re.search('\.|-| ', name):
490@@ -354,7 +400,9 @@
491 "file path isn't a valid module name; try renaming "
492 "it to %r." % (name, dir, sanitised_name))
493 else:
494- trace.warning('Unable to load plugin %r from %r' % (name, dir))
495+ record_plugin_warning(
496+ name,
497+ 'Unable to load plugin %r from %r' % (name, dir))
498 trace.log_exception_quietly()
499 if 'error' in debug.debug_flags:
500 trace.print_exception(sys.exc_info(), sys.stderr)
501
502=== modified file 'bzrlib/plugins/launchpad/lp_propose.py'
503--- bzrlib/plugins/launchpad/lp_propose.py 2010-12-02 10:41:05 +0000
504+++ bzrlib/plugins/launchpad/lp_propose.py 2011-01-19 00:31:58 +0000
505@@ -14,21 +14,22 @@
506 # along with this program; if not, write to the Free Software
507 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
508
509-
510-import webbrowser
511-
512 from bzrlib import (
513 errors,
514 hooks,
515+ )
516+from bzrlib.lazy_import import lazy_import
517+lazy_import(globals(), """
518+import webbrowser
519+
520+from bzrlib import (
521 msgeditor,
522 )
523 from bzrlib.plugins.launchpad import (
524 lp_api,
525 lp_registration,
526 )
527-from bzrlib.plugins.launchpad.lp_api import canonical_url
528-
529-from lazr.restfulclient import errors as restful_errors
530+""")
531
532
533 class ProposeMergeHooks(hooks.Hooks):
534@@ -153,7 +154,7 @@
535 if mp.target_branch.self_link == self.target_branch.lp.self_link:
536 raise errors.BzrCommandError(
537 'There is already a branch merge proposal: %s' %
538- canonical_url(mp))
539+ lp_api.canonical_url(mp))
540
541 def _get_prerequisite_branch(self):
542 hooks = self.hooks['get_prerequisite']
543@@ -174,6 +175,7 @@
544 :param **kwargs: **kwargs for the call.
545 :return: The result of calling call(*args, *kwargs).
546 """
547+ from lazr.restfulclient import errors as restful_errors
548 try:
549 return call(*args, **kwargs)
550 except restful_errors.HTTPError, e:
551@@ -208,7 +210,7 @@
552 review_types=review_types)
553 if self.approve:
554 self.call_webservice(mp.setStatus, status='Approved')
555- webbrowser.open(canonical_url(mp))
556+ webbrowser.open(lp_api.canonical_url(mp))
557
558
559 def modified_files(old_tree, new_tree):
560
561=== modified file 'bzrlib/remote.py'
562--- bzrlib/remote.py 2011-01-10 22:20:12 +0000
563+++ bzrlib/remote.py 2011-01-19 00:31:58 +0000
564@@ -1348,15 +1348,29 @@
565 return result
566
567 @needs_read_lock
568- def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
569+ def search_missing_revision_ids(self, other,
570+ revision_id=symbol_versioning.DEPRECATED_PARAMETER,
571+ find_ghosts=True, revision_ids=None, if_present_ids=None):
572 """Return the revision ids that other has that this does not.
573
574 These are returned in topological order.
575
576 revision_id: only return revision ids included by revision_id.
577 """
578- return repository.InterRepository.get(
579- other, self).search_missing_revision_ids(revision_id, find_ghosts)
580+ if symbol_versioning.deprecated_passed(revision_id):
581+ symbol_versioning.warn(
582+ 'search_missing_revision_ids(revision_id=...) was '
583+ 'deprecated in 2.3. Use revision_ids=[...] instead.',
584+ DeprecationWarning, stacklevel=2)
585+ if revision_ids is not None:
586+ raise AssertionError(
587+ 'revision_ids is mutually exclusive with revision_id')
588+ if revision_id is not None:
589+ revision_ids = [revision_id]
590+ inter_repo = repository.InterRepository.get(other, self)
591+ return inter_repo.search_missing_revision_ids(
592+ find_ghosts=find_ghosts, revision_ids=revision_ids,
593+ if_present_ids=if_present_ids)
594
595 def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
596 fetch_spec=None):
597@@ -1763,12 +1777,7 @@
598 return '\n'.join((start_keys, stop_keys, count))
599
600 def _serialise_search_result(self, search_result):
601- if isinstance(search_result, graph.PendingAncestryResult):
602- parts = ['ancestry-of']
603- parts.extend(search_result.heads)
604- else:
605- recipe = search_result.get_recipe()
606- parts = [recipe[0], self._serialise_search_recipe(recipe)]
607+ parts = search_result.get_network_struct()
608 return '\n'.join(parts)
609
610 def autopack(self):
611@@ -1968,6 +1977,7 @@
612 candidate_verbs = [
613 ('Repository.get_stream_1.19', (1, 19)),
614 ('Repository.get_stream', (1, 13))]
615+
616 found_verb = False
617 for verb, version in candidate_verbs:
618 if medium._is_remote_before(version):
619@@ -1977,6 +1987,17 @@
620 verb, args, search_bytes)
621 except errors.UnknownSmartMethod:
622 medium._remember_remote_is_before(version)
623+ except errors.UnknownErrorFromSmartServer, e:
624+ if isinstance(search, graph.EverythingResult):
625+ error_verb = e.error_from_smart_server.error_verb
626+ if error_verb == 'BadSearch':
627+ # Pre-2.3 servers don't support this sort of search.
628+ # XXX: perhaps falling back to VFS on BadSearch is a
629+ # good idea in general? It might provide a little bit
630+ # of protection against client-side bugs.
631+ medium._remember_remote_is_before((2, 3))
632+ break
633+ raise
634 else:
635 response_tuple, response_handler = response
636 found_verb = True
637
638=== modified file 'bzrlib/repofmt/knitrepo.py'
639--- bzrlib/repofmt/knitrepo.py 2010-11-20 21:41:05 +0000
640+++ bzrlib/repofmt/knitrepo.py 2011-01-19 00:31:58 +0000
641@@ -43,6 +43,7 @@
642 RepositoryFormat,
643 RootCommitBuilder,
644 )
645+from bzrlib import symbol_versioning
646
647
648 class _KnitParentsProvider(object):
649@@ -534,16 +535,23 @@
650 return are_knits and InterRepository._same_model(source, target)
651
652 @needs_read_lock
653- def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
654- """See InterRepository.missing_revision_ids()."""
655- if revision_id is not None:
656- source_ids = self.source.get_ancestry(revision_id)
657- if source_ids[0] is not None:
658- raise AssertionError()
659- source_ids.pop(0)
660- else:
661- source_ids = self.source.all_revision_ids()
662- source_ids_set = set(source_ids)
663+ def search_missing_revision_ids(self,
664+ revision_id=symbol_versioning.DEPRECATED_PARAMETER,
665+ find_ghosts=True, revision_ids=None, if_present_ids=None):
666+ """See InterRepository.search_missing_revision_ids()."""
667+ if symbol_versioning.deprecated_passed(revision_id):
668+ symbol_versioning.warn(
669+ 'search_missing_revision_ids(revision_id=...) was '
670+ 'deprecated in 2.3. Use revision_ids=[...] instead.',
671+ DeprecationWarning, stacklevel=2)
672+ if revision_ids is not None:
673+ raise AssertionError(
674+ 'revision_ids is mutually exclusive with revision_id')
675+ if revision_id is not None:
676+ revision_ids = [revision_id]
677+ del revision_id
678+ source_ids_set = self._present_source_revisions_for(
679+ revision_ids, if_present_ids)
680 # source_ids is the worst possible case we may need to pull.
681 # now we want to filter source_ids against what we actually
682 # have in target, but don't try to check for existence where we know
683@@ -553,7 +561,7 @@
684 actually_present_revisions = set(
685 self.target._eliminate_revisions_not_present(possibly_present_revisions))
686 required_revisions = source_ids_set.difference(actually_present_revisions)
687- if revision_id is not None:
688+ if revision_ids is not None:
689 # we used get_ancestry to determine source_ids then we are assured all
690 # revisions referenced are present as they are installed in topological order.
691 # and the tip revision was validated by get_ancestry.
692
693=== modified file 'bzrlib/repofmt/weaverepo.py'
694--- bzrlib/repofmt/weaverepo.py 2011-01-13 00:41:19 +0000
695+++ bzrlib/repofmt/weaverepo.py 2011-01-19 00:31:58 +0000
696@@ -40,6 +40,7 @@
697 lockable_files,
698 lockdir,
699 osutils,
700+ symbol_versioning,
701 trace,
702 tuned_gzip,
703 urlutils,
704@@ -488,6 +489,7 @@
705 _versionedfile_class = weave.WeaveFile
706 supports_ghosts = False
707 supports_chks = False
708+ supports_funky_characters = False
709
710 _fetch_order = 'topological'
711 _fetch_reconcile = True
712@@ -806,8 +808,10 @@
713 self.target.fetch(self.source, revision_id=revision_id)
714
715 @needs_read_lock
716- def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
717- """See InterRepository.missing_revision_ids()."""
718+ def search_missing_revision_ids(self,
719+ revision_id=symbol_versioning.DEPRECATED_PARAMETER,
720+ find_ghosts=True, revision_ids=None, if_present_ids=None):
721+ """See InterRepository.search_missing_revision_ids()."""
722 # we want all revisions to satisfy revision_id in source.
723 # but we don't want to stat every file here and there.
724 # we want then, all revisions other needs to satisfy revision_id
725@@ -819,14 +823,19 @@
726 # disk format scales terribly for push anyway due to rewriting
727 # inventory.weave, this is considered acceptable.
728 # - RBC 20060209
729- if revision_id is not None:
730- source_ids = self.source.get_ancestry(revision_id)
731- if source_ids[0] is not None:
732- raise AssertionError()
733- source_ids.pop(0)
734- else:
735- source_ids = self.source._all_possible_ids()
736- source_ids_set = set(source_ids)
737+ if symbol_versioning.deprecated_passed(revision_id):
738+ symbol_versioning.warn(
739+ 'search_missing_revision_ids(revision_id=...) was '
740+ 'deprecated in 2.3. Use revision_ids=[...] instead.',
741+ DeprecationWarning, stacklevel=2)
742+ if revision_ids is not None:
743+ raise AssertionError(
744+ 'revision_ids is mutually exclusive with revision_id')
745+ if revision_id is not None:
746+ revision_ids = [revision_id]
747+ del revision_id
748+ source_ids_set = self._present_source_revisions_for(
749+ revision_ids, if_present_ids)
750 # source_ids is the worst possible case we may need to pull.
751 # now we want to filter source_ids against what we actually
752 # have in target, but don't try to check for existence where we know
753@@ -836,7 +845,7 @@
754 actually_present_revisions = set(
755 self.target._eliminate_revisions_not_present(possibly_present_revisions))
756 required_revisions = source_ids_set.difference(actually_present_revisions)
757- if revision_id is not None:
758+ if revision_ids is not None:
759 # we used get_ancestry to determine source_ids then we are assured all
760 # revisions referenced are present as they are installed in topological order.
761 # and the tip revision was validated by get_ancestry.
762
763=== modified file 'bzrlib/repository.py'
764--- bzrlib/repository.py 2011-01-13 00:41:19 +0000
765+++ bzrlib/repository.py 2011-01-19 00:31:58 +0000
766@@ -42,7 +42,6 @@
767 pyutils,
768 revision as _mod_revision,
769 static_tuple,
770- symbol_versioning,
771 trace,
772 tsort,
773 versionedfile,
774@@ -57,6 +56,7 @@
775 from bzrlib import (
776 errors,
777 registry,
778+ symbol_versioning,
779 ui,
780 )
781 from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
782@@ -1597,15 +1597,28 @@
783 return ret
784
785 @needs_read_lock
786- def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
787+ def search_missing_revision_ids(self, other,
788+ revision_id=symbol_versioning.DEPRECATED_PARAMETER,
789+ find_ghosts=True, revision_ids=None, if_present_ids=None):
790 """Return the revision ids that other has that this does not.
791
792 These are returned in topological order.
793
794 revision_id: only return revision ids included by revision_id.
795 """
796+ if symbol_versioning.deprecated_passed(revision_id):
797+ symbol_versioning.warn(
798+ 'search_missing_revision_ids(revision_id=...) was '
799+ 'deprecated in 2.3. Use revision_ids=[...] instead.',
800+ DeprecationWarning, stacklevel=3)
801+ if revision_ids is not None:
802+ raise AssertionError(
803+ 'revision_ids is mutually exclusive with revision_id')
804+ if revision_id is not None:
805+ revision_ids = [revision_id]
806 return InterRepository.get(other, self).search_missing_revision_ids(
807- revision_id, find_ghosts)
808+ find_ghosts=find_ghosts, revision_ids=revision_ids,
809+ if_present_ids=if_present_ids)
810
811 @staticmethod
812 def open(base):
813@@ -3436,7 +3449,7 @@
814 fetch_spec=fetch_spec,
815 find_ghosts=find_ghosts)
816
817- def _walk_to_common_revisions(self, revision_ids):
818+ def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
819 """Walk out from revision_ids in source to revisions target has.
820
821 :param revision_ids: The start point for the search.
822@@ -3444,10 +3457,14 @@
823 """
824 target_graph = self.target.get_graph()
825 revision_ids = frozenset(revision_ids)
826+ if if_present_ids:
827+ all_wanted_revs = revision_ids.union(if_present_ids)
828+ else:
829+ all_wanted_revs = revision_ids
830 missing_revs = set()
831 source_graph = self.source.get_graph()
832 # ensure we don't pay silly lookup costs.
833- searcher = source_graph._make_breadth_first_searcher(revision_ids)
834+ searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
835 null_set = frozenset([_mod_revision.NULL_REVISION])
836 searcher_exhausted = False
837 while True:
838@@ -3489,30 +3506,79 @@
839 return searcher.get_result()
840
841 @needs_read_lock
842- def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
843+ def search_missing_revision_ids(self,
844+ revision_id=symbol_versioning.DEPRECATED_PARAMETER,
845+ find_ghosts=True, revision_ids=None, if_present_ids=None):
846 """Return the revision ids that source has that target does not.
847
848 :param revision_id: only return revision ids included by this
849- revision_id.
850+ revision_id.
851+ :param revision_ids: return revision ids included by these
852+ revision_ids. NoSuchRevision will be raised if any of these
853+ revisions are not present.
854+ :param if_present_ids: like revision_ids, but will not cause
855+ NoSuchRevision if any of these are absent, instead they will simply
856+ not be in the result. This is useful for e.g. finding revisions
857+ to fetch for tags, which may reference absent revisions.
858 :param find_ghosts: If True find missing revisions in deep history
859 rather than just finding the surface difference.
860 :return: A bzrlib.graph.SearchResult.
861 """
862+ if symbol_versioning.deprecated_passed(revision_id):
863+ symbol_versioning.warn(
864+ 'search_missing_revision_ids(revision_id=...) was '
865+ 'deprecated in 2.3. Use revision_ids=[...] instead.',
866+ DeprecationWarning, stacklevel=2)
867+ if revision_ids is not None:
868+ raise AssertionError(
869+ 'revision_ids is mutually exclusive with revision_id')
870+ if revision_id is not None:
871+ revision_ids = [revision_id]
872+ del revision_id
873 # stop searching at found target revisions.
874- if not find_ghosts and revision_id is not None:
875- return self._walk_to_common_revisions([revision_id])
876+ if not find_ghosts and (revision_ids is not None or if_present_ids is
877+ not None):
878+ return self._walk_to_common_revisions(revision_ids,
879+ if_present_ids=if_present_ids)
880 # generic, possibly worst case, slow code path.
881 target_ids = set(self.target.all_revision_ids())
882- if revision_id is not None:
883- source_ids = self.source.get_ancestry(revision_id)
884- if source_ids[0] is not None:
885- raise AssertionError()
886- source_ids.pop(0)
887- else:
888- source_ids = self.source.all_revision_ids()
889+ source_ids = self._present_source_revisions_for(
890+ revision_ids, if_present_ids)
891 result_set = set(source_ids).difference(target_ids)
892 return self.source.revision_ids_to_search_result(result_set)
893
894+ def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
895+ """Returns set of all revisions in ancestry of revision_ids present in
896+ the source repo.
897+
898+ :param revision_ids: if None, all revisions in source are returned.
899+ :param if_present_ids: like revision_ids, but if any/all of these are
900+ absent no error is raised.
901+ """
902+ if revision_ids is not None or if_present_ids is not None:
903+ # First, ensure all specified revisions exist. Callers expect
904+ # NoSuchRevision when they pass absent revision_ids here.
905+ if revision_ids is None:
906+ revision_ids = set()
907+ if if_present_ids is None:
908+ if_present_ids = set()
909+ revision_ids = set(revision_ids)
910+ if_present_ids = set(if_present_ids)
911+ all_wanted_ids = revision_ids.union(if_present_ids)
912+ graph = self.source.get_graph()
913+ present_revs = set(graph.get_parent_map(all_wanted_ids))
914+ missing = revision_ids.difference(present_revs)
915+ if missing:
916+ raise errors.NoSuchRevision(self.source, missing.pop())
917+ found_ids = all_wanted_ids.intersection(present_revs)
918+ source_ids = [rev_id for (rev_id, parents) in
919+ graph.iter_ancestry(found_ids)
920+ if rev_id != _mod_revision.NULL_REVISION
921+ and parents is not None]
922+ else:
923+ source_ids = self.source.all_revision_ids()
924+ return set(source_ids)
925+
926 @staticmethod
927 def _same_model(source, target):
928 """True if source and target have the same data representation.
929@@ -3836,7 +3902,13 @@
930 fetch_spec=None):
931 """See InterRepository.fetch()."""
932 if fetch_spec is not None:
933- raise AssertionError("Not implemented yet...")
934+ if (isinstance(fetch_spec, graph.NotInOtherForRevs) and
935+ len(fetch_spec.required_ids) == 1 and not
936+ fetch_spec.if_present_ids):
937+ revision_id = list(fetch_spec.required_ids)[0]
938+ del fetch_spec
939+ else:
940+ raise AssertionError("Not implemented yet...")
941 ui.ui_factory.warn_experimental_format_fetch(self)
942 if (not self.source.supports_rich_root()
943 and self.target.supports_rich_root()):
944@@ -3849,8 +3921,12 @@
945 ui.ui_factory.show_user_warning('cross_format_fetch',
946 from_format=self.source._format,
947 to_format=self.target._format)
948+ if revision_id:
949+ search_revision_ids = [revision_id]
950+ else:
951+ search_revision_ids = None
952 revision_ids = self.target.search_missing_revision_ids(self.source,
953- revision_id, find_ghosts=find_ghosts).get_keys()
954+ revision_ids=search_revision_ids, find_ghosts=find_ghosts).get_keys()
955 if not revision_ids:
956 return 0, 0
957 revision_ids = tsort.topo_sort(
958
959=== modified file 'bzrlib/smart/repository.py'
960--- bzrlib/smart/repository.py 2010-11-16 06:06:11 +0000
961+++ bzrlib/smart/repository.py 2011-01-19 00:31:58 +0000
962@@ -81,6 +81,8 @@
963 recreate_search trusts that clients will look for missing things
964 they expected and get it from elsewhere.
965 """
966+ if search_bytes == 'everything':
967+ return graph.EverythingResult(repository), None
968 lines = search_bytes.split('\n')
969 if lines[0] == 'ancestry-of':
970 heads = lines[1:]
971@@ -412,6 +414,13 @@
972 def do_repository_request(self, repository, to_network_name):
973 """Get a stream for inserting into a to_format repository.
974
975+ The request body is 'search_bytes', a description of the revisions
976+ being requested.
977+
978+ In 2.3 this verb added support for search_bytes == 'everything'. Older
979+ implementations will respond with a BadSearch error, and clients should
980+ catch this and fallback appropriately.
981+
982 :param repository: The repository to stream from.
983 :param to_network_name: The network name of the format of the target
984 repository.
985@@ -489,6 +498,13 @@
986
987
988 class SmartServerRepositoryGetStream_1_19(SmartServerRepositoryGetStream):
989+ """The same as Repository.get_stream, but will return stream CHK formats to
990+ clients.
991+
992+ See SmartServerRepositoryGetStream._should_fake_unknown.
993+
994+ New in 1.19.
995+ """
996
997 def _should_fake_unknown(self):
998 """Returns False; we don't need to workaround bugs in 1.19+ clients."""
999
1000=== modified file 'bzrlib/smart/server.py'
1001--- bzrlib/smart/server.py 2010-07-01 15:25:41 +0000
1002+++ bzrlib/smart/server.py 2011-01-19 00:31:58 +0000
1003@@ -18,7 +18,6 @@
1004
1005 import errno
1006 import os.path
1007-import select
1008 import socket
1009 import sys
1010 import threading
1011@@ -27,7 +26,6 @@
1012 from bzrlib import (
1013 errors,
1014 trace,
1015- transport,
1016 )
1017 from bzrlib.lazy_import import lazy_import
1018 lazy_import(globals(), """
1019@@ -178,7 +176,7 @@
1020
1021 def get_url(self):
1022 """Return the url of the server"""
1023- return "bzr://%s:%d/" % self._sockname
1024+ return "bzr://%s:%s/" % (self._sockname[0], self._sockname[1])
1025
1026 def serve_conn(self, conn, thread_name_suffix):
1027 # For WIN32, where the timeout value from the listening socket
1028
1029=== modified file 'bzrlib/tests/blackbox/test_serve.py'
1030--- bzrlib/tests/blackbox/test_serve.py 2011-01-10 22:20:12 +0000
1031+++ bzrlib/tests/blackbox/test_serve.py 2011-01-19 00:31:58 +0000
1032@@ -18,16 +18,12 @@
1033 """Tests of the bzr serve command."""
1034
1035 import os
1036-import os.path
1037 import signal
1038-import subprocess
1039-import sys
1040 import thread
1041 import threading
1042
1043 from bzrlib import (
1044 builtins,
1045- debug,
1046 errors,
1047 osutils,
1048 revision as _mod_revision,
1049@@ -37,11 +33,13 @@
1050 from bzrlib.branch import Branch
1051 from bzrlib.bzrdir import BzrDir
1052 from bzrlib.smart import client, medium
1053-from bzrlib.smart.server import BzrServerFactory, SmartTCPServer
1054+from bzrlib.smart.server import (
1055+ BzrServerFactory,
1056+ SmartTCPServer,
1057+ )
1058 from bzrlib.tests import (
1059 TestCaseWithMemoryTransport,
1060 TestCaseWithTransport,
1061- TestSkipped,
1062 )
1063 from bzrlib.trace import mutter
1064 from bzrlib.transport import remote
1065@@ -53,9 +51,9 @@
1066 *func_args, **func_kwargs):
1067 """Run 'bzr serve', and run the given func in a thread once the server
1068 has started.
1069-
1070+
1071 When 'func' terminates, the server will be terminated too.
1072-
1073+
1074 Returns stdout and stderr.
1075 """
1076 # install hook
1077@@ -164,7 +162,7 @@
1078 url = 'bzr://localhost:%d/' % port
1079 self.permit_url(url)
1080 return process, url
1081-
1082+
1083 def test_bzr_serve_quiet(self):
1084 self.make_branch('.')
1085 args = ['--port', 'localhost:0', '--quiet']
1086@@ -334,4 +332,3 @@
1087 self.assertEqual(base_url, self.bzr_serve_transport.base)
1088 self.assertEqual(base_dir,
1089 server_maker.get_base_path(self.bzr_serve_transport))
1090-
1091
1092=== modified file 'bzrlib/tests/per_branch/test_push.py'
1093--- bzrlib/tests/per_branch/test_push.py 2011-01-13 01:02:53 +0000
1094+++ bzrlib/tests/per_branch/test_push.py 2011-01-19 00:31:58 +0000
1095@@ -170,6 +170,21 @@
1096 self.assertEqual(tree.branch.last_revision(),
1097 to_branch.last_revision())
1098
1099+ def test_push_overwrite_with_older_mainline_rev(self):
1100+ """Pushing an older mainline revision with overwrite.
1101+
1102+ This was <https://bugs.launchpad.net/bzr/+bug/386576>.
1103+ """
1104+ source = self.make_branch_and_tree('source')
1105+ target = self.make_branch('target')
1106+
1107+ source.commit('1st commit')
1108+ source.commit('2nd commit', rev_id='rev-2')
1109+ source.commit('3rd commit')
1110+ source.branch.push(target)
1111+ source.branch.push(target, stop_revision='rev-2', overwrite=True)
1112+ self.assertEqual('rev-2', target.last_revision())
1113+
1114 def test_push_overwrite_of_non_tip_with_stop_revision(self):
1115 """Combining the stop_revision and overwrite options works.
1116
1117
1118=== modified file 'bzrlib/tests/per_interrepository/test_interrepository.py'
1119--- bzrlib/tests/per_interrepository/test_interrepository.py 2011-01-13 00:41:19 +0000
1120+++ bzrlib/tests/per_interrepository/test_interrepository.py 2011-01-19 00:31:58 +0000
1121@@ -131,9 +131,15 @@
1122 self.assertFalse(repo_b.has_revision('pizza'))
1123 # Asking specifically for an absent revision errors.
1124 self.assertRaises(errors.NoSuchRevision,
1125- repo_b.search_missing_revision_ids, repo_a, revision_id='pizza',
1126+ repo_b.search_missing_revision_ids, repo_a, revision_ids=['pizza'],
1127 find_ghosts=True)
1128 self.assertRaises(errors.NoSuchRevision,
1129+ repo_b.search_missing_revision_ids, repo_a, revision_ids=['pizza'],
1130+ find_ghosts=False)
1131+ self.callDeprecated(
1132+ ['search_missing_revision_ids(revision_id=...) was deprecated in '
1133+ '2.3. Use revision_ids=[...] instead.'],
1134+ self.assertRaises, errors.NoSuchRevision,
1135 repo_b.search_missing_revision_ids, repo_a, revision_id='pizza',
1136 find_ghosts=False)
1137
1138@@ -143,7 +149,8 @@
1139 # make a repository to compare against that is empty
1140 repo_b = self.make_to_repository('empty')
1141 repo_a = self.bzrdir.open_repository()
1142- result = repo_b.search_missing_revision_ids(repo_a, revision_id='rev1')
1143+ result = repo_b.search_missing_revision_ids(
1144+ repo_a, revision_ids=['rev1'])
1145 self.assertEqual(set(['rev1']), result.get_keys())
1146 self.assertEqual(('search', set(['rev1']), set([NULL_REVISION]), 1),
1147 result.get_recipe())
1148
1149=== modified file 'bzrlib/tests/per_repository_reference/test_fetch.py'
1150--- bzrlib/tests/per_repository_reference/test_fetch.py 2009-06-01 18:13:46 +0000
1151+++ bzrlib/tests/per_repository_reference/test_fetch.py 2011-01-19 00:31:58 +0000
1152@@ -18,6 +18,7 @@
1153 from bzrlib import (
1154 branch,
1155 errors,
1156+ graph,
1157 )
1158 from bzrlib.smart import (
1159 server,
1160@@ -25,7 +26,7 @@
1161 from bzrlib.tests.per_repository import TestCaseWithRepository
1162
1163
1164-class TestFetch(TestCaseWithRepository):
1165+class TestFetchBase(TestCaseWithRepository):
1166
1167 def make_source_branch(self):
1168 # It would be nice if there was a way to force this to be memory-only
1169@@ -51,6 +52,9 @@
1170 self.addCleanup(source_b.unlock)
1171 return content, source_b
1172
1173+
1174+class TestFetch(TestFetchBase):
1175+
1176 def test_sprout_from_stacked_with_short_history(self):
1177 content, source_b = self.make_source_branch()
1178 # Split the generated content into a base branch, and a stacked branch
1179@@ -149,3 +153,38 @@
1180 source_b.lock_read()
1181 self.addCleanup(source_b.unlock)
1182 stacked.pull(source_b, stop_revision='B-id')
1183+
1184+
1185+class TestFetchFromRepoWithUnconfiguredFallbacks(TestFetchBase):
1186+
1187+ def make_stacked_source_repo(self):
1188+ _, source_b = self.make_source_branch()
1189+ # Use 'make_branch' which gives us a bzr:// branch when appropriate,
1190+ # rather than creating a branch-on-disk
1191+ stack_b = self.make_branch('stack-on')
1192+ stack_b.pull(source_b, stop_revision='B-id')
1193+ stacked_b = self.make_branch('stacked')
1194+ stacked_b.set_stacked_on_url('../stack-on')
1195+ stacked_b.pull(source_b, stop_revision='C-id')
1196+ return stacked_b.repository
1197+
1198+ def test_fetch_everything_includes_parent_invs(self):
1199+ stacked = self.make_stacked_source_repo()
1200+ repo_missing_fallbacks = stacked.bzrdir.open_repository()
1201+ self.addCleanup(repo_missing_fallbacks.lock_read().unlock)
1202+ target = self.make_repository('target')
1203+ self.addCleanup(target.lock_write().unlock)
1204+ target.fetch(
1205+ repo_missing_fallbacks,
1206+ fetch_spec=graph.EverythingResult(repo_missing_fallbacks))
1207+ self.assertEqual(repo_missing_fallbacks.revisions.keys(),
1208+ target.revisions.keys())
1209+ self.assertEqual(repo_missing_fallbacks.inventories.keys(),
1210+ target.inventories.keys())
1211+ self.assertEqual(['C-id'],
1212+ sorted(k[-1] for k in target.revisions.keys()))
1213+ self.assertEqual(['B-id', 'C-id'],
1214+ sorted(k[-1] for k in target.inventories.keys()))
1215+
1216+
1217+
1218
1219=== modified file 'bzrlib/tests/test_crash.py'
1220--- bzrlib/tests/test_crash.py 2011-01-12 01:01:53 +0000
1221+++ bzrlib/tests/test_crash.py 2011-01-19 00:31:58 +0000
1222@@ -26,6 +26,7 @@
1223 config,
1224 crash,
1225 osutils,
1226+ plugin,
1227 tests,
1228 )
1229
1230@@ -42,6 +43,11 @@
1231 self.overrideEnv('APPORT_CRASH_DIR', crash_dir)
1232 self.assertEquals(crash_dir, config.crash_dir())
1233
1234+ self.overrideAttr(
1235+ plugin,
1236+ 'plugin_warnings',
1237+ {'example': ['Failed to load plugin foo']})
1238+
1239 stderr = StringIO()
1240
1241 try:
1242@@ -71,3 +77,6 @@
1243 self.assertContainsRe(report, 'test_apport_report')
1244 # should also be in there
1245 self.assertContainsRe(report, '(?m)^CommandLine:')
1246+ self.assertContainsRe(
1247+ report,
1248+ 'Failed to load plugin foo')
1249
1250=== modified file 'bzrlib/tests/test_plugins.py'
1251--- bzrlib/tests/test_plugins.py 2011-01-10 22:20:12 +0000
1252+++ bzrlib/tests/test_plugins.py 2011-01-19 00:31:58 +0000
1253@@ -267,8 +267,14 @@
1254 stream.close()
1255
1256 def test_plugin_with_bad_api_version_reports(self):
1257- # This plugin asks for bzrlib api version 1.0.0, which is not supported
1258- # anymore.
1259+ """Try loading a plugin that requests an unsupported api.
1260+
1261+ Up to bzr 2.2, the plugin just didn't load. But now we prefer to let
1262+ it load, and record a warning that can be shown in error messages.
1263+
1264+ See https://bugs.launchpad.net/bzr/+bug/704195
1265+ """
1266+ self.overrideAttr(plugin, 'plugin_warnings', {})
1267 name = 'wants100.py'
1268 f = file(name, 'w')
1269 try:
1270@@ -276,9 +282,14 @@
1271 "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
1272 finally:
1273 f.close()
1274-
1275 log = self.load_and_capture(name)
1276- self.assertContainsRe(log,
1277+ self.assertNotContainsRe(log,
1278+ r"It requested API version")
1279+ self.assertEquals(
1280+ ['wants100'],
1281+ plugin.plugin_warnings.keys())
1282+ self.assertContainsRe(
1283+ plugin.plugin_warnings['wants100'][0],
1284 r"It requested API version")
1285
1286 def test_plugin_with_bad_name_does_not_load(self):
1287@@ -847,6 +858,9 @@
1288 self.create_plugin_package('test_foo', dir='standard/test_foo')
1289 # All the tests will load the 'test_foo' plugin from various locations
1290 self.addCleanup(self._unregister_plugin, 'test_foo')
1291+ # Unfortunately there's global cached state for the specific
1292+ # registered paths.
1293+ self.addCleanup(plugin.PluginImporter.reset)
1294
1295 def assertTestFooLoadedFrom(self, path):
1296 self.assertPluginKnown('test_foo')
1297
1298=== modified file 'bzrlib/tests/test_remote.py'
1299--- bzrlib/tests/test_remote.py 2011-01-12 01:01:53 +0000
1300+++ bzrlib/tests/test_remote.py 2011-01-19 00:31:58 +0000
1301@@ -57,9 +57,12 @@
1302 )
1303 from bzrlib.repofmt import groupcompress_repo, pack_repo
1304 from bzrlib.revision import NULL_REVISION
1305-from bzrlib.smart import medium
1306+from bzrlib.smart import medium, request
1307 from bzrlib.smart.client import _SmartClient
1308-from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
1309+from bzrlib.smart.repository import (
1310+ SmartServerRepositoryGetParentMap,
1311+ SmartServerRepositoryGetStream_1_19,
1312+ )
1313 from bzrlib.tests import (
1314 test_server,
1315 )
1316@@ -3180,11 +3183,63 @@
1317
1318 def test_copy_content_into_avoids_revision_history(self):
1319 local = self.make_branch('local')
1320- remote_backing_tree = self.make_branch_and_tree('remote')
1321- remote_backing_tree.commit("Commit.")
1322+ builder = self.make_branch_builder('remote')
1323+ builder.build_commit(message="Commit.")
1324 remote_branch_url = self.smart_server.get_url() + 'remote'
1325 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
1326 local.repository.fetch(remote_branch.repository)
1327 self.hpss_calls = []
1328 remote_branch.copy_content_into(local)
1329 self.assertFalse('Branch.revision_history' in self.hpss_calls)
1330+
1331+ def test_fetch_everything_needs_just_one_call(self):
1332+ local = self.make_branch('local')
1333+ builder = self.make_branch_builder('remote')
1334+ builder.build_commit(message="Commit.")
1335+ remote_branch_url = self.smart_server.get_url() + 'remote'
1336+ remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
1337+ self.hpss_calls = []
1338+ local.repository.fetch(remote_branch.repository,
1339+ fetch_spec=graph.EverythingResult(remote_branch.repository))
1340+ self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
1341+
1342+ def override_verb(self, verb_name, verb):
1343+ request_handlers = request.request_handlers
1344+ orig_verb = request_handlers.get(verb_name)
1345+ request_handlers.register(verb_name, verb, override_existing=True)
1346+ self.addCleanup(request_handlers.register, verb_name, orig_verb,
1347+ override_existing=True)
1348+
1349+ def test_fetch_everything_backwards_compat(self):
1350+ """Can fetch with EverythingResult even with pre 2.3 servers.
1351+
1352+ Pre-2.3 do not support 'everything' searches with the
1353+ Repository.get_stream_1.19 verb.
1354+ """
1355+ verb_log = []
1356+ class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
1357+ """A version of the Repository.get_stream_1.19 verb patched to
1358+ reject 'everything' searches the way 2.2 and earlier do.
1359+ """
1360+ def recreate_search(self, repository, search_bytes, discard_excess=False):
1361+ verb_log.append(search_bytes.split('\n', 1)[0])
1362+ if search_bytes == 'everything':
1363+ return (None, request.FailedSmartServerResponse(('BadSearch',)))
1364+ return super(OldGetStreamVerb,
1365+ self).recreate_search(repository, search_bytes,
1366+ discard_excess=discard_excess)
1367+ self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
1368+ local = self.make_branch('local')
1369+ builder = self.make_branch_builder('remote')
1370+ builder.build_commit(message="Commit.")
1371+ remote_branch_url = self.smart_server.get_url() + 'remote'
1372+ remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
1373+ self.hpss_calls = []
1374+ local.repository.fetch(remote_branch.repository,
1375+ fetch_spec=graph.EverythingResult(remote_branch.repository))
1376+ # make sure the overridden verb was used
1377+ self.assertLength(1, verb_log)
1378+ # more than one HPSS call is needed, but because it's a VFS callback
1379+ # its hard to predict exactly how many.
1380+ self.assertTrue(len(self.hpss_calls) > 1)
1381+
1382
1383=== modified file 'bzrlib/tests/test_repository.py'
1384--- bzrlib/tests/test_repository.py 2010-11-22 22:27:58 +0000
1385+++ bzrlib/tests/test_repository.py 2011-01-19 00:31:58 +0000
1386@@ -1659,7 +1659,7 @@
1387 self.orig_pack = target.pack
1388 target.pack = self.log_pack
1389 search = target.search_missing_revision_ids(
1390- source_tree.branch.repository, tip)
1391+ source_tree.branch.repository, revision_ids=[tip])
1392 stream = source.get_stream(search)
1393 from_format = source_tree.branch.repository._format
1394 sink = target._get_sink()
1395
1396=== modified file 'bzrlib/tests/test_server.py'
1397--- bzrlib/tests/test_server.py 2011-01-12 01:01:53 +0000
1398+++ bzrlib/tests/test_server.py 2011-01-19 00:31:58 +0000
1399@@ -694,8 +694,6 @@
1400 server.SmartTCPServer.__init__(self, backing_transport,
1401 root_client_path)
1402 def serve(self):
1403- # FIXME: No test are exercising the hooks for the test server
1404- # -- vila 20100618
1405 self.run_server_started_hooks()
1406 try:
1407 TestingThreadingTCPServer.serve(self)
1408@@ -803,7 +801,3 @@
1409 """Get a backing transport from a server we are decorating."""
1410 url = 'readonly+' + backing_transport_server.get_url()
1411 return transport.get_transport(url)
1412-
1413-
1414-
1415-
1416
1417=== modified file 'bzrlib/tests/test_smart.py'
1418--- bzrlib/tests/test_smart.py 2011-01-12 01:01:53 +0000
1419+++ bzrlib/tests/test_smart.py 2011-01-19 00:31:58 +0000
1420@@ -41,6 +41,7 @@
1421 repository as smart_repo,
1422 packrepository as smart_packrepo,
1423 request as smart_req,
1424+ server,
1425 vfs,
1426 )
1427 from bzrlib.tests import test_server
1428@@ -1469,7 +1470,7 @@
1429 request.execute('stacked', 1, (3, r3)))
1430
1431
1432-class TestSmartServerRepositoryGetStream(tests.TestCaseWithMemoryTransport):
1433+class GetStreamTestBase(tests.TestCaseWithMemoryTransport):
1434
1435 def make_two_commit_repo(self):
1436 tree = self.make_branch_and_memory_tree('.')
1437@@ -1481,6 +1482,9 @@
1438 repo = tree.branch.repository
1439 return repo, r1, r2
1440
1441+
1442+class TestSmartServerRepositoryGetStream(GetStreamTestBase):
1443+
1444 def test_ancestry_of(self):
1445 """The search argument may be a 'ancestry-of' some heads'."""
1446 backing = self.get_transport()
1447@@ -1507,6 +1511,18 @@
1448 stream_bytes = ''.join(response.body_stream)
1449 self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
1450
1451+ def test_search_everything(self):
1452+ """A search of 'everything' returns a stream."""
1453+ backing = self.get_transport()
1454+ request = smart_repo.SmartServerRepositoryGetStream_1_19(backing)
1455+ repo, r1, r2 = self.make_two_commit_repo()
1456+ serialised_fetch_spec = 'everything'
1457+ request.execute('', repo._format.network_name())
1458+ response = request.do_body(serialised_fetch_spec)
1459+ self.assertEqual(('ok',), response.args)
1460+ stream_bytes = ''.join(response.body_stream)
1461+ self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
1462+
1463
1464 class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
1465
1466@@ -1906,6 +1922,8 @@
1467 smart_repo.SmartServerRepositoryGetRevisionGraph)
1468 self.assertHandlerEqual('Repository.get_stream',
1469 smart_repo.SmartServerRepositoryGetStream)
1470+ self.assertHandlerEqual('Repository.get_stream_1.19',
1471+ smart_repo.SmartServerRepositoryGetStream_1_19)
1472 self.assertHandlerEqual('Repository.has_revision',
1473 smart_repo.SmartServerRequestHasRevision)
1474 self.assertHandlerEqual('Repository.insert_stream',
1475@@ -1922,3 +1940,50 @@
1476 smart_repo.SmartServerRepositoryUnlock)
1477 self.assertHandlerEqual('Transport.is_readonly',
1478 smart_req.SmartServerIsReadonly)
1479+
1480+
1481+class SmartTCPServerHookTests(tests.TestCaseWithMemoryTransport):
1482+ """Tests for SmartTCPServer hooks."""
1483+
1484+ def setUp(self):
1485+ super(SmartTCPServerHookTests, self).setUp()
1486+ self.server = server.SmartTCPServer(self.get_transport())
1487+
1488+ def test_run_server_started_hooks(self):
1489+ """Test the server started hooks get fired properly."""
1490+ started_calls = []
1491+ server.SmartTCPServer.hooks.install_named_hook('server_started',
1492+ lambda backing_urls, url: started_calls.append((backing_urls, url)),
1493+ None)
1494+ started_ex_calls = []
1495+ server.SmartTCPServer.hooks.install_named_hook('server_started_ex',
1496+ lambda backing_urls, url: started_ex_calls.append((backing_urls, url)),
1497+ None)
1498+ self.server._sockname = ('example.com', 42)
1499+ self.server.run_server_started_hooks()
1500+ self.assertEquals(started_calls,
1501+ [([self.get_transport().base], 'bzr://example.com:42/')])
1502+ self.assertEquals(started_ex_calls,
1503+ [([self.get_transport().base], self.server)])
1504+
1505+ def test_run_server_started_hooks_ipv6(self):
1506+ """Test that socknames can contain 4-tuples."""
1507+ self.server._sockname = ('::', 42, 0, 0)
1508+ started_calls = []
1509+ server.SmartTCPServer.hooks.install_named_hook('server_started',
1510+ lambda backing_urls, url: started_calls.append((backing_urls, url)),
1511+ None)
1512+ self.server.run_server_started_hooks()
1513+ self.assertEquals(started_calls,
1514+ [([self.get_transport().base], 'bzr://:::42/')])
1515+
1516+ def test_run_server_stopped_hooks(self):
1517+ """Test the server stopped hooks."""
1518+ self.server._sockname = ('example.com', 42)
1519+ stopped_calls = []
1520+ server.SmartTCPServer.hooks.install_named_hook('server_stopped',
1521+ lambda backing_urls, url: stopped_calls.append((backing_urls, url)),
1522+ None)
1523+ self.server.run_server_stopped_hooks()
1524+ self.assertEquals(stopped_calls,
1525+ [([self.get_transport().base], 'bzr://example.com:42/')])
1526
1527=== added file 'doc/developers/fetch.txt'
1528--- doc/developers/fetch.txt 1970-01-01 00:00:00 +0000
1529+++ doc/developers/fetch.txt 2011-01-19 00:31:58 +0000
1530@@ -0,0 +1,86 @@
1531+=============
1532+Fetching data
1533+=============
1534+
1535+Overview of a fetch
1536+===================
1537+
1538+Inside bzr, a typical fetch happens like this:
1539+
1540+* a user runs a command like ``bzr branch`` or ``bzr pull``
1541+
1542+* ``Repository.fetch`` is called (by a higher-level method such as
1543+ ``ControlDir.sprout``, ``Branch.fetch``, etc).
1544+
1545+* An ``InterRepository`` object is created. The exact implementation of
1546+ ``InterRepository`` chosen depends on the format/capabilities of the
1547+ source and target repos.
1548+
1549+* The source and target repositories are compared to determine which data
1550+ needs to be transferred.
1551+
1552+* The repository data is copied. Often this is done by creating a
1553+ ``StreamSource`` and ``StreamSink`` from the source and target
1554+ repositories and feeding the stream from the source into the sink, but
1555+ some ``InterRepository`` implementations do differently.
1556+
1557+
1558+How objects to be transferred are determined
1559+============================================
1560+
1561+See ``InterRepository._walk_to_common_revisions``. The basic idea is to
1562+do a breadth-first search in the source repository's revision graph
1563+(starting from the head or heads the caller asked for), and look in the
1564+target repository to see if those revisions are already present.
1565+Eventually this will find the common ancestors in both graphs, and thus
1566+the set of revisions to be copied has been identified.
1567+
1568+All inventories for the copied revisions need to be present (and all
1569+parent inventories at the stacking boundary too, to support stacking).
1570+
1571+All texts versions introduced by those inventories need to be transferred
1572+(but see also stacking constraints).
1573+
1574+Fetch specs
1575+===========
1576+
1577+The most ``fetch`` methods accept a ``fetch_spec`` parameter. This is how
1578+the caller controls what is fetched: e.g. all revisions for a given head
1579+(that aren't already present in the target), the full ancestry for one or
1580+more heads, or even the full contents of the source repository.
1581+
1582+The ``fetch_spec`` parameter is an object that implements the interface
1583+defined by ``AbstractSearchResult`` in ``bzrlib.graph``. It describes
1584+which keys should be fetched. Current implementations are
1585+``SearchResult``, ``PendingAncestryResult``, ``EmptySearchResult``, and
1586+``EverythingResult``. Some have options controlling if missing revisions
1587+cause errors or not, etc.
1588+
1589+There are also some “search” objects, which can be used to conveniently
1590+construct a search result for common cases: ``EverythingNotInOther`` and
1591+``NotInOtherForRevs``. They provide an ``execute`` method that performs
1592+the search and returns a search result.
1593+
1594+Also, ``Graph._make_breadth_first_searcher`` returns an object with a
1595+``get_result`` method that returns a search result.
1596+
1597+
1598+Streams
1599+=======
1600+
1601+A **stream** is an iterable of (substream type, substream) pairs.
1602+The **substream type** is a ``str`` that will be one of ``texts``,
1603+``inventories``, ``inventory-deltas``, ``chk_bytes``, ``revisions`` or
1604+``signatures``. A **substream** is a record stream. The format of those
1605+records depends on the repository format being streamed, except for
1606+``inventory-deltas`` records which are format-independent.
1607+
1608+A stream source can be constructed with ``repo._get_source(to_format)``,
1609+and it provides a ``get_stream(search)`` method (among others). A stream
1610+sink can be constructed with ``repo._get_sink()``, and provides an
1611+``insert_stream(stream, src_format, resume_tokens)`` method (among
1612+others).
1613+
1614+
1615+..
1616+ vim: ft=rst tw=74 ai
1617
1618=== modified file 'doc/developers/index.txt'
1619--- doc/developers/index.txt 2010-10-13 04:13:48 +0000
1620+++ doc/developers/index.txt 2011-01-19 00:31:58 +0000
1621@@ -38,6 +38,7 @@
1622
1623 transports
1624 ui
1625+ fetch
1626
1627 Releasing and Packaging
1628 =======================
1629
1630=== modified file 'doc/en/release-notes/bzr-2.3.txt'
1631--- doc/en/release-notes/bzr-2.3.txt 2011-01-15 17:29:41 +0000
1632+++ doc/en/release-notes/bzr-2.3.txt 2011-01-19 00:31:58 +0000
1633@@ -32,6 +32,11 @@
1634 .. Fixes for situations where bzr would previously crash or give incorrect
1635 or undesirable results.
1636
1637+* Plugins incompatible with bzr no longer produce a warning on every
1638+ command invocation. Instead, a message is shown by ``bzr plugins`` and
1639+ in crash reports.
1640+ (#704195, Martin Pool)
1641+
1642 Documentation
1643 *************
1644
1645@@ -275,6 +280,11 @@
1646 crashes when encountering private bugs (they are just displayed as such).
1647 (Vincent Ladeuil, #354985)
1648
1649+* The ``revision_id`` parameter of
1650+ ``Repository.search_missing_revision_ids`` and
1651+ ``InterRepository.search_missing_revision_ids`` is deprecated. It is
1652+ replaced by the ``revision_ids`` parameter. (Andrew Bennetts)
1653+
1654 Internals
1655 *********
1656
1657
1658=== added file 'doc/en/release-notes/bzr-2.4.txt'
1659--- doc/en/release-notes/bzr-2.4.txt 1970-01-01 00:00:00 +0000
1660+++ doc/en/release-notes/bzr-2.4.txt 2011-01-19 00:31:58 +0000
1661@@ -0,0 +1,64 @@
1662+####################
1663+Bazaar Release Notes
1664+####################
1665+
1666+.. toctree::
1667+ :maxdepth: 1
1668+
1669+bzr 2.4b1
1670+#########
1671+
1672+:2.4b1: NOT RELEASED YET
1673+
1674+External Compatibility Breaks
1675+*****************************
1676+
1677+.. These may require users to change the way they use Bazaar.
1678+
1679+New Features
1680+************
1681+
1682+.. New commands, options, etc that users may wish to try out.
1683+
1684+Improvements
1685+************
1686+
1687+.. Improvements to existing commands, especially improved performance
1688+ or memory usage, or better results.
1689+
1690+Bug Fixes
1691+*********
1692+
1693+.. Fixes for situations where bzr would previously crash or give incorrect
1694+ or undesirable results.
1695+
1696+* ``bzr push --overwrite`` with an older revision specified will now correctly
1697+ roll back the target branch. (Jelmer Vernooij, #386576)
1698+
1699+* ``bzr serve`` no longer crashes when a server_started hook is installed and IPv6
1700+ support is available on the system. (Jelmer Vernooij, #293697)
1701+
1702+Documentation
1703+*************
1704+
1705+.. Improved or updated documentation.
1706+
1707+API Changes
1708+***********
1709+
1710+.. Changes that may require updates in plugins or other code that uses
1711+ bzrlib.
1712+
1713+Internals
1714+*********
1715+
1716+.. Major internal changes, unlikely to be visible to users or plugin
1717+ developers, but interesting for bzr developers.
1718+
1719+Testing
1720+*******
1721+
1722+.. Fixes and changes that are only relevant to bzr's test framework and
1723+ suite. This can include new facilities for writing tests, fixes to
1724+ spurious test failures and changes to the way things should be tested.
1725+
1726
1727=== added file 'doc/en/whats-new/whats-new-in-2.4.txt'
1728--- doc/en/whats-new/whats-new-in-2.4.txt 1970-01-01 00:00:00 +0000
1729+++ doc/en/whats-new/whats-new-in-2.4.txt 2011-01-19 00:31:58 +0000
1730@@ -0,0 +1,33 @@
1731+*************************
1732+What's New in Bazaar 2.4?
1733+*************************
1734+
1735+Bazaar 2.4 is still under development, and will be released in August 2011.
1736+This document accumulates a high level summary of what's changed. See the
1737+:doc:`../release-notes/index` for a full list.
1738+
1739+Users are encouraged to upgrade from the other stable series. This
1740+document outlines the improvements in Bazaar 2.4 vs Bazaar 2.3. As well as
1741+summarizing improvements made to the core product, it highlights
1742+enhancements within the broader Bazaar world of potential interest to
1743+those upgrading.
1744+
1745+Bazaar 2.4.0 is fully compatible both locally and on the network with 2.0
1746+2.1, 2.2 and 2.3, and can read and write repositories generated by all
1747+previous versions.
1748+
1749+
1750+Further information
1751+*******************
1752+
1753+For more detailed information on the changes made, see the the
1754+:doc:`../release-notes/index` for:
1755+
1756+* the interim bzr `milestones <https://launchpad.net/bzr/2.4>`_
1757+* the plugins you use.
1758+
1759+For a summary of changes made in earlier releases, see:
1760+
1761+* :doc:`whats-new-in-2.1`
1762+* :doc:`whats-new-in-2.2`
1763+* :doc:`whats-new-in-2.3`

Subscribers

People subscribed via source and target branches