Merge lp:~jameinel/bzr/1.18-stack-and-annotate-393366 into lp:~bzr/bzr/trunk-old
- 1.18-stack-and-annotate-393366
- Merge into trunk-old
Proposed by
John A Meinel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~jameinel/bzr/1.18-stack-and-annotate-393366 |
Merge into: | lp:~bzr/bzr/trunk-old |
Diff against target: | 335 lines |
To merge this branch: | bzr merge lp:~jameinel/bzr/1.18-stack-and-annotate-393366 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Approve | ||
Review via email: mp+8840@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote : | # |
Revision history for this message
Martin Pool (mbp) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-07-24 03:15:56 +0000 |
3 | +++ NEWS 2009-07-24 19:35:12 +0000 |
4 | @@ -26,6 +26,10 @@ |
5 | Bug Fixes |
6 | ********* |
7 | |
8 | +* Annotating on a stacked branch will now succeed in simple scenarios. |
9 | + There are still some complex scenarios where it will fail (bug #399884) |
10 | + (John Arbash Meinel, #393366) |
11 | + |
12 | * Authenticating against an ssh server now uses ``auth_none`` to determine |
13 | if password authentication is even supported. This fixes a bug where |
14 | users would be prompted for a launchpad password, even though launchpad |
15 | |
16 | === modified file 'bzrlib/knit.py' |
17 | --- bzrlib/knit.py 2009-07-08 23:10:47 +0000 |
18 | +++ bzrlib/knit.py 2009-07-24 19:35:12 +0000 |
19 | @@ -1489,7 +1489,8 @@ |
20 | non_local_keys, |
21 | positions): |
22 | generator = _VFContentMapGenerator(self, keys, non_local_keys, |
23 | - global_map) |
24 | + global_map, |
25 | + ordering=ordering) |
26 | for record in generator.get_record_stream(): |
27 | yield record |
28 | else: |
29 | @@ -1993,6 +1994,9 @@ |
30 | class _ContentMapGenerator(object): |
31 | """Generate texts or expose raw deltas for a set of texts.""" |
32 | |
33 | + def __init__(self, ordering='unordered'): |
34 | + self._ordering = ordering |
35 | + |
36 | def _get_content(self, key): |
37 | """Get the content object for key.""" |
38 | # Note that _get_content is only called when the _ContentMapGenerator |
39 | @@ -2032,7 +2036,7 @@ |
40 | # Loop over fallback repositories asking them for texts - ignore |
41 | # any missing from a particular fallback. |
42 | for record in source.get_record_stream(missing_keys, |
43 | - 'unordered', True): |
44 | + self._ordering, True): |
45 | if record.storage_kind == 'absent': |
46 | # Not in thie particular stream, may be in one of the |
47 | # other fallback vfs objects. |
48 | @@ -2170,7 +2174,7 @@ |
49 | """Content map generator reading from a VersionedFiles object.""" |
50 | |
51 | def __init__(self, versioned_files, keys, nonlocal_keys=None, |
52 | - global_map=None, raw_record_map=None): |
53 | + global_map=None, raw_record_map=None, ordering='unordered'): |
54 | """Create a _ContentMapGenerator. |
55 | |
56 | :param versioned_files: The versioned files that the texts are being |
57 | @@ -2184,6 +2188,7 @@ |
58 | :param raw_record_map: A unparsed raw record map to use for answering |
59 | contents. |
60 | """ |
61 | + _ContentMapGenerator.__init__(self, ordering=ordering) |
62 | # The vf to source data from |
63 | self.vf = versioned_files |
64 | # The keys desired |
65 | |
66 | === modified file 'bzrlib/tests/per_repository_reference/__init__.py' |
67 | --- bzrlib/tests/per_repository_reference/__init__.py 2009-06-15 06:47:14 +0000 |
68 | +++ bzrlib/tests/per_repository_reference/__init__.py 2009-07-24 19:35:12 +0000 |
69 | @@ -1,4 +1,4 @@ |
70 | -# Copyright (C) 2008 Canonical Ltd |
71 | +# Copyright (C) 2008, 2009 Canonical Ltd |
72 | # |
73 | # This program is free software; you can redistribute it and/or modify |
74 | # it under the terms of the GNU General Public License as published by |
75 | @@ -98,6 +98,7 @@ |
76 | 'bzrlib.tests.per_repository_reference.test_check', |
77 | 'bzrlib.tests.per_repository_reference.test_default_stacking', |
78 | 'bzrlib.tests.per_repository_reference.test_fetch', |
79 | + 'bzrlib.tests.per_repository_reference.test_get_record_stream', |
80 | 'bzrlib.tests.per_repository_reference.test_get_rev_id_for_revno', |
81 | 'bzrlib.tests.per_repository_reference.test_initialize', |
82 | 'bzrlib.tests.per_repository_reference.test_unlock', |
83 | |
84 | === added file 'bzrlib/tests/per_repository_reference/test_get_record_stream.py' |
85 | --- bzrlib/tests/per_repository_reference/test_get_record_stream.py 1970-01-01 00:00:00 +0000 |
86 | +++ bzrlib/tests/per_repository_reference/test_get_record_stream.py 2009-07-24 19:35:12 +0000 |
87 | @@ -0,0 +1,201 @@ |
88 | +# Copyright (C) 2008, 2009 Canonical Ltd |
89 | +# |
90 | +# This program is free software; you can redistribute it and/or modify |
91 | +# it under the terms of the GNU General Public License as published by |
92 | +# the Free Software Foundation; either version 2 of the License, or |
93 | +# (at your option) any later version. |
94 | +# |
95 | +# This program is distributed in the hope that it will be useful, |
96 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
97 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
98 | +# GNU General Public License for more details. |
99 | +# |
100 | +# You should have received a copy of the GNU General Public License |
101 | +# along with this program; if not, write to the Free Software |
102 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
103 | + |
104 | +"""Tests that get_record_stream() behaves itself properly when stacked.""" |
105 | + |
106 | +from bzrlib import ( |
107 | + errors, |
108 | + knit, |
109 | + ) |
110 | +from bzrlib.tests.per_repository_reference import ( |
111 | + TestCaseWithExternalReferenceRepository, |
112 | + ) |
113 | + |
114 | + |
115 | +class TestGetRecordStream(TestCaseWithExternalReferenceRepository): |
116 | + |
117 | + def setUp(self): |
118 | + super(TestGetRecordStream, self).setUp() |
119 | + builder = self.make_branch_builder('all') |
120 | + builder.start_series() |
121 | + # Graph of revisions: |
122 | + # |
123 | + # A |
124 | + # |\ |
125 | + # B C |
126 | + # |/| |
127 | + # D E |
128 | + # |\| |
129 | + # F G |
130 | + # These can be split up among the different repos as desired |
131 | + # |
132 | + |
133 | + builder.build_snapshot('A', None, [ |
134 | + ('add', ('', 'root-id', 'directory', None)), |
135 | + ('add', ('file', 'f-id', 'file', 'initial content\n')), |
136 | + ]) |
137 | + builder.build_snapshot('B', ['A'], [ |
138 | + ('modify', ('f-id', 'initial content\n' |
139 | + 'and B content\n')), |
140 | + ]) |
141 | + builder.build_snapshot('C', ['A'], [ |
142 | + ('modify', ('f-id', 'initial content\n' |
143 | + 'and C content\n')), |
144 | + ]) |
145 | + builder.build_snapshot('D', ['B', 'C'], [ |
146 | + ('modify', ('f-id', 'initial content\n' |
147 | + 'and B content\n' |
148 | + 'and C content\n')), |
149 | + ]) |
150 | + builder.build_snapshot('E', ['C'], [ |
151 | + ('modify', ('f-id', 'initial content\n' |
152 | + 'and C content\n' |
153 | + 'and E content\n')), |
154 | + ]) |
155 | + builder.build_snapshot('F', ['D'], [ |
156 | + ('modify', ('f-id', 'initial content\n' |
157 | + 'and B content\n' |
158 | + 'and C content\n' |
159 | + 'and F content\n')), |
160 | + ]) |
161 | + builder.build_snapshot('G', ['E', 'D'], [ |
162 | + ('modify', ('f-id', 'initial content\n' |
163 | + 'and B content\n' |
164 | + 'and C content\n' |
165 | + 'and E content\n')), |
166 | + ]) |
167 | + builder.finish_series() |
168 | + self.all_repo = builder.get_branch().repository |
169 | + self.all_repo.lock_read() |
170 | + self.addCleanup(self.all_repo.unlock) |
171 | + self.base_repo = self.make_repository('base') |
172 | + self.stacked_repo = self.make_referring('referring', 'base') |
173 | + |
174 | + def make_simple_split(self): |
175 | + """Set up the repositories so that everything is in base except F""" |
176 | + self.base_repo.fetch(self.all_repo, revision_id='G') |
177 | + self.stacked_repo.fetch(self.all_repo, revision_id='F') |
178 | + |
179 | + def make_complex_split(self): |
180 | + """intermix the revisions so that base holds left stacked holds right. |
181 | + |
182 | + base will hold |
183 | + A B D F (and C because it is a parent of D) |
184 | + referring will hold |
185 | + C E G (only) |
186 | + """ |
187 | + self.base_repo.fetch(self.all_repo, revision_id='B') |
188 | + self.stacked_repo.fetch(self.all_repo, revision_id='C') |
189 | + self.base_repo.fetch(self.all_repo, revision_id='F') |
190 | + self.stacked_repo.fetch(self.all_repo, revision_id='G') |
191 | + |
192 | + def test_unordered_fetch_simple_split(self): |
193 | + self.make_simple_split() |
194 | + keys = [('f-id', r) for r in 'ABCDF'] |
195 | + self.stacked_repo.lock_read() |
196 | + self.addCleanup(self.stacked_repo.unlock) |
197 | + stream = self.stacked_repo.texts.get_record_stream( |
198 | + keys, 'unordered', False) |
199 | + record_keys = set() |
200 | + for record in stream: |
201 | + if record.storage_kind == 'absent': |
202 | + raise ValueError('absent record: %s' % (record.key,)) |
203 | + record_keys.add(record.key) |
204 | + # everything should be present, we don't care about the order |
205 | + self.assertEqual(keys, sorted(record_keys)) |
206 | + |
207 | + def test_unordered_fetch_complex_split(self): |
208 | + self.make_complex_split() |
209 | + keys = [('f-id', r) for r in 'ABCDEG'] |
210 | + self.stacked_repo.lock_read() |
211 | + self.addCleanup(self.stacked_repo.unlock) |
212 | + stream = self.stacked_repo.texts.get_record_stream( |
213 | + keys, 'unordered', False) |
214 | + record_keys = set() |
215 | + for record in stream: |
216 | + if record.storage_kind == 'absent': |
217 | + raise ValueError('absent record: %s' % (record.key,)) |
218 | + record_keys.add(record.key) |
219 | + # everything should be present, we don't care about the order |
220 | + self.assertEqual(keys, sorted(record_keys)) |
221 | + |
222 | + def test_ordered_no_closure(self): |
223 | + self.make_complex_split() |
224 | + # Topological ordering allows B & C and D & E to be returned with |
225 | + # either one first, so the required ordering is: |
226 | + # [A (B C) (D E) G] |
227 | + keys = [('f-id', r) for r in 'ABCDEG'] |
228 | + alt_1 = [('f-id', r) for r in 'ACBDEG'] |
229 | + alt_2 = [('f-id', r) for r in 'ABCEDG'] |
230 | + alt_3 = [('f-id', r) for r in 'ACBEDG'] |
231 | + self.stacked_repo.lock_read() |
232 | + self.addCleanup(self.stacked_repo.unlock) |
233 | + stream = self.stacked_repo.texts.get_record_stream( |
234 | + keys, 'topological', False) |
235 | + record_keys = [] |
236 | + for record in stream: |
237 | + if record.storage_kind == 'absent': |
238 | + raise ValueError('absent record: %s' % (record.key,)) |
239 | + record_keys.append(record.key) |
240 | + self.assertTrue(record_keys in (keys, alt_1, alt_2, alt_3)) |
241 | + |
242 | + def test_ordered_fulltext_simple(self): |
243 | + self.make_simple_split() |
244 | + # This is a common case in asking to annotate a file that exists on a |
245 | + # stacked branch. |
246 | + # See https://bugs.launchpad.net/bzr/+bug/393366 |
247 | + # Topological ordering allows B & C and D & E to be returned with |
248 | + # either one first, so the required ordering is: |
249 | + # [A (B C) D F] |
250 | + keys = [('f-id', r) for r in 'ABCDF'] |
251 | + alt_1 = [('f-id', r) for r in 'ACBDF'] |
252 | + self.stacked_repo.lock_read() |
253 | + self.addCleanup(self.stacked_repo.unlock) |
254 | + stream = self.stacked_repo.texts.get_record_stream( |
255 | + keys, 'topological', True) |
256 | + record_keys = [] |
257 | + for record in stream: |
258 | + if record.storage_kind == 'absent': |
259 | + raise ValueError('absent record: %s' % (record.key,)) |
260 | + record_keys.append(record.key) |
261 | + self.assertTrue(record_keys in (keys, alt_1)) |
262 | + |
263 | + def test_ordered_fulltext_complex(self): |
264 | + self.make_complex_split() |
265 | + # Topological ordering allows B & C and D & E to be returned with |
266 | + # either one first, so the required ordering is: |
267 | + # [A (B C) (D E) G] |
268 | + keys = [('f-id', r) for r in 'ABCDEG'] |
269 | + alt_1 = [('f-id', r) for r in 'ACBDEG'] |
270 | + alt_2 = [('f-id', r) for r in 'ABCEDG'] |
271 | + alt_3 = [('f-id', r) for r in 'ACBEDG'] |
272 | + self.stacked_repo.lock_read() |
273 | + self.addCleanup(self.stacked_repo.unlock) |
274 | + stream = self.stacked_repo.texts.get_record_stream( |
275 | + keys, 'topological', True) |
276 | + record_keys = [] |
277 | + for record in stream: |
278 | + if record.storage_kind == 'absent': |
279 | + raise ValueError('absent record: %s' % (record.key,)) |
280 | + record_keys.append(record.key) |
281 | + # Note that currently --2a format repositories do this correctly, but |
282 | + # KnitPack format repositories do not. |
283 | + if isinstance(self.stacked_repo.texts, knit.KnitVersionedFiles): |
284 | + # See https://bugs.launchpad.net/bzr/+bug/399884 |
285 | + self.expectFailure('KVF does not weave fulltexts from fallback' |
286 | + ' repositories to preserve perfect order', |
287 | + self.assertTrue, record_keys in (keys, alt_1, alt_2, alt_3)) |
288 | + self.assertTrue(record_keys in (keys, alt_1, alt_2, alt_3)) |
289 | |
290 | === modified file 'bzrlib/tests/test_knit.py' |
291 | --- bzrlib/tests/test_knit.py 2009-07-06 19:37:07 +0000 |
292 | +++ bzrlib/tests/test_knit.py 2009-07-24 19:35:12 +0000 |
293 | @@ -2230,7 +2230,7 @@ |
294 | # self.assertEqual([("annotate", key_basis)], basis.calls) |
295 | self.assertEqual([('get_parent_map', set([key_basis])), |
296 | ('get_parent_map', set([key_basis])), |
297 | - ('get_record_stream', [key_basis], 'unordered', True)], |
298 | + ('get_record_stream', [key_basis], 'topological', True)], |
299 | basis.calls) |
300 | |
301 | def test_check(self): |
302 | @@ -2342,9 +2342,9 @@ |
303 | # ask which fallbacks have which parents. |
304 | self.assertEqual([ |
305 | ("get_parent_map", set([key_basis, key_basis_2, key_missing])), |
306 | - # unordered is asked for by the underlying worker as it still |
307 | - # buffers everything while answering - which is a problem! |
308 | - ("get_record_stream", [key_basis_2, key_basis], 'unordered', True)], |
309 | + # topological is requested from the fallback, because that is what |
310 | + # was requested at the top level. |
311 | + ("get_record_stream", [key_basis_2, key_basis], 'topological', True)], |
312 | calls) |
313 | |
314 | def test_get_record_stream_unordered_deltas(self): |
315 | @@ -2571,7 +2571,7 @@ |
316 | last_call = basis.calls[-1] |
317 | self.assertEqual('get_record_stream', last_call[0]) |
318 | self.assertEqual(set([key_left, key_right]), set(last_call[1])) |
319 | - self.assertEqual('unordered', last_call[2]) |
320 | + self.assertEqual('topological', last_call[2]) |
321 | self.assertEqual(True, last_call[3]) |
322 | |
323 | |
324 | |
325 | === modified file 'bzrlib/ui/text.py' |
326 | --- bzrlib/ui/text.py 2009-07-19 01:05:42 +0000 |
327 | +++ bzrlib/ui/text.py 2009-07-24 19:35:13 +0000 |
328 | @@ -18,6 +18,7 @@ |
329 | """Text UI, write output to the console. |
330 | """ |
331 | |
332 | +import getpass |
333 | import os |
334 | import sys |
335 | import time |
We have two bugs when it comes to annotate and get_record_stream.
1) If doing VF.get_ record_ stream( .., 'topological', True), it was not passing the 'topological' flag down to fallback repositories. This meant that 'bzr annotate' would fail pretty much any time there was a graph that crossed a stacking boundary. As it would properly return the stacked-on revisions, but the fallback repository would return the keys in 'unordered' mode.
2) It isn't quite sufficient to just return fallback revs in 'topological' and then stacked revs in 'topological', as some of the stacked revs may actually be ancestors of some of the fallback revs.
However, I didn't fix (2) because --2a works correctly today, and I think (1) is sufficient to dramatically reduce the number of times people run into this, and is much easier to fix.
This adds tests for the behavior of get_record_stream() across all repositories that support stacking, and includes an expectFailure() for formats where I know it is broken in the complex case.