Merge lp:~jameinel/bzr/1.18-stack-and-annotate-393366 into lp:~bzr/bzr/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
Reviewer Review Type Date Requested Status
Martin Pool Approve
Review via email: mp+8840@code.launchpad.net
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

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.

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