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
=== modified file 'bzr'
--- bzr 2010-11-26 17:59:08 +0000
+++ bzr 2011-01-19 00:31:58 +0000
@@ -23,7 +23,7 @@
23import warnings23import warnings
2424
25# update this on each release25# update this on each release
26_script_version = (2, 3, 0)26_script_version = (2, 4, 0)
2727
28try:28try:
29 version_info = sys.version_info29 version_info = sys.version_info
3030
=== modified file 'bzrlib/__init__.py'
--- bzrlib/__init__.py 2011-01-14 23:20:15 +0000
+++ bzrlib/__init__.py 2011-01-19 00:31:58 +0000
@@ -52,10 +52,10 @@
52# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a52# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
53# releaselevel of 'dev' for unreleased under-development code.53# releaselevel of 'dev' for unreleased under-development code.
5454
55version_info = (2, 3, 0, 'dev', 6)55version_info = (2, 4, 0, 'dev', 1)
5656
57# API compatibility version57# API compatibility version
58api_minimum_version = (2, 3, 0)58api_minimum_version = (2, 4, 0)
5959
6060
61def _format_version_tuple(version_info):61def _format_version_tuple(version_info):
6262
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2011-01-12 01:01:53 +0000
+++ bzrlib/branch.py 2011-01-19 00:31:58 +0000
@@ -2664,7 +2664,7 @@
2664 result.target_branch = target2664 result.target_branch = target
2665 result.old_revno, result.old_revid = target.last_revision_info()2665 result.old_revno, result.old_revid = target.last_revision_info()
2666 self.update_references(target)2666 self.update_references(target)
2667 if result.old_revid != self.last_revision():2667 if result.old_revid != stop_revision:
2668 # We assume that during 'push' this repository is closer than2668 # We assume that during 'push' this repository is closer than
2669 # the target.2669 # the target.
2670 graph = self.repository.get_graph(target.repository)2670 graph = self.repository.get_graph(target.repository)
26712671
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2011-01-12 01:01:53 +0000
+++ bzrlib/builtins.py 2011-01-19 00:31:58 +0000
@@ -4581,26 +4581,9 @@
45814581
4582 @display_command4582 @display_command
4583 def run(self, verbose=False):4583 def run(self, verbose=False):
4584 import bzrlib.plugin4584 from bzrlib import plugin
4585 from inspect import getdoc4585 self.outf.writelines(
4586 result = []4586 plugin.describe_loaded_plugins(show_paths=verbose))
4587 for name, plugin in bzrlib.plugin.plugins().items():
4588 version = plugin.__version__
4589 if version == 'unknown':
4590 version = ''
4591 name_ver = '%s %s' % (name, version)
4592 d = getdoc(plugin.module)
4593 if d:
4594 doc = d.split('\n')[0]
4595 else:
4596 doc = '(no description)'
4597 result.append((name_ver, doc, plugin.path()))
4598 for name_ver, doc, path in sorted(result):
4599 self.outf.write("%s\n" % name_ver)
4600 self.outf.write(" %s\n" % doc)
4601 if verbose:
4602 self.outf.write(" %s\n" % path)
4603 self.outf.write("\n")
46044587
46054588
4606class cmd_testament(Command):4589class cmd_testament(Command):
46074590
=== modified file 'bzrlib/crash.py'
--- bzrlib/crash.py 2010-09-28 18:51:47 +0000
+++ bzrlib/crash.py 2011-01-19 00:31:58 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2009, 2010 Canonical Ltd1# Copyright (C) 2009, 2010, 2011 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -254,11 +254,7 @@
254254
255255
256def _format_plugin_list():256def _format_plugin_list():
257 plugin_lines = []257 return ''.join(plugin.describe_loaded_plugins(show_paths=True))
258 for name, a_plugin in sorted(plugin.plugins().items()):
259 plugin_lines.append(" %-20s %s [%s]" %
260 (name, a_plugin.path(), a_plugin.__version__))
261 return '\n'.join(plugin_lines)
262258
263259
264def _format_module_list():260def _format_module_list():
265261
=== modified file 'bzrlib/fetch.py'
--- bzrlib/fetch.py 2011-01-10 22:20:12 +0000
+++ bzrlib/fetch.py 2011-01-19 00:31:58 +0000
@@ -28,6 +28,7 @@
28from bzrlib.lazy_import import lazy_import28from bzrlib.lazy_import import lazy_import
29lazy_import(globals(), """29lazy_import(globals(), """
30from bzrlib import (30from bzrlib import (
31 graph,
31 tsort,32 tsort,
32 versionedfile,33 versionedfile,
33 )34 )
@@ -93,7 +94,8 @@
93 try:94 try:
94 pb.update("Finding revisions", 0, 2)95 pb.update("Finding revisions", 0, 2)
95 search = self._revids_to_fetch()96 search = self._revids_to_fetch()
96 if search is None:97 mutter('fetching: %s', search)
98 if search.is_empty():
97 return99 return
98 pb.update("Fetching revisions", 1, 2)100 pb.update("Fetching revisions", 1, 2)
99 self._fetch_everything_for_search(search)101 self._fetch_everything_for_search(search)
@@ -148,17 +150,33 @@
148 """Determines the exact revisions needed from self.from_repository to150 """Determines the exact revisions needed from self.from_repository to
149 install self._last_revision in self.to_repository.151 install self._last_revision in self.to_repository.
150152
151 If no revisions need to be fetched, then this just returns None.153 :returns: A SearchResult of some sort. (Possibly a
154 PendingAncestryResult, EmptySearchResult, etc.)
152 """155 """
153 if self._fetch_spec is not None:156 mutter("self._fetch_spec, self._last_revision: %r, %r",
157 self._fetch_spec, self._last_revision)
158 get_search_result = getattr(self._fetch_spec, 'get_search_result', None)
159 if get_search_result is not None:
160 mutter(
161 'resolving fetch_spec into search result: %s', self._fetch_spec)
162 # This is EverythingNotInOther or a similar kind of fetch_spec.
163 # Turn it into a search result.
164 return get_search_result()
165 elif self._fetch_spec is not None:
166 # The fetch spec is already a concrete search result.
154 return self._fetch_spec167 return self._fetch_spec
155 mutter('fetch up to rev {%s}', self._last_revision)168 elif self._last_revision == NULL_REVISION:
156 if self._last_revision is NULL_REVISION:169 # fetch_spec is None + last_revision is null => empty fetch.
157 # explicit limit of no revisions needed170 # explicit limit of no revisions needed
158 return None171 return graph.EmptySearchResult()
159 return self.to_repository.search_missing_revision_ids(172 elif self._last_revision is not None:
160 self.from_repository, self._last_revision,173 return graph.NotInOtherForRevs(self.to_repository,
161 find_ghosts=self.find_ghosts)174 self.from_repository, [self._last_revision],
175 find_ghosts=self.find_ghosts).get_search_result()
176 else: # self._last_revision is None:
177 return graph.EverythingNotInOther(self.to_repository,
178 self.from_repository,
179 find_ghosts=self.find_ghosts).get_search_result()
162180
163181
164class Inter1and2Helper(object):182class Inter1and2Helper(object):
165183
=== modified file 'bzrlib/graph.py'
--- bzrlib/graph.py 2011-01-10 22:20:12 +0000
+++ bzrlib/graph.py 2011-01-19 00:31:58 +0000
@@ -1536,7 +1536,57 @@
1536 return revs, ghosts1536 return revs, ghosts
15371537
15381538
1539class SearchResult(object):1539class AbstractSearchResult(object):
1540
1541 def get_recipe(self):
1542 """Return a recipe that can be used to replay this search.
1543
1544 The recipe allows reconstruction of the same results at a later date.
1545
1546 :return: A tuple of (search_kind_str, *details). The details vary by
1547 kind of search result.
1548 """
1549 raise NotImplementedError(self.get_recipe)
1550
1551 def get_network_struct(self):
1552 """Return a tuple that can be transmitted via the HPSS protocol."""
1553 raise NotImplementedError(self.get_network_struct)
1554
1555 def get_keys(self):
1556 """Return the keys found in this search.
1557
1558 :return: A set of keys.
1559 """
1560 raise NotImplementedError(self.get_keys)
1561
1562 def is_empty(self):
1563 """Return false if the search lists 1 or more revisions."""
1564 raise NotImplementedError(self.is_empty)
1565
1566 def refine(self, seen, referenced):
1567 """Create a new search by refining this search.
1568
1569 :param seen: Revisions that have been satisfied.
1570 :param referenced: Revision references observed while satisfying some
1571 of this search.
1572 :return: A search result.
1573 """
1574 raise NotImplementedError(self.refine)
1575
1576
1577class AbstractSearch(object):
1578
1579 def get_search_result(self):
1580 """Construct a network-ready search result from this search description.
1581
1582 This may take some time to search repositories, etc.
1583
1584 :return: A search result.
1585 """
1586 raise NotImplementedError(self.get_search_result)
1587
1588
1589class SearchResult(AbstractSearchResult):
1540 """The result of a breadth first search.1590 """The result of a breadth first search.
15411591
1542 A SearchResult provides the ability to reconstruct the search or access a1592 A SearchResult provides the ability to reconstruct the search or access a
@@ -1557,6 +1607,19 @@
1557 self._recipe = ('search', start_keys, exclude_keys, key_count)1607 self._recipe = ('search', start_keys, exclude_keys, key_count)
1558 self._keys = frozenset(keys)1608 self._keys = frozenset(keys)
15591609
1610 def __repr__(self):
1611 kind, start_keys, exclude_keys, key_count = self._recipe
1612 if len(start_keys) > 5:
1613 start_keys_repr = repr(list(start_keys)[:5])[:-1] + ', ...]'
1614 else:
1615 start_keys_repr = repr(start_keys)
1616 if len(exclude_keys) > 5:
1617 exclude_keys_repr = repr(list(exclude_keys)[:5])[:-1] + ', ...]'
1618 else:
1619 exclude_keys_repr = repr(exclude_keys)
1620 return '<%s %s:(%s, %s, %d)>' % (self.__class__.__name__,
1621 kind, start_keys_repr, exclude_keys_repr, key_count)
1622
1560 def get_recipe(self):1623 def get_recipe(self):
1561 """Return a recipe that can be used to replay this search.1624 """Return a recipe that can be used to replay this search.
15621625
@@ -1580,6 +1643,12 @@
1580 """1643 """
1581 return self._recipe1644 return self._recipe
15821645
1646 def get_network_struct(self):
1647 start_keys = ' '.join(self._recipe[1])
1648 stop_keys = ' '.join(self._recipe[2])
1649 count = str(self._recipe[3])
1650 return (self._recipe[0], '\n'.join((start_keys, stop_keys, count)))
1651
1583 def get_keys(self):1652 def get_keys(self):
1584 """Return the keys found in this search.1653 """Return the keys found in this search.
15851654
@@ -1617,7 +1686,7 @@
1617 return SearchResult(pending_refs, exclude, count, keys)1686 return SearchResult(pending_refs, exclude, count, keys)
16181687
16191688
1620class PendingAncestryResult(object):1689class PendingAncestryResult(AbstractSearchResult):
1621 """A search result that will reconstruct the ancestry for some graph heads.1690 """A search result that will reconstruct the ancestry for some graph heads.
16221691
1623 Unlike SearchResult, this doesn't hold the complete search result in1692 Unlike SearchResult, this doesn't hold the complete search result in
@@ -1634,6 +1703,14 @@
1634 self.heads = frozenset(heads)1703 self.heads = frozenset(heads)
1635 self.repo = repo1704 self.repo = repo
16361705
1706 def __repr__(self):
1707 if len(self.heads) > 5:
1708 heads_repr = repr(list(self.heads)[:5] + ', ...]')
1709 else:
1710 heads_repr = repr(self.heads)
1711 return '<%s heads:%s repo:%r>' % (
1712 self.__class__.__name__, heads_repr, self.repo)
1713
1637 def get_recipe(self):1714 def get_recipe(self):
1638 """Return a recipe that can be used to replay this search.1715 """Return a recipe that can be used to replay this search.
16391716
@@ -1647,6 +1724,11 @@
1647 """1724 """
1648 return ('proxy-search', self.heads, set(), -1)1725 return ('proxy-search', self.heads, set(), -1)
16491726
1727 def get_network_struct(self):
1728 parts = ['ancestry-of']
1729 parts.extend(self.heads)
1730 return parts
1731
1650 def get_keys(self):1732 def get_keys(self):
1651 """See SearchResult.get_keys.1733 """See SearchResult.get_keys.
16521734
@@ -1679,6 +1761,104 @@
1679 return PendingAncestryResult(referenced - seen, self.repo)1761 return PendingAncestryResult(referenced - seen, self.repo)
16801762
16811763
1764class EmptySearchResult(AbstractSearchResult):
1765 """An empty search result."""
1766
1767 def is_empty(self):
1768 return True
1769
1770
1771class EverythingResult(AbstractSearchResult):
1772 """A search result that simply requests everything in the repository."""
1773
1774 def __init__(self, repo):
1775 self._repo = repo
1776
1777 def __repr__(self):
1778 return '%s(%r)' % (self.__class__.__name__, self._repo)
1779
1780 def get_recipe(self):
1781 raise NotImplementedError(self.get_recipe)
1782
1783 def get_network_struct(self):
1784 return ('everything',)
1785
1786 def get_keys(self):
1787 if 'evil' in debug.debug_flags:
1788 from bzrlib import remote
1789 if isinstance(self._repo, remote.RemoteRepository):
1790 # warn developers (not users) not to do this
1791 trace.mutter_callsite(
1792 2, "EverythingResult(RemoteRepository).get_keys() is slow.")
1793 return self._repo.all_revision_ids()
1794
1795 def is_empty(self):
1796 # It's ok for this to wrongly return False: the worst that can happen
1797 # is that RemoteStreamSource will initiate a get_stream on an empty
1798 # repository. And almost all repositories are non-empty.
1799 return False
1800
1801 def refine(self, seen, referenced):
1802 heads = set(self._repo.all_revision_ids())
1803 heads.difference_update(seen)
1804 heads.update(referenced)
1805 return PendingAncestryResult(heads, self._repo)
1806
1807
1808class EverythingNotInOther(AbstractSearch):
1809 """Find all revisions in that are in one repo but not the other."""
1810
1811 def __init__(self, to_repo, from_repo, find_ghosts=False):
1812 self.to_repo = to_repo
1813 self.from_repo = from_repo
1814 self.find_ghosts = find_ghosts
1815
1816 def get_search_result(self):
1817 return self.to_repo.search_missing_revision_ids(
1818 self.from_repo, find_ghosts=self.find_ghosts)
1819
1820
1821class NotInOtherForRevs(AbstractSearch):
1822 """Find all revisions missing in one repo for a some specific heads."""
1823
1824 def __init__(self, to_repo, from_repo, required_ids, if_present_ids=None,
1825 find_ghosts=False):
1826 """Constructor.
1827
1828 :param required_ids: revision IDs of heads that must be found, or else
1829 the search will fail with NoSuchRevision. All revisions in their
1830 ancestry not already in the other repository will be included in
1831 the search result.
1832 :param if_present_ids: revision IDs of heads that may be absent in the
1833 source repository. If present, then their ancestry not already
1834 found in other will be included in the search result.
1835 """
1836 self.to_repo = to_repo
1837 self.from_repo = from_repo
1838 self.find_ghosts = find_ghosts
1839 self.required_ids = required_ids
1840 self.if_present_ids = if_present_ids
1841
1842 def __repr__(self):
1843 if len(self.required_ids) > 5:
1844 reqd_revs_repr = repr(list(self.required_ids)[:5])[:-1] + ', ...]'
1845 else:
1846 reqd_revs_repr = repr(self.required_ids)
1847 if self.if_present_ids and len(self.if_present_ids) > 5:
1848 ifp_revs_repr = repr(list(self.if_present_ids)[:5])[:-1] + ', ...]'
1849 else:
1850 ifp_revs_repr = repr(self.if_present_ids)
1851
1852 return "<%s from:%r to:%r find_ghosts:%r req'd:%r if-present:%r>" % (
1853 self.__class__.__name__, self.from_repo, self.to_repo,
1854 self.find_ghosts, reqd_revs_repr, ifp_revs_repr)
1855
1856 def get_search_result(self):
1857 return self.to_repo.search_missing_revision_ids(
1858 self.from_repo, revision_ids=self.required_ids,
1859 if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts)
1860
1861
1682def collapse_linear_regions(parent_map):1862def collapse_linear_regions(parent_map):
1683 """Collapse regions of the graph that are 'linear'.1863 """Collapse regions of the graph that are 'linear'.
16841864
16851865
=== modified file 'bzrlib/plugin.py'
--- bzrlib/plugin.py 2010-06-11 08:02:42 +0000
+++ bzrlib/plugin.py 2011-01-19 00:31:58 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2010 Canonical Ltd1# Copyright (C) 2005-2011 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -63,6 +63,11 @@
63_plugins_disabled = False63_plugins_disabled = False
6464
6565
66plugin_warnings = {}
67# Map from plugin name, to list of string warnings about eg plugin
68# dependencies.
69
70
66def are_plugins_disabled():71def are_plugins_disabled():
67 return _plugins_disabled72 return _plugins_disabled
6873
@@ -77,6 +82,40 @@
77 load_plugins([])82 load_plugins([])
7883
7984
85def describe_loaded_plugins(show_paths=False):
86 """Generate text description of loaded plugins.
87
88 :param show_paths: If true,
89 :returns: Iterator of text lines (including newlines.)
90 """
91 from inspect import getdoc
92 unreported_warnings = plugin_warnings.copy()
93 for name, plugin in sorted(plugins().items()):
94 version = plugin.__version__
95 if version == 'unknown':
96 version = ''
97 name_ver = '%s %s' % (name, version)
98 d = getdoc(plugin.module)
99 if d:
100 doc = d.split('\n')[0]
101 else:
102 doc = '(no description)'
103 yield ("%s\n" % name_ver)
104 yield (" %s\n" % doc)
105 if show_paths:
106 yield (" %s\n" % plugin.path())
107 if name in unreported_warnings:
108 for line in unreported_warnings[name]:
109 yield " ** " + line + '\n'
110 del unreported_warnings[name]
111 yield ("\n")
112 for name in sorted(unreported_warnings.keys()):
113 yield "%s (failed to load)\n" % name
114 for line in unreported_warnings[name]:
115 yield " ** " + line + '\n'
116 yield '\n'
117
118
80def _strip_trailing_sep(path):119def _strip_trailing_sep(path):
81 return path.rstrip("\\/")120 return path.rstrip("\\/")
82121
@@ -327,6 +366,11 @@
327 return None, None, (None, None, None)366 return None, None, (None, None, None)
328367
329368
369def record_plugin_warning(plugin_name, warning_message):
370 trace.mutter(warning_message)
371 plugin_warnings.setdefault(plugin_name, []).append(warning_message)
372
373
330def _load_plugin_module(name, dir):374def _load_plugin_module(name, dir):
331 """Load plugin name from dir.375 """Load plugin name from dir.
332376
@@ -340,10 +384,12 @@
340 except KeyboardInterrupt:384 except KeyboardInterrupt:
341 raise385 raise
342 except errors.IncompatibleAPI, e:386 except errors.IncompatibleAPI, e:
343 trace.warning("Unable to load plugin %r. It requested API version "387 warning_message = (
388 "Unable to load plugin %r. It requested API version "
344 "%s of module %s but the minimum exported version is %s, and "389 "%s of module %s but the minimum exported version is %s, and "
345 "the maximum is %s" %390 "the maximum is %s" %
346 (name, e.wanted, e.api, e.minimum, e.current))391 (name, e.wanted, e.api, e.minimum, e.current))
392 record_plugin_warning(name, warning_message)
347 except Exception, e:393 except Exception, e:
348 trace.warning("%s" % e)394 trace.warning("%s" % e)
349 if re.search('\.|-| ', name):395 if re.search('\.|-| ', name):
@@ -354,7 +400,9 @@
354 "file path isn't a valid module name; try renaming "400 "file path isn't a valid module name; try renaming "
355 "it to %r." % (name, dir, sanitised_name))401 "it to %r." % (name, dir, sanitised_name))
356 else:402 else:
357 trace.warning('Unable to load plugin %r from %r' % (name, dir))403 record_plugin_warning(
404 name,
405 'Unable to load plugin %r from %r' % (name, dir))
358 trace.log_exception_quietly()406 trace.log_exception_quietly()
359 if 'error' in debug.debug_flags:407 if 'error' in debug.debug_flags:
360 trace.print_exception(sys.exc_info(), sys.stderr)408 trace.print_exception(sys.exc_info(), sys.stderr)
361409
=== modified file 'bzrlib/plugins/launchpad/lp_propose.py'
--- bzrlib/plugins/launchpad/lp_propose.py 2010-12-02 10:41:05 +0000
+++ bzrlib/plugins/launchpad/lp_propose.py 2011-01-19 00:31:58 +0000
@@ -14,21 +14,22 @@
14# along with this program; if not, write to the Free Software14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616
17
18import webbrowser
19
20from bzrlib import (17from bzrlib import (
21 errors,18 errors,
22 hooks,19 hooks,
20 )
21from bzrlib.lazy_import import lazy_import
22lazy_import(globals(), """
23import webbrowser
24
25from bzrlib import (
23 msgeditor,26 msgeditor,
24)27)
25from bzrlib.plugins.launchpad import (28from bzrlib.plugins.launchpad import (
26 lp_api,29 lp_api,
27 lp_registration,30 lp_registration,
28)31)
29from bzrlib.plugins.launchpad.lp_api import canonical_url32""")
30
31from lazr.restfulclient import errors as restful_errors
3233
3334
34class ProposeMergeHooks(hooks.Hooks):35class ProposeMergeHooks(hooks.Hooks):
@@ -153,7 +154,7 @@
153 if mp.target_branch.self_link == self.target_branch.lp.self_link:154 if mp.target_branch.self_link == self.target_branch.lp.self_link:
154 raise errors.BzrCommandError(155 raise errors.BzrCommandError(
155 'There is already a branch merge proposal: %s' %156 'There is already a branch merge proposal: %s' %
156 canonical_url(mp))157 lp_api.canonical_url(mp))
157158
158 def _get_prerequisite_branch(self):159 def _get_prerequisite_branch(self):
159 hooks = self.hooks['get_prerequisite']160 hooks = self.hooks['get_prerequisite']
@@ -174,6 +175,7 @@
174 :param **kwargs: **kwargs for the call.175 :param **kwargs: **kwargs for the call.
175 :return: The result of calling call(*args, *kwargs).176 :return: The result of calling call(*args, *kwargs).
176 """177 """
178 from lazr.restfulclient import errors as restful_errors
177 try:179 try:
178 return call(*args, **kwargs)180 return call(*args, **kwargs)
179 except restful_errors.HTTPError, e:181 except restful_errors.HTTPError, e:
@@ -208,7 +210,7 @@
208 review_types=review_types)210 review_types=review_types)
209 if self.approve:211 if self.approve:
210 self.call_webservice(mp.setStatus, status='Approved')212 self.call_webservice(mp.setStatus, status='Approved')
211 webbrowser.open(canonical_url(mp))213 webbrowser.open(lp_api.canonical_url(mp))
212214
213215
214def modified_files(old_tree, new_tree):216def modified_files(old_tree, new_tree):
215217
=== modified file 'bzrlib/remote.py'
--- bzrlib/remote.py 2011-01-10 22:20:12 +0000
+++ bzrlib/remote.py 2011-01-19 00:31:58 +0000
@@ -1348,15 +1348,29 @@
1348 return result1348 return result
13491349
1350 @needs_read_lock1350 @needs_read_lock
1351 def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):1351 def search_missing_revision_ids(self, other,
1352 revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1353 find_ghosts=True, revision_ids=None, if_present_ids=None):
1352 """Return the revision ids that other has that this does not.1354 """Return the revision ids that other has that this does not.
13531355
1354 These are returned in topological order.1356 These are returned in topological order.
13551357
1356 revision_id: only return revision ids included by revision_id.1358 revision_id: only return revision ids included by revision_id.
1357 """1359 """
1358 return repository.InterRepository.get(1360 if symbol_versioning.deprecated_passed(revision_id):
1359 other, self).search_missing_revision_ids(revision_id, find_ghosts)1361 symbol_versioning.warn(
1362 'search_missing_revision_ids(revision_id=...) was '
1363 'deprecated in 2.3. Use revision_ids=[...] instead.',
1364 DeprecationWarning, stacklevel=2)
1365 if revision_ids is not None:
1366 raise AssertionError(
1367 'revision_ids is mutually exclusive with revision_id')
1368 if revision_id is not None:
1369 revision_ids = [revision_id]
1370 inter_repo = repository.InterRepository.get(other, self)
1371 return inter_repo.search_missing_revision_ids(
1372 find_ghosts=find_ghosts, revision_ids=revision_ids,
1373 if_present_ids=if_present_ids)
13601374
1361 def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,1375 def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1362 fetch_spec=None):1376 fetch_spec=None):
@@ -1763,12 +1777,7 @@
1763 return '\n'.join((start_keys, stop_keys, count))1777 return '\n'.join((start_keys, stop_keys, count))
17641778
1765 def _serialise_search_result(self, search_result):1779 def _serialise_search_result(self, search_result):
1766 if isinstance(search_result, graph.PendingAncestryResult):1780 parts = search_result.get_network_struct()
1767 parts = ['ancestry-of']
1768 parts.extend(search_result.heads)
1769 else:
1770 recipe = search_result.get_recipe()
1771 parts = [recipe[0], self._serialise_search_recipe(recipe)]
1772 return '\n'.join(parts)1781 return '\n'.join(parts)
17731782
1774 def autopack(self):1783 def autopack(self):
@@ -1968,6 +1977,7 @@
1968 candidate_verbs = [1977 candidate_verbs = [
1969 ('Repository.get_stream_1.19', (1, 19)),1978 ('Repository.get_stream_1.19', (1, 19)),
1970 ('Repository.get_stream', (1, 13))]1979 ('Repository.get_stream', (1, 13))]
1980
1971 found_verb = False1981 found_verb = False
1972 for verb, version in candidate_verbs:1982 for verb, version in candidate_verbs:
1973 if medium._is_remote_before(version):1983 if medium._is_remote_before(version):
@@ -1977,6 +1987,17 @@
1977 verb, args, search_bytes)1987 verb, args, search_bytes)
1978 except errors.UnknownSmartMethod:1988 except errors.UnknownSmartMethod:
1979 medium._remember_remote_is_before(version)1989 medium._remember_remote_is_before(version)
1990 except errors.UnknownErrorFromSmartServer, e:
1991 if isinstance(search, graph.EverythingResult):
1992 error_verb = e.error_from_smart_server.error_verb
1993 if error_verb == 'BadSearch':
1994 # Pre-2.3 servers don't support this sort of search.
1995 # XXX: perhaps falling back to VFS on BadSearch is a
1996 # good idea in general? It might provide a little bit
1997 # of protection against client-side bugs.
1998 medium._remember_remote_is_before((2, 3))
1999 break
2000 raise
1980 else:2001 else:
1981 response_tuple, response_handler = response2002 response_tuple, response_handler = response
1982 found_verb = True2003 found_verb = True
19832004
=== modified file 'bzrlib/repofmt/knitrepo.py'
--- bzrlib/repofmt/knitrepo.py 2010-11-20 21:41:05 +0000
+++ bzrlib/repofmt/knitrepo.py 2011-01-19 00:31:58 +0000
@@ -43,6 +43,7 @@
43 RepositoryFormat,43 RepositoryFormat,
44 RootCommitBuilder,44 RootCommitBuilder,
45 )45 )
46from bzrlib import symbol_versioning
4647
4748
48class _KnitParentsProvider(object):49class _KnitParentsProvider(object):
@@ -534,16 +535,23 @@
534 return are_knits and InterRepository._same_model(source, target)535 return are_knits and InterRepository._same_model(source, target)
535536
536 @needs_read_lock537 @needs_read_lock
537 def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):538 def search_missing_revision_ids(self,
538 """See InterRepository.missing_revision_ids()."""539 revision_id=symbol_versioning.DEPRECATED_PARAMETER,
539 if revision_id is not None:540 find_ghosts=True, revision_ids=None, if_present_ids=None):
540 source_ids = self.source.get_ancestry(revision_id)541 """See InterRepository.search_missing_revision_ids()."""
541 if source_ids[0] is not None:542 if symbol_versioning.deprecated_passed(revision_id):
542 raise AssertionError()543 symbol_versioning.warn(
543 source_ids.pop(0)544 'search_missing_revision_ids(revision_id=...) was '
544 else:545 'deprecated in 2.3. Use revision_ids=[...] instead.',
545 source_ids = self.source.all_revision_ids()546 DeprecationWarning, stacklevel=2)
546 source_ids_set = set(source_ids)547 if revision_ids is not None:
548 raise AssertionError(
549 'revision_ids is mutually exclusive with revision_id')
550 if revision_id is not None:
551 revision_ids = [revision_id]
552 del revision_id
553 source_ids_set = self._present_source_revisions_for(
554 revision_ids, if_present_ids)
547 # source_ids is the worst possible case we may need to pull.555 # source_ids is the worst possible case we may need to pull.
548 # now we want to filter source_ids against what we actually556 # now we want to filter source_ids against what we actually
549 # have in target, but don't try to check for existence where we know557 # have in target, but don't try to check for existence where we know
@@ -553,7 +561,7 @@
553 actually_present_revisions = set(561 actually_present_revisions = set(
554 self.target._eliminate_revisions_not_present(possibly_present_revisions))562 self.target._eliminate_revisions_not_present(possibly_present_revisions))
555 required_revisions = source_ids_set.difference(actually_present_revisions)563 required_revisions = source_ids_set.difference(actually_present_revisions)
556 if revision_id is not None:564 if revision_ids is not None:
557 # we used get_ancestry to determine source_ids then we are assured all565 # we used get_ancestry to determine source_ids then we are assured all
558 # revisions referenced are present as they are installed in topological order.566 # revisions referenced are present as they are installed in topological order.
559 # and the tip revision was validated by get_ancestry.567 # and the tip revision was validated by get_ancestry.
560568
=== modified file 'bzrlib/repofmt/weaverepo.py'
--- bzrlib/repofmt/weaverepo.py 2011-01-13 00:41:19 +0000
+++ bzrlib/repofmt/weaverepo.py 2011-01-19 00:31:58 +0000
@@ -40,6 +40,7 @@
40 lockable_files,40 lockable_files,
41 lockdir,41 lockdir,
42 osutils,42 osutils,
43 symbol_versioning,
43 trace,44 trace,
44 tuned_gzip,45 tuned_gzip,
45 urlutils,46 urlutils,
@@ -488,6 +489,7 @@
488 _versionedfile_class = weave.WeaveFile489 _versionedfile_class = weave.WeaveFile
489 supports_ghosts = False490 supports_ghosts = False
490 supports_chks = False491 supports_chks = False
492 supports_funky_characters = False
491493
492 _fetch_order = 'topological'494 _fetch_order = 'topological'
493 _fetch_reconcile = True495 _fetch_reconcile = True
@@ -806,8 +808,10 @@
806 self.target.fetch(self.source, revision_id=revision_id)808 self.target.fetch(self.source, revision_id=revision_id)
807809
808 @needs_read_lock810 @needs_read_lock
809 def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):811 def search_missing_revision_ids(self,
810 """See InterRepository.missing_revision_ids()."""812 revision_id=symbol_versioning.DEPRECATED_PARAMETER,
813 find_ghosts=True, revision_ids=None, if_present_ids=None):
814 """See InterRepository.search_missing_revision_ids()."""
811 # we want all revisions to satisfy revision_id in source.815 # we want all revisions to satisfy revision_id in source.
812 # but we don't want to stat every file here and there.816 # but we don't want to stat every file here and there.
813 # we want then, all revisions other needs to satisfy revision_id817 # we want then, all revisions other needs to satisfy revision_id
@@ -819,14 +823,19 @@
819 # disk format scales terribly for push anyway due to rewriting823 # disk format scales terribly for push anyway due to rewriting
820 # inventory.weave, this is considered acceptable.824 # inventory.weave, this is considered acceptable.
821 # - RBC 20060209825 # - RBC 20060209
822 if revision_id is not None:826 if symbol_versioning.deprecated_passed(revision_id):
823 source_ids = self.source.get_ancestry(revision_id)827 symbol_versioning.warn(
824 if source_ids[0] is not None:828 'search_missing_revision_ids(revision_id=...) was '
825 raise AssertionError()829 'deprecated in 2.3. Use revision_ids=[...] instead.',
826 source_ids.pop(0)830 DeprecationWarning, stacklevel=2)
827 else:831 if revision_ids is not None:
828 source_ids = self.source._all_possible_ids()832 raise AssertionError(
829 source_ids_set = set(source_ids)833 'revision_ids is mutually exclusive with revision_id')
834 if revision_id is not None:
835 revision_ids = [revision_id]
836 del revision_id
837 source_ids_set = self._present_source_revisions_for(
838 revision_ids, if_present_ids)
830 # source_ids is the worst possible case we may need to pull.839 # source_ids is the worst possible case we may need to pull.
831 # now we want to filter source_ids against what we actually840 # now we want to filter source_ids against what we actually
832 # have in target, but don't try to check for existence where we know841 # have in target, but don't try to check for existence where we know
@@ -836,7 +845,7 @@
836 actually_present_revisions = set(845 actually_present_revisions = set(
837 self.target._eliminate_revisions_not_present(possibly_present_revisions))846 self.target._eliminate_revisions_not_present(possibly_present_revisions))
838 required_revisions = source_ids_set.difference(actually_present_revisions)847 required_revisions = source_ids_set.difference(actually_present_revisions)
839 if revision_id is not None:848 if revision_ids is not None:
840 # we used get_ancestry to determine source_ids then we are assured all849 # we used get_ancestry to determine source_ids then we are assured all
841 # revisions referenced are present as they are installed in topological order.850 # revisions referenced are present as they are installed in topological order.
842 # and the tip revision was validated by get_ancestry.851 # and the tip revision was validated by get_ancestry.
843852
=== modified file 'bzrlib/repository.py'
--- bzrlib/repository.py 2011-01-13 00:41:19 +0000
+++ bzrlib/repository.py 2011-01-19 00:31:58 +0000
@@ -42,7 +42,6 @@
42 pyutils,42 pyutils,
43 revision as _mod_revision,43 revision as _mod_revision,
44 static_tuple,44 static_tuple,
45 symbol_versioning,
46 trace,45 trace,
47 tsort,46 tsort,
48 versionedfile,47 versionedfile,
@@ -57,6 +56,7 @@
57from bzrlib import (56from bzrlib import (
58 errors,57 errors,
59 registry,58 registry,
59 symbol_versioning,
60 ui,60 ui,
61 )61 )
62from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises62from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
@@ -1597,15 +1597,28 @@
1597 return ret1597 return ret
15981598
1599 @needs_read_lock1599 @needs_read_lock
1600 def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):1600 def search_missing_revision_ids(self, other,
1601 revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1602 find_ghosts=True, revision_ids=None, if_present_ids=None):
1601 """Return the revision ids that other has that this does not.1603 """Return the revision ids that other has that this does not.
16021604
1603 These are returned in topological order.1605 These are returned in topological order.
16041606
1605 revision_id: only return revision ids included by revision_id.1607 revision_id: only return revision ids included by revision_id.
1606 """1608 """
1609 if symbol_versioning.deprecated_passed(revision_id):
1610 symbol_versioning.warn(
1611 'search_missing_revision_ids(revision_id=...) was '
1612 'deprecated in 2.3. Use revision_ids=[...] instead.',
1613 DeprecationWarning, stacklevel=3)
1614 if revision_ids is not None:
1615 raise AssertionError(
1616 'revision_ids is mutually exclusive with revision_id')
1617 if revision_id is not None:
1618 revision_ids = [revision_id]
1607 return InterRepository.get(other, self).search_missing_revision_ids(1619 return InterRepository.get(other, self).search_missing_revision_ids(
1608 revision_id, find_ghosts)1620 find_ghosts=find_ghosts, revision_ids=revision_ids,
1621 if_present_ids=if_present_ids)
16091622
1610 @staticmethod1623 @staticmethod
1611 def open(base):1624 def open(base):
@@ -3436,7 +3449,7 @@
3436 fetch_spec=fetch_spec,3449 fetch_spec=fetch_spec,
3437 find_ghosts=find_ghosts)3450 find_ghosts=find_ghosts)
34383451
3439 def _walk_to_common_revisions(self, revision_ids):3452 def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3440 """Walk out from revision_ids in source to revisions target has.3453 """Walk out from revision_ids in source to revisions target has.
34413454
3442 :param revision_ids: The start point for the search.3455 :param revision_ids: The start point for the search.
@@ -3444,10 +3457,14 @@
3444 """3457 """
3445 target_graph = self.target.get_graph()3458 target_graph = self.target.get_graph()
3446 revision_ids = frozenset(revision_ids)3459 revision_ids = frozenset(revision_ids)
3460 if if_present_ids:
3461 all_wanted_revs = revision_ids.union(if_present_ids)
3462 else:
3463 all_wanted_revs = revision_ids
3447 missing_revs = set()3464 missing_revs = set()
3448 source_graph = self.source.get_graph()3465 source_graph = self.source.get_graph()
3449 # ensure we don't pay silly lookup costs.3466 # ensure we don't pay silly lookup costs.
3450 searcher = source_graph._make_breadth_first_searcher(revision_ids)3467 searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3451 null_set = frozenset([_mod_revision.NULL_REVISION])3468 null_set = frozenset([_mod_revision.NULL_REVISION])
3452 searcher_exhausted = False3469 searcher_exhausted = False
3453 while True:3470 while True:
@@ -3489,30 +3506,79 @@
3489 return searcher.get_result()3506 return searcher.get_result()
34903507
3491 @needs_read_lock3508 @needs_read_lock
3492 def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):3509 def search_missing_revision_ids(self,
3510 revision_id=symbol_versioning.DEPRECATED_PARAMETER,
3511 find_ghosts=True, revision_ids=None, if_present_ids=None):
3493 """Return the revision ids that source has that target does not.3512 """Return the revision ids that source has that target does not.
34943513
3495 :param revision_id: only return revision ids included by this3514 :param revision_id: only return revision ids included by this
3496 revision_id.3515 revision_id.
3516 :param revision_ids: return revision ids included by these
3517 revision_ids. NoSuchRevision will be raised if any of these
3518 revisions are not present.
3519 :param if_present_ids: like revision_ids, but will not cause
3520 NoSuchRevision if any of these are absent, instead they will simply
3521 not be in the result. This is useful for e.g. finding revisions
3522 to fetch for tags, which may reference absent revisions.
3497 :param find_ghosts: If True find missing revisions in deep history3523 :param find_ghosts: If True find missing revisions in deep history
3498 rather than just finding the surface difference.3524 rather than just finding the surface difference.
3499 :return: A bzrlib.graph.SearchResult.3525 :return: A bzrlib.graph.SearchResult.
3500 """3526 """
3527 if symbol_versioning.deprecated_passed(revision_id):
3528 symbol_versioning.warn(
3529 'search_missing_revision_ids(revision_id=...) was '
3530 'deprecated in 2.3. Use revision_ids=[...] instead.',
3531 DeprecationWarning, stacklevel=2)
3532 if revision_ids is not None:
3533 raise AssertionError(
3534 'revision_ids is mutually exclusive with revision_id')
3535 if revision_id is not None:
3536 revision_ids = [revision_id]
3537 del revision_id
3501 # stop searching at found target revisions.3538 # stop searching at found target revisions.
3502 if not find_ghosts and revision_id is not None:3539 if not find_ghosts and (revision_ids is not None or if_present_ids is
3503 return self._walk_to_common_revisions([revision_id])3540 not None):
3541 return self._walk_to_common_revisions(revision_ids,
3542 if_present_ids=if_present_ids)
3504 # generic, possibly worst case, slow code path.3543 # generic, possibly worst case, slow code path.
3505 target_ids = set(self.target.all_revision_ids())3544 target_ids = set(self.target.all_revision_ids())
3506 if revision_id is not None:3545 source_ids = self._present_source_revisions_for(
3507 source_ids = self.source.get_ancestry(revision_id)3546 revision_ids, if_present_ids)
3508 if source_ids[0] is not None:
3509 raise AssertionError()
3510 source_ids.pop(0)
3511 else:
3512 source_ids = self.source.all_revision_ids()
3513 result_set = set(source_ids).difference(target_ids)3547 result_set = set(source_ids).difference(target_ids)
3514 return self.source.revision_ids_to_search_result(result_set)3548 return self.source.revision_ids_to_search_result(result_set)
35153549
3550 def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
3551 """Returns set of all revisions in ancestry of revision_ids present in
3552 the source repo.
3553
3554 :param revision_ids: if None, all revisions in source are returned.
3555 :param if_present_ids: like revision_ids, but if any/all of these are
3556 absent no error is raised.
3557 """
3558 if revision_ids is not None or if_present_ids is not None:
3559 # First, ensure all specified revisions exist. Callers expect
3560 # NoSuchRevision when they pass absent revision_ids here.
3561 if revision_ids is None:
3562 revision_ids = set()
3563 if if_present_ids is None:
3564 if_present_ids = set()
3565 revision_ids = set(revision_ids)
3566 if_present_ids = set(if_present_ids)
3567 all_wanted_ids = revision_ids.union(if_present_ids)
3568 graph = self.source.get_graph()
3569 present_revs = set(graph.get_parent_map(all_wanted_ids))
3570 missing = revision_ids.difference(present_revs)
3571 if missing:
3572 raise errors.NoSuchRevision(self.source, missing.pop())
3573 found_ids = all_wanted_ids.intersection(present_revs)
3574 source_ids = [rev_id for (rev_id, parents) in
3575 graph.iter_ancestry(found_ids)
3576 if rev_id != _mod_revision.NULL_REVISION
3577 and parents is not None]
3578 else:
3579 source_ids = self.source.all_revision_ids()
3580 return set(source_ids)
3581
3516 @staticmethod3582 @staticmethod
3517 def _same_model(source, target):3583 def _same_model(source, target):
3518 """True if source and target have the same data representation.3584 """True if source and target have the same data representation.
@@ -3836,7 +3902,13 @@
3836 fetch_spec=None):3902 fetch_spec=None):
3837 """See InterRepository.fetch()."""3903 """See InterRepository.fetch()."""
3838 if fetch_spec is not None:3904 if fetch_spec is not None:
3839 raise AssertionError("Not implemented yet...")3905 if (isinstance(fetch_spec, graph.NotInOtherForRevs) and
3906 len(fetch_spec.required_ids) == 1 and not
3907 fetch_spec.if_present_ids):
3908 revision_id = list(fetch_spec.required_ids)[0]
3909 del fetch_spec
3910 else:
3911 raise AssertionError("Not implemented yet...")
3840 ui.ui_factory.warn_experimental_format_fetch(self)3912 ui.ui_factory.warn_experimental_format_fetch(self)
3841 if (not self.source.supports_rich_root()3913 if (not self.source.supports_rich_root()
3842 and self.target.supports_rich_root()):3914 and self.target.supports_rich_root()):
@@ -3849,8 +3921,12 @@
3849 ui.ui_factory.show_user_warning('cross_format_fetch',3921 ui.ui_factory.show_user_warning('cross_format_fetch',
3850 from_format=self.source._format,3922 from_format=self.source._format,
3851 to_format=self.target._format)3923 to_format=self.target._format)
3924 if revision_id:
3925 search_revision_ids = [revision_id]
3926 else:
3927 search_revision_ids = None
3852 revision_ids = self.target.search_missing_revision_ids(self.source,3928 revision_ids = self.target.search_missing_revision_ids(self.source,
3853 revision_id, find_ghosts=find_ghosts).get_keys()3929 revision_ids=search_revision_ids, find_ghosts=find_ghosts).get_keys()
3854 if not revision_ids:3930 if not revision_ids:
3855 return 0, 03931 return 0, 0
3856 revision_ids = tsort.topo_sort(3932 revision_ids = tsort.topo_sort(
38573933
=== modified file 'bzrlib/smart/repository.py'
--- bzrlib/smart/repository.py 2010-11-16 06:06:11 +0000
+++ bzrlib/smart/repository.py 2011-01-19 00:31:58 +0000
@@ -81,6 +81,8 @@
81 recreate_search trusts that clients will look for missing things81 recreate_search trusts that clients will look for missing things
82 they expected and get it from elsewhere.82 they expected and get it from elsewhere.
83 """83 """
84 if search_bytes == 'everything':
85 return graph.EverythingResult(repository), None
84 lines = search_bytes.split('\n')86 lines = search_bytes.split('\n')
85 if lines[0] == 'ancestry-of':87 if lines[0] == 'ancestry-of':
86 heads = lines[1:]88 heads = lines[1:]
@@ -412,6 +414,13 @@
412 def do_repository_request(self, repository, to_network_name):414 def do_repository_request(self, repository, to_network_name):
413 """Get a stream for inserting into a to_format repository.415 """Get a stream for inserting into a to_format repository.
414416
417 The request body is 'search_bytes', a description of the revisions
418 being requested.
419
420 In 2.3 this verb added support for search_bytes == 'everything'. Older
421 implementations will respond with a BadSearch error, and clients should
422 catch this and fallback appropriately.
423
415 :param repository: The repository to stream from.424 :param repository: The repository to stream from.
416 :param to_network_name: The network name of the format of the target425 :param to_network_name: The network name of the format of the target
417 repository.426 repository.
@@ -489,6 +498,13 @@
489498
490499
491class SmartServerRepositoryGetStream_1_19(SmartServerRepositoryGetStream):500class SmartServerRepositoryGetStream_1_19(SmartServerRepositoryGetStream):
501 """The same as Repository.get_stream, but will return stream CHK formats to
502 clients.
503
504 See SmartServerRepositoryGetStream._should_fake_unknown.
505
506 New in 1.19.
507 """
492508
493 def _should_fake_unknown(self):509 def _should_fake_unknown(self):
494 """Returns False; we don't need to workaround bugs in 1.19+ clients."""510 """Returns False; we don't need to workaround bugs in 1.19+ clients."""
495511
=== modified file 'bzrlib/smart/server.py'
--- bzrlib/smart/server.py 2010-07-01 15:25:41 +0000
+++ bzrlib/smart/server.py 2011-01-19 00:31:58 +0000
@@ -18,7 +18,6 @@
1818
19import errno19import errno
20import os.path20import os.path
21import select
22import socket21import socket
23import sys22import sys
24import threading23import threading
@@ -27,7 +26,6 @@
27from bzrlib import (26from bzrlib import (
28 errors,27 errors,
29 trace,28 trace,
30 transport,
31)29)
32from bzrlib.lazy_import import lazy_import30from bzrlib.lazy_import import lazy_import
33lazy_import(globals(), """31lazy_import(globals(), """
@@ -178,7 +176,7 @@
178176
179 def get_url(self):177 def get_url(self):
180 """Return the url of the server"""178 """Return the url of the server"""
181 return "bzr://%s:%d/" % self._sockname179 return "bzr://%s:%s/" % (self._sockname[0], self._sockname[1])
182180
183 def serve_conn(self, conn, thread_name_suffix):181 def serve_conn(self, conn, thread_name_suffix):
184 # For WIN32, where the timeout value from the listening socket182 # For WIN32, where the timeout value from the listening socket
185183
=== modified file 'bzrlib/tests/blackbox/test_serve.py'
--- bzrlib/tests/blackbox/test_serve.py 2011-01-10 22:20:12 +0000
+++ bzrlib/tests/blackbox/test_serve.py 2011-01-19 00:31:58 +0000
@@ -18,16 +18,12 @@
18"""Tests of the bzr serve command."""18"""Tests of the bzr serve command."""
1919
20import os20import os
21import os.path
22import signal21import signal
23import subprocess
24import sys
25import thread22import thread
26import threading23import threading
2724
28from bzrlib import (25from bzrlib import (
29 builtins,26 builtins,
30 debug,
31 errors,27 errors,
32 osutils,28 osutils,
33 revision as _mod_revision,29 revision as _mod_revision,
@@ -37,11 +33,13 @@
37from bzrlib.branch import Branch33from bzrlib.branch import Branch
38from bzrlib.bzrdir import BzrDir34from bzrlib.bzrdir import BzrDir
39from bzrlib.smart import client, medium35from bzrlib.smart import client, medium
40from bzrlib.smart.server import BzrServerFactory, SmartTCPServer36from bzrlib.smart.server import (
37 BzrServerFactory,
38 SmartTCPServer,
39 )
41from bzrlib.tests import (40from bzrlib.tests import (
42 TestCaseWithMemoryTransport,41 TestCaseWithMemoryTransport,
43 TestCaseWithTransport,42 TestCaseWithTransport,
44 TestSkipped,
45 )43 )
46from bzrlib.trace import mutter44from bzrlib.trace import mutter
47from bzrlib.transport import remote45from bzrlib.transport import remote
@@ -53,9 +51,9 @@
53 *func_args, **func_kwargs):51 *func_args, **func_kwargs):
54 """Run 'bzr serve', and run the given func in a thread once the server52 """Run 'bzr serve', and run the given func in a thread once the server
55 has started.53 has started.
56 54
57 When 'func' terminates, the server will be terminated too.55 When 'func' terminates, the server will be terminated too.
58 56
59 Returns stdout and stderr.57 Returns stdout and stderr.
60 """58 """
61 # install hook59 # install hook
@@ -164,7 +162,7 @@
164 url = 'bzr://localhost:%d/' % port162 url = 'bzr://localhost:%d/' % port
165 self.permit_url(url)163 self.permit_url(url)
166 return process, url164 return process, url
167 165
168 def test_bzr_serve_quiet(self):166 def test_bzr_serve_quiet(self):
169 self.make_branch('.')167 self.make_branch('.')
170 args = ['--port', 'localhost:0', '--quiet']168 args = ['--port', 'localhost:0', '--quiet']
@@ -334,4 +332,3 @@
334 self.assertEqual(base_url, self.bzr_serve_transport.base)332 self.assertEqual(base_url, self.bzr_serve_transport.base)
335 self.assertEqual(base_dir,333 self.assertEqual(base_dir,
336 server_maker.get_base_path(self.bzr_serve_transport))334 server_maker.get_base_path(self.bzr_serve_transport))
337
338335
=== modified file 'bzrlib/tests/per_branch/test_push.py'
--- bzrlib/tests/per_branch/test_push.py 2011-01-13 01:02:53 +0000
+++ bzrlib/tests/per_branch/test_push.py 2011-01-19 00:31:58 +0000
@@ -170,6 +170,21 @@
170 self.assertEqual(tree.branch.last_revision(),170 self.assertEqual(tree.branch.last_revision(),
171 to_branch.last_revision())171 to_branch.last_revision())
172172
173 def test_push_overwrite_with_older_mainline_rev(self):
174 """Pushing an older mainline revision with overwrite.
175
176 This was <https://bugs.launchpad.net/bzr/+bug/386576>.
177 """
178 source = self.make_branch_and_tree('source')
179 target = self.make_branch('target')
180
181 source.commit('1st commit')
182 source.commit('2nd commit', rev_id='rev-2')
183 source.commit('3rd commit')
184 source.branch.push(target)
185 source.branch.push(target, stop_revision='rev-2', overwrite=True)
186 self.assertEqual('rev-2', target.last_revision())
187
173 def test_push_overwrite_of_non_tip_with_stop_revision(self):188 def test_push_overwrite_of_non_tip_with_stop_revision(self):
174 """Combining the stop_revision and overwrite options works.189 """Combining the stop_revision and overwrite options works.
175190
176191
=== modified file 'bzrlib/tests/per_interrepository/test_interrepository.py'
--- bzrlib/tests/per_interrepository/test_interrepository.py 2011-01-13 00:41:19 +0000
+++ bzrlib/tests/per_interrepository/test_interrepository.py 2011-01-19 00:31:58 +0000
@@ -131,9 +131,15 @@
131 self.assertFalse(repo_b.has_revision('pizza'))131 self.assertFalse(repo_b.has_revision('pizza'))
132 # Asking specifically for an absent revision errors.132 # Asking specifically for an absent revision errors.
133 self.assertRaises(errors.NoSuchRevision,133 self.assertRaises(errors.NoSuchRevision,
134 repo_b.search_missing_revision_ids, repo_a, revision_id='pizza',134 repo_b.search_missing_revision_ids, repo_a, revision_ids=['pizza'],
135 find_ghosts=True)135 find_ghosts=True)
136 self.assertRaises(errors.NoSuchRevision,136 self.assertRaises(errors.NoSuchRevision,
137 repo_b.search_missing_revision_ids, repo_a, revision_ids=['pizza'],
138 find_ghosts=False)
139 self.callDeprecated(
140 ['search_missing_revision_ids(revision_id=...) was deprecated in '
141 '2.3. Use revision_ids=[...] instead.'],
142 self.assertRaises, errors.NoSuchRevision,
137 repo_b.search_missing_revision_ids, repo_a, revision_id='pizza',143 repo_b.search_missing_revision_ids, repo_a, revision_id='pizza',
138 find_ghosts=False)144 find_ghosts=False)
139145
@@ -143,7 +149,8 @@
143 # make a repository to compare against that is empty149 # make a repository to compare against that is empty
144 repo_b = self.make_to_repository('empty')150 repo_b = self.make_to_repository('empty')
145 repo_a = self.bzrdir.open_repository()151 repo_a = self.bzrdir.open_repository()
146 result = repo_b.search_missing_revision_ids(repo_a, revision_id='rev1')152 result = repo_b.search_missing_revision_ids(
153 repo_a, revision_ids=['rev1'])
147 self.assertEqual(set(['rev1']), result.get_keys())154 self.assertEqual(set(['rev1']), result.get_keys())
148 self.assertEqual(('search', set(['rev1']), set([NULL_REVISION]), 1),155 self.assertEqual(('search', set(['rev1']), set([NULL_REVISION]), 1),
149 result.get_recipe())156 result.get_recipe())
150157
=== modified file 'bzrlib/tests/per_repository_reference/test_fetch.py'
--- bzrlib/tests/per_repository_reference/test_fetch.py 2009-06-01 18:13:46 +0000
+++ bzrlib/tests/per_repository_reference/test_fetch.py 2011-01-19 00:31:58 +0000
@@ -18,6 +18,7 @@
18from bzrlib import (18from bzrlib import (
19 branch,19 branch,
20 errors,20 errors,
21 graph,
21 )22 )
22from bzrlib.smart import (23from bzrlib.smart import (
23 server,24 server,
@@ -25,7 +26,7 @@
25from bzrlib.tests.per_repository import TestCaseWithRepository26from bzrlib.tests.per_repository import TestCaseWithRepository
2627
2728
28class TestFetch(TestCaseWithRepository):29class TestFetchBase(TestCaseWithRepository):
2930
30 def make_source_branch(self):31 def make_source_branch(self):
31 # It would be nice if there was a way to force this to be memory-only32 # It would be nice if there was a way to force this to be memory-only
@@ -51,6 +52,9 @@
51 self.addCleanup(source_b.unlock)52 self.addCleanup(source_b.unlock)
52 return content, source_b53 return content, source_b
5354
55
56class TestFetch(TestFetchBase):
57
54 def test_sprout_from_stacked_with_short_history(self):58 def test_sprout_from_stacked_with_short_history(self):
55 content, source_b = self.make_source_branch()59 content, source_b = self.make_source_branch()
56 # Split the generated content into a base branch, and a stacked branch60 # Split the generated content into a base branch, and a stacked branch
@@ -149,3 +153,38 @@
149 source_b.lock_read()153 source_b.lock_read()
150 self.addCleanup(source_b.unlock)154 self.addCleanup(source_b.unlock)
151 stacked.pull(source_b, stop_revision='B-id')155 stacked.pull(source_b, stop_revision='B-id')
156
157
158class TestFetchFromRepoWithUnconfiguredFallbacks(TestFetchBase):
159
160 def make_stacked_source_repo(self):
161 _, source_b = self.make_source_branch()
162 # Use 'make_branch' which gives us a bzr:// branch when appropriate,
163 # rather than creating a branch-on-disk
164 stack_b = self.make_branch('stack-on')
165 stack_b.pull(source_b, stop_revision='B-id')
166 stacked_b = self.make_branch('stacked')
167 stacked_b.set_stacked_on_url('../stack-on')
168 stacked_b.pull(source_b, stop_revision='C-id')
169 return stacked_b.repository
170
171 def test_fetch_everything_includes_parent_invs(self):
172 stacked = self.make_stacked_source_repo()
173 repo_missing_fallbacks = stacked.bzrdir.open_repository()
174 self.addCleanup(repo_missing_fallbacks.lock_read().unlock)
175 target = self.make_repository('target')
176 self.addCleanup(target.lock_write().unlock)
177 target.fetch(
178 repo_missing_fallbacks,
179 fetch_spec=graph.EverythingResult(repo_missing_fallbacks))
180 self.assertEqual(repo_missing_fallbacks.revisions.keys(),
181 target.revisions.keys())
182 self.assertEqual(repo_missing_fallbacks.inventories.keys(),
183 target.inventories.keys())
184 self.assertEqual(['C-id'],
185 sorted(k[-1] for k in target.revisions.keys()))
186 self.assertEqual(['B-id', 'C-id'],
187 sorted(k[-1] for k in target.inventories.keys()))
188
189
190
152191
=== modified file 'bzrlib/tests/test_crash.py'
--- bzrlib/tests/test_crash.py 2011-01-12 01:01:53 +0000
+++ bzrlib/tests/test_crash.py 2011-01-19 00:31:58 +0000
@@ -26,6 +26,7 @@
26 config,26 config,
27 crash,27 crash,
28 osutils,28 osutils,
29 plugin,
29 tests,30 tests,
30 )31 )
3132
@@ -42,6 +43,11 @@
42 self.overrideEnv('APPORT_CRASH_DIR', crash_dir)43 self.overrideEnv('APPORT_CRASH_DIR', crash_dir)
43 self.assertEquals(crash_dir, config.crash_dir())44 self.assertEquals(crash_dir, config.crash_dir())
4445
46 self.overrideAttr(
47 plugin,
48 'plugin_warnings',
49 {'example': ['Failed to load plugin foo']})
50
45 stderr = StringIO()51 stderr = StringIO()
4652
47 try:53 try:
@@ -71,3 +77,6 @@
71 self.assertContainsRe(report, 'test_apport_report')77 self.assertContainsRe(report, 'test_apport_report')
72 # should also be in there78 # should also be in there
73 self.assertContainsRe(report, '(?m)^CommandLine:')79 self.assertContainsRe(report, '(?m)^CommandLine:')
80 self.assertContainsRe(
81 report,
82 'Failed to load plugin foo')
7483
=== modified file 'bzrlib/tests/test_plugins.py'
--- bzrlib/tests/test_plugins.py 2011-01-10 22:20:12 +0000
+++ bzrlib/tests/test_plugins.py 2011-01-19 00:31:58 +0000
@@ -267,8 +267,14 @@
267 stream.close()267 stream.close()
268268
269 def test_plugin_with_bad_api_version_reports(self):269 def test_plugin_with_bad_api_version_reports(self):
270 # This plugin asks for bzrlib api version 1.0.0, which is not supported270 """Try loading a plugin that requests an unsupported api.
271 # anymore.271
272 Up to bzr 2.2, the plugin just didn't load. But now we prefer to let
273 it load, and record a warning that can be shown in error messages.
274
275 See https://bugs.launchpad.net/bzr/+bug/704195
276 """
277 self.overrideAttr(plugin, 'plugin_warnings', {})
272 name = 'wants100.py'278 name = 'wants100.py'
273 f = file(name, 'w')279 f = file(name, 'w')
274 try:280 try:
@@ -276,9 +282,14 @@
276 "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")282 "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
277 finally:283 finally:
278 f.close()284 f.close()
279
280 log = self.load_and_capture(name)285 log = self.load_and_capture(name)
281 self.assertContainsRe(log,286 self.assertNotContainsRe(log,
287 r"It requested API version")
288 self.assertEquals(
289 ['wants100'],
290 plugin.plugin_warnings.keys())
291 self.assertContainsRe(
292 plugin.plugin_warnings['wants100'][0],
282 r"It requested API version")293 r"It requested API version")
283294
284 def test_plugin_with_bad_name_does_not_load(self):295 def test_plugin_with_bad_name_does_not_load(self):
@@ -847,6 +858,9 @@
847 self.create_plugin_package('test_foo', dir='standard/test_foo')858 self.create_plugin_package('test_foo', dir='standard/test_foo')
848 # All the tests will load the 'test_foo' plugin from various locations859 # All the tests will load the 'test_foo' plugin from various locations
849 self.addCleanup(self._unregister_plugin, 'test_foo')860 self.addCleanup(self._unregister_plugin, 'test_foo')
861 # Unfortunately there's global cached state for the specific
862 # registered paths.
863 self.addCleanup(plugin.PluginImporter.reset)
850864
851 def assertTestFooLoadedFrom(self, path):865 def assertTestFooLoadedFrom(self, path):
852 self.assertPluginKnown('test_foo')866 self.assertPluginKnown('test_foo')
853867
=== modified file 'bzrlib/tests/test_remote.py'
--- bzrlib/tests/test_remote.py 2011-01-12 01:01:53 +0000
+++ bzrlib/tests/test_remote.py 2011-01-19 00:31:58 +0000
@@ -57,9 +57,12 @@
57 )57 )
58from bzrlib.repofmt import groupcompress_repo, pack_repo58from bzrlib.repofmt import groupcompress_repo, pack_repo
59from bzrlib.revision import NULL_REVISION59from bzrlib.revision import NULL_REVISION
60from bzrlib.smart import medium60from bzrlib.smart import medium, request
61from bzrlib.smart.client import _SmartClient61from bzrlib.smart.client import _SmartClient
62from bzrlib.smart.repository import SmartServerRepositoryGetParentMap62from bzrlib.smart.repository import (
63 SmartServerRepositoryGetParentMap,
64 SmartServerRepositoryGetStream_1_19,
65 )
63from bzrlib.tests import (66from bzrlib.tests import (
64 test_server,67 test_server,
65 )68 )
@@ -3180,11 +3183,63 @@
31803183
3181 def test_copy_content_into_avoids_revision_history(self):3184 def test_copy_content_into_avoids_revision_history(self):
3182 local = self.make_branch('local')3185 local = self.make_branch('local')
3183 remote_backing_tree = self.make_branch_and_tree('remote')3186 builder = self.make_branch_builder('remote')
3184 remote_backing_tree.commit("Commit.")3187 builder.build_commit(message="Commit.")
3185 remote_branch_url = self.smart_server.get_url() + 'remote'3188 remote_branch_url = self.smart_server.get_url() + 'remote'
3186 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()3189 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3187 local.repository.fetch(remote_branch.repository)3190 local.repository.fetch(remote_branch.repository)
3188 self.hpss_calls = []3191 self.hpss_calls = []
3189 remote_branch.copy_content_into(local)3192 remote_branch.copy_content_into(local)
3190 self.assertFalse('Branch.revision_history' in self.hpss_calls)3193 self.assertFalse('Branch.revision_history' in self.hpss_calls)
3194
3195 def test_fetch_everything_needs_just_one_call(self):
3196 local = self.make_branch('local')
3197 builder = self.make_branch_builder('remote')
3198 builder.build_commit(message="Commit.")
3199 remote_branch_url = self.smart_server.get_url() + 'remote'
3200 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3201 self.hpss_calls = []
3202 local.repository.fetch(remote_branch.repository,
3203 fetch_spec=graph.EverythingResult(remote_branch.repository))
3204 self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3205
3206 def override_verb(self, verb_name, verb):
3207 request_handlers = request.request_handlers
3208 orig_verb = request_handlers.get(verb_name)
3209 request_handlers.register(verb_name, verb, override_existing=True)
3210 self.addCleanup(request_handlers.register, verb_name, orig_verb,
3211 override_existing=True)
3212
3213 def test_fetch_everything_backwards_compat(self):
3214 """Can fetch with EverythingResult even with pre 2.3 servers.
3215
3216 Pre-2.3 do not support 'everything' searches with the
3217 Repository.get_stream_1.19 verb.
3218 """
3219 verb_log = []
3220 class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3221 """A version of the Repository.get_stream_1.19 verb patched to
3222 reject 'everything' searches the way 2.2 and earlier do.
3223 """
3224 def recreate_search(self, repository, search_bytes, discard_excess=False):
3225 verb_log.append(search_bytes.split('\n', 1)[0])
3226 if search_bytes == 'everything':
3227 return (None, request.FailedSmartServerResponse(('BadSearch',)))
3228 return super(OldGetStreamVerb,
3229 self).recreate_search(repository, search_bytes,
3230 discard_excess=discard_excess)
3231 self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3232 local = self.make_branch('local')
3233 builder = self.make_branch_builder('remote')
3234 builder.build_commit(message="Commit.")
3235 remote_branch_url = self.smart_server.get_url() + 'remote'
3236 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3237 self.hpss_calls = []
3238 local.repository.fetch(remote_branch.repository,
3239 fetch_spec=graph.EverythingResult(remote_branch.repository))
3240 # make sure the overridden verb was used
3241 self.assertLength(1, verb_log)
3242 # more than one HPSS call is needed, but because it's a VFS callback
3243 # its hard to predict exactly how many.
3244 self.assertTrue(len(self.hpss_calls) > 1)
3245
31913246
=== modified file 'bzrlib/tests/test_repository.py'
--- bzrlib/tests/test_repository.py 2010-11-22 22:27:58 +0000
+++ bzrlib/tests/test_repository.py 2011-01-19 00:31:58 +0000
@@ -1659,7 +1659,7 @@
1659 self.orig_pack = target.pack1659 self.orig_pack = target.pack
1660 target.pack = self.log_pack1660 target.pack = self.log_pack
1661 search = target.search_missing_revision_ids(1661 search = target.search_missing_revision_ids(
1662 source_tree.branch.repository, tip)1662 source_tree.branch.repository, revision_ids=[tip])
1663 stream = source.get_stream(search)1663 stream = source.get_stream(search)
1664 from_format = source_tree.branch.repository._format1664 from_format = source_tree.branch.repository._format
1665 sink = target._get_sink()1665 sink = target._get_sink()
16661666
=== modified file 'bzrlib/tests/test_server.py'
--- bzrlib/tests/test_server.py 2011-01-12 01:01:53 +0000
+++ bzrlib/tests/test_server.py 2011-01-19 00:31:58 +0000
@@ -694,8 +694,6 @@
694 server.SmartTCPServer.__init__(self, backing_transport,694 server.SmartTCPServer.__init__(self, backing_transport,
695 root_client_path)695 root_client_path)
696 def serve(self):696 def serve(self):
697 # FIXME: No test are exercising the hooks for the test server
698 # -- vila 20100618
699 self.run_server_started_hooks()697 self.run_server_started_hooks()
700 try:698 try:
701 TestingThreadingTCPServer.serve(self)699 TestingThreadingTCPServer.serve(self)
@@ -803,7 +801,3 @@
803 """Get a backing transport from a server we are decorating."""801 """Get a backing transport from a server we are decorating."""
804 url = 'readonly+' + backing_transport_server.get_url()802 url = 'readonly+' + backing_transport_server.get_url()
805 return transport.get_transport(url)803 return transport.get_transport(url)
806
807
808
809
810804
=== modified file 'bzrlib/tests/test_smart.py'
--- bzrlib/tests/test_smart.py 2011-01-12 01:01:53 +0000
+++ bzrlib/tests/test_smart.py 2011-01-19 00:31:58 +0000
@@ -41,6 +41,7 @@
41 repository as smart_repo,41 repository as smart_repo,
42 packrepository as smart_packrepo,42 packrepository as smart_packrepo,
43 request as smart_req,43 request as smart_req,
44 server,
44 vfs,45 vfs,
45 )46 )
46from bzrlib.tests import test_server47from bzrlib.tests import test_server
@@ -1469,7 +1470,7 @@
1469 request.execute('stacked', 1, (3, r3)))1470 request.execute('stacked', 1, (3, r3)))
14701471
14711472
1472class TestSmartServerRepositoryGetStream(tests.TestCaseWithMemoryTransport):1473class GetStreamTestBase(tests.TestCaseWithMemoryTransport):
14731474
1474 def make_two_commit_repo(self):1475 def make_two_commit_repo(self):
1475 tree = self.make_branch_and_memory_tree('.')1476 tree = self.make_branch_and_memory_tree('.')
@@ -1481,6 +1482,9 @@
1481 repo = tree.branch.repository1482 repo = tree.branch.repository
1482 return repo, r1, r21483 return repo, r1, r2
14831484
1485
1486class TestSmartServerRepositoryGetStream(GetStreamTestBase):
1487
1484 def test_ancestry_of(self):1488 def test_ancestry_of(self):
1485 """The search argument may be a 'ancestry-of' some heads'."""1489 """The search argument may be a 'ancestry-of' some heads'."""
1486 backing = self.get_transport()1490 backing = self.get_transport()
@@ -1507,6 +1511,18 @@
1507 stream_bytes = ''.join(response.body_stream)1511 stream_bytes = ''.join(response.body_stream)
1508 self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')1512 self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
15091513
1514 def test_search_everything(self):
1515 """A search of 'everything' returns a stream."""
1516 backing = self.get_transport()
1517 request = smart_repo.SmartServerRepositoryGetStream_1_19(backing)
1518 repo, r1, r2 = self.make_two_commit_repo()
1519 serialised_fetch_spec = 'everything'
1520 request.execute('', repo._format.network_name())
1521 response = request.do_body(serialised_fetch_spec)
1522 self.assertEqual(('ok',), response.args)
1523 stream_bytes = ''.join(response.body_stream)
1524 self.assertStartsWith(stream_bytes, 'Bazaar pack format 1')
1525
15101526
1511class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):1527class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
15121528
@@ -1906,6 +1922,8 @@
1906 smart_repo.SmartServerRepositoryGetRevisionGraph)1922 smart_repo.SmartServerRepositoryGetRevisionGraph)
1907 self.assertHandlerEqual('Repository.get_stream',1923 self.assertHandlerEqual('Repository.get_stream',
1908 smart_repo.SmartServerRepositoryGetStream)1924 smart_repo.SmartServerRepositoryGetStream)
1925 self.assertHandlerEqual('Repository.get_stream_1.19',
1926 smart_repo.SmartServerRepositoryGetStream_1_19)
1909 self.assertHandlerEqual('Repository.has_revision',1927 self.assertHandlerEqual('Repository.has_revision',
1910 smart_repo.SmartServerRequestHasRevision)1928 smart_repo.SmartServerRequestHasRevision)
1911 self.assertHandlerEqual('Repository.insert_stream',1929 self.assertHandlerEqual('Repository.insert_stream',
@@ -1922,3 +1940,50 @@
1922 smart_repo.SmartServerRepositoryUnlock)1940 smart_repo.SmartServerRepositoryUnlock)
1923 self.assertHandlerEqual('Transport.is_readonly',1941 self.assertHandlerEqual('Transport.is_readonly',
1924 smart_req.SmartServerIsReadonly)1942 smart_req.SmartServerIsReadonly)
1943
1944
1945class SmartTCPServerHookTests(tests.TestCaseWithMemoryTransport):
1946 """Tests for SmartTCPServer hooks."""
1947
1948 def setUp(self):
1949 super(SmartTCPServerHookTests, self).setUp()
1950 self.server = server.SmartTCPServer(self.get_transport())
1951
1952 def test_run_server_started_hooks(self):
1953 """Test the server started hooks get fired properly."""
1954 started_calls = []
1955 server.SmartTCPServer.hooks.install_named_hook('server_started',
1956 lambda backing_urls, url: started_calls.append((backing_urls, url)),
1957 None)
1958 started_ex_calls = []
1959 server.SmartTCPServer.hooks.install_named_hook('server_started_ex',
1960 lambda backing_urls, url: started_ex_calls.append((backing_urls, url)),
1961 None)
1962 self.server._sockname = ('example.com', 42)
1963 self.server.run_server_started_hooks()
1964 self.assertEquals(started_calls,
1965 [([self.get_transport().base], 'bzr://example.com:42/')])
1966 self.assertEquals(started_ex_calls,
1967 [([self.get_transport().base], self.server)])
1968
1969 def test_run_server_started_hooks_ipv6(self):
1970 """Test that socknames can contain 4-tuples."""
1971 self.server._sockname = ('::', 42, 0, 0)
1972 started_calls = []
1973 server.SmartTCPServer.hooks.install_named_hook('server_started',
1974 lambda backing_urls, url: started_calls.append((backing_urls, url)),
1975 None)
1976 self.server.run_server_started_hooks()
1977 self.assertEquals(started_calls,
1978 [([self.get_transport().base], 'bzr://:::42/')])
1979
1980 def test_run_server_stopped_hooks(self):
1981 """Test the server stopped hooks."""
1982 self.server._sockname = ('example.com', 42)
1983 stopped_calls = []
1984 server.SmartTCPServer.hooks.install_named_hook('server_stopped',
1985 lambda backing_urls, url: stopped_calls.append((backing_urls, url)),
1986 None)
1987 self.server.run_server_stopped_hooks()
1988 self.assertEquals(stopped_calls,
1989 [([self.get_transport().base], 'bzr://example.com:42/')])
19251990
=== added file 'doc/developers/fetch.txt'
--- doc/developers/fetch.txt 1970-01-01 00:00:00 +0000
+++ doc/developers/fetch.txt 2011-01-19 00:31:58 +0000
@@ -0,0 +1,86 @@
1=============
2Fetching data
3=============
4
5Overview of a fetch
6===================
7
8Inside bzr, a typical fetch happens like this:
9
10* a user runs a command like ``bzr branch`` or ``bzr pull``
11
12* ``Repository.fetch`` is called (by a higher-level method such as
13 ``ControlDir.sprout``, ``Branch.fetch``, etc).
14
15* An ``InterRepository`` object is created. The exact implementation of
16 ``InterRepository`` chosen depends on the format/capabilities of the
17 source and target repos.
18
19* The source and target repositories are compared to determine which data
20 needs to be transferred.
21
22* The repository data is copied. Often this is done by creating a
23 ``StreamSource`` and ``StreamSink`` from the source and target
24 repositories and feeding the stream from the source into the sink, but
25 some ``InterRepository`` implementations do differently.
26
27
28How objects to be transferred are determined
29============================================
30
31See ``InterRepository._walk_to_common_revisions``. The basic idea is to
32do a breadth-first search in the source repository's revision graph
33(starting from the head or heads the caller asked for), and look in the
34target repository to see if those revisions are already present.
35Eventually this will find the common ancestors in both graphs, and thus
36the set of revisions to be copied has been identified.
37
38All inventories for the copied revisions need to be present (and all
39parent inventories at the stacking boundary too, to support stacking).
40
41All texts versions introduced by those inventories need to be transferred
42(but see also stacking constraints).
43
44Fetch specs
45===========
46
47The most ``fetch`` methods accept a ``fetch_spec`` parameter. This is how
48the caller controls what is fetched: e.g. all revisions for a given head
49(that aren't already present in the target), the full ancestry for one or
50more heads, or even the full contents of the source repository.
51
52The ``fetch_spec`` parameter is an object that implements the interface
53defined by ``AbstractSearchResult`` in ``bzrlib.graph``. It describes
54which keys should be fetched. Current implementations are
55``SearchResult``, ``PendingAncestryResult``, ``EmptySearchResult``, and
56``EverythingResult``. Some have options controlling if missing revisions
57cause errors or not, etc.
58
59There are also some “search” objects, which can be used to conveniently
60construct a search result for common cases: ``EverythingNotInOther`` and
61``NotInOtherForRevs``. They provide an ``execute`` method that performs
62the search and returns a search result.
63
64Also, ``Graph._make_breadth_first_searcher`` returns an object with a
65``get_result`` method that returns a search result.
66
67
68Streams
69=======
70
71A **stream** is an iterable of (substream type, substream) pairs.
72The **substream type** is a ``str`` that will be one of ``texts``,
73``inventories``, ``inventory-deltas``, ``chk_bytes``, ``revisions`` or
74``signatures``. A **substream** is a record stream. The format of those
75records depends on the repository format being streamed, except for
76``inventory-deltas`` records which are format-independent.
77
78A stream source can be constructed with ``repo._get_source(to_format)``,
79and it provides a ``get_stream(search)`` method (among others). A stream
80sink can be constructed with ``repo._get_sink()``, and provides an
81``insert_stream(stream, src_format, resume_tokens)`` method (among
82others).
83
84
85..
86 vim: ft=rst tw=74 ai
087
=== modified file 'doc/developers/index.txt'
--- doc/developers/index.txt 2010-10-13 04:13:48 +0000
+++ doc/developers/index.txt 2011-01-19 00:31:58 +0000
@@ -38,6 +38,7 @@
3838
39 transports39 transports
40 ui40 ui
41 fetch
4142
42Releasing and Packaging43Releasing and Packaging
43=======================44=======================
4445
=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- doc/en/release-notes/bzr-2.3.txt 2011-01-15 17:29:41 +0000
+++ doc/en/release-notes/bzr-2.3.txt 2011-01-19 00:31:58 +0000
@@ -32,6 +32,11 @@
32.. Fixes for situations where bzr would previously crash or give incorrect32.. Fixes for situations where bzr would previously crash or give incorrect
33 or undesirable results.33 or undesirable results.
3434
35* Plugins incompatible with bzr no longer produce a warning on every
36 command invocation. Instead, a message is shown by ``bzr plugins`` and
37 in crash reports.
38 (#704195, Martin Pool)
39
35Documentation40Documentation
36*************41*************
3742
@@ -275,6 +280,11 @@
275 crashes when encountering private bugs (they are just displayed as such).280 crashes when encountering private bugs (they are just displayed as such).
276 (Vincent Ladeuil, #354985)281 (Vincent Ladeuil, #354985)
277282
283* The ``revision_id`` parameter of
284 ``Repository.search_missing_revision_ids`` and
285 ``InterRepository.search_missing_revision_ids`` is deprecated. It is
286 replaced by the ``revision_ids`` parameter. (Andrew Bennetts)
287
278Internals288Internals
279*********289*********
280290
281291
=== added file 'doc/en/release-notes/bzr-2.4.txt'
--- doc/en/release-notes/bzr-2.4.txt 1970-01-01 00:00:00 +0000
+++ doc/en/release-notes/bzr-2.4.txt 2011-01-19 00:31:58 +0000
@@ -0,0 +1,64 @@
1####################
2Bazaar Release Notes
3####################
4
5.. toctree::
6 :maxdepth: 1
7
8bzr 2.4b1
9#########
10
11:2.4b1: NOT RELEASED YET
12
13External Compatibility Breaks
14*****************************
15
16.. These may require users to change the way they use Bazaar.
17
18New Features
19************
20
21.. New commands, options, etc that users may wish to try out.
22
23Improvements
24************
25
26.. Improvements to existing commands, especially improved performance
27 or memory usage, or better results.
28
29Bug Fixes
30*********
31
32.. Fixes for situations where bzr would previously crash or give incorrect
33 or undesirable results.
34
35* ``bzr push --overwrite`` with an older revision specified will now correctly
36 roll back the target branch. (Jelmer Vernooij, #386576)
37
38* ``bzr serve`` no longer crashes when a server_started hook is installed and IPv6
39 support is available on the system. (Jelmer Vernooij, #293697)
40
41Documentation
42*************
43
44.. Improved or updated documentation.
45
46API Changes
47***********
48
49.. Changes that may require updates in plugins or other code that uses
50 bzrlib.
51
52Internals
53*********
54
55.. Major internal changes, unlikely to be visible to users or plugin
56 developers, but interesting for bzr developers.
57
58Testing
59*******
60
61.. Fixes and changes that are only relevant to bzr's test framework and
62 suite. This can include new facilities for writing tests, fixes to
63 spurious test failures and changes to the way things should be tested.
64
065
=== added file 'doc/en/whats-new/whats-new-in-2.4.txt'
--- doc/en/whats-new/whats-new-in-2.4.txt 1970-01-01 00:00:00 +0000
+++ doc/en/whats-new/whats-new-in-2.4.txt 2011-01-19 00:31:58 +0000
@@ -0,0 +1,33 @@
1*************************
2What's New in Bazaar 2.4?
3*************************
4
5Bazaar 2.4 is still under development, and will be released in August 2011.
6This document accumulates a high level summary of what's changed. See the
7:doc:`../release-notes/index` for a full list.
8
9Users are encouraged to upgrade from the other stable series. This
10document outlines the improvements in Bazaar 2.4 vs Bazaar 2.3. As well as
11summarizing improvements made to the core product, it highlights
12enhancements within the broader Bazaar world of potential interest to
13those upgrading.
14
15Bazaar 2.4.0 is fully compatible both locally and on the network with 2.0
162.1, 2.2 and 2.3, and can read and write repositories generated by all
17previous versions.
18
19
20Further information
21*******************
22
23For more detailed information on the changes made, see the the
24:doc:`../release-notes/index` for:
25
26* the interim bzr `milestones <https://launchpad.net/bzr/2.4>`_
27* the plugins you use.
28
29For a summary of changes made in earlier releases, see:
30
31* :doc:`whats-new-in-2.1`
32* :doc:`whats-new-in-2.2`
33* :doc:`whats-new-in-2.3`

Subscribers

People subscribed via source and target branches