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