Merge lp:~spiv/bzr/bug-440952-bzrdir into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Approved by: John A Meinel
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/bug-440952-bzrdir
Merge into: lp:bzr
Diff against target: 511 lines (+253/-34)
11 files modified
NEWS (+8/-0)
bzrlib/branch.py (+2/-2)
bzrlib/errors.py (+24/-3)
bzrlib/remote.py (+30/-14)
bzrlib/smart/bzrdir.py (+40/-6)
bzrlib/smart/request.py (+3/-0)
bzrlib/tests/blackbox/test_shared_repository.py (+19/-1)
bzrlib/tests/per_branch/test_push.py (+1/-1)
bzrlib/tests/test_errors.py (+32/-0)
bzrlib/tests/test_remote.py (+16/-7)
bzrlib/tests/test_smart.py (+78/-0)
To merge this branch: bzr merge lp:~spiv/bzr/bug-440952-bzrdir
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+17183@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

This is a substantial reworking of <https://code.edge.launchpad.net/~slyguy/bzr/bug-440952-bzrdir/+merge/13431>.

The basic mechanism now is that probing for a repository is delayed until the NotBranchError is stringified. So for non-UI cases, the only extra cost should be that NotBranchError will hold a reference to a bzrdir that is never used.

This also adds a BzrDir.open_branchV3 verb so that this extra field can be passed via the smart protocol. Perhaps the 'nobranch' case should be handled via more generic error translation instead, but this patch was already large enough.

I think this resolves the various objections raised in earlier reviews: it doesn't probe unnecessarily, it doesn't add a has_repository method, it doesn't break HPSS backwards compatibility, etc. I'm willing to be proven wrong, of course! So, please review :)

Revision history for this message
John A Meinel (jameinel) wrote :

The final output seems to be:
$ awbzr branch ../bzr
bzr: ERROR: Not a branch: "C:/Users/jameinel/dev/bzr/.bzr/branch/": location is a repository.

I'm not 100% happy with that, but it is better than nothing. The code looks good.

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 2010-01-15 04:06:45 +0000
3+++ NEWS 2010-01-15 05:14:10 +0000
4@@ -116,6 +116,10 @@
5 ``try``/``finally`` blocks where applicable as it is simpler and more
6 robust. (Andrew Bennetts)
7
8+* Attempts to open a shared repository as a branch (e.g. ``bzr branch
9+ path/to/repo``) will now include "location is a repository" as a hint in
10+ the error message. (Brian de Alwis, Andrew Bennetts, #440952)
11+
12 * Push will now inform the user when they are trying to push to a foreign
13 VCS for which roundtripping is not supported, and will suggest them to
14 use dpush. (Jelmer Vernooij)
15@@ -166,6 +170,10 @@
16 Internals
17 *********
18
19+* Added ``BzrDir.open_branchV3`` smart server request, which can receive
20+ a string of details (such as "location is a repository") as part of a
21+ ``nobranch`` response. (Andrew Bennetts, #440952)
22+
23 * New helper osutils.UnicodeOrBytesToBytesWriter which encodes unicode
24 objects but passes str objects straight through. This is used for
25 selftest but may be useful for diff and other operations that generate
26
27=== modified file 'bzrlib/branch.py'
28--- bzrlib/branch.py 2010-01-08 05:28:17 +0000
29+++ bzrlib/branch.py 2010-01-15 05:14:10 +0000
30@@ -1441,7 +1441,7 @@
31 format_string = transport.get_bytes("format")
32 return klass._formats[format_string]
33 except errors.NoSuchFile:
34- raise errors.NotBranchError(path=transport.base)
35+ raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
36 except KeyError:
37 raise errors.UnknownFormatError(format=format_string, kind='branch')
38
39@@ -1796,7 +1796,7 @@
40 _repository=a_bzrdir.find_repository(),
41 ignore_fallbacks=ignore_fallbacks)
42 except errors.NoSuchFile:
43- raise errors.NotBranchError(path=transport.base)
44+ raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
45
46 def __init__(self):
47 super(BranchFormatMetadir, self).__init__()
48
49=== modified file 'bzrlib/errors.py'
50--- bzrlib/errors.py 2010-01-08 06:33:05 +0000
51+++ bzrlib/errors.py 2010-01-15 05:14:10 +0000
52@@ -702,11 +702,32 @@
53 # TODO: Probably this behavior of should be a common superclass
54 class NotBranchError(PathError):
55
56- _fmt = 'Not a branch: "%(path)s".'
57+ _fmt = 'Not a branch: "%(path)s"%(detail)s.'
58
59- def __init__(self, path):
60+ def __init__(self, path, detail=None, bzrdir=None):
61 import bzrlib.urlutils as urlutils
62- self.path = urlutils.unescape_for_display(path, 'ascii')
63+ path = urlutils.unescape_for_display(path, 'ascii')
64+ if detail is not None:
65+ detail = ': ' + detail
66+ self.detail = detail
67+ self.bzrdir = bzrdir
68+ PathError.__init__(self, path=path)
69+
70+ def _format(self):
71+ # XXX: Ideally self.detail would be a property, but Exceptions in
72+ # Python 2.4 have to be old-style classes so properties don't work.
73+ # Instead we override _format.
74+ if self.detail is None:
75+ if self.bzrdir is not None:
76+ try:
77+ self.bzrdir.open_repository()
78+ except NoRepositoryPresent:
79+ self.detail = ''
80+ else:
81+ self.detail = ': location is a repository'
82+ else:
83+ self.detail = ''
84+ return PathError._format(self)
85
86
87 class NoSubmitBranch(PathError):
88
89=== modified file 'bzrlib/remote.py'
90--- bzrlib/remote.py 2009-12-15 20:32:34 +0000
91+++ bzrlib/remote.py 2010-01-15 05:14:10 +0000
92@@ -284,21 +284,32 @@
93 def _get_branch_reference(self):
94 path = self._path_for_remote_call(self._client)
95 medium = self._client._medium
96- if not medium._is_remote_before((1, 13)):
97+ candidate_calls = [
98+ ('BzrDir.open_branchV3', (2, 1)),
99+ ('BzrDir.open_branchV2', (1, 13)),
100+ ('BzrDir.open_branch', None),
101+ ]
102+ for verb, required_version in candidate_calls:
103+ if required_version and medium._is_remote_before(required_version):
104+ continue
105 try:
106- response = self._call('BzrDir.open_branchV2', path)
107- if response[0] not in ('ref', 'branch'):
108- raise errors.UnexpectedSmartServerResponse(response)
109- return response
110+ response = self._call(verb, path)
111 except errors.UnknownSmartMethod:
112- medium._remember_remote_is_before((1, 13))
113- response = self._call('BzrDir.open_branch', path)
114- if response[0] != 'ok':
115+ if required_version is None:
116+ raise
117+ medium._remember_remote_is_before(required_version)
118+ else:
119+ break
120+ if verb == 'BzrDir.open_branch':
121+ if response[0] != 'ok':
122+ raise errors.UnexpectedSmartServerResponse(response)
123+ if response[1] != '':
124+ return ('ref', response[1])
125+ else:
126+ return ('branch', '')
127+ if response[0] not in ('ref', 'branch'):
128 raise errors.UnexpectedSmartServerResponse(response)
129- if response[1] != '':
130- return ('ref', response[1])
131- else:
132- return ('branch', '')
133+ return response
134
135 def _get_tree_branch(self):
136 """See BzrDir._get_tree_branch()."""
137@@ -2823,8 +2834,13 @@
138 raise NoSuchRevision(find('branch'), err.error_args[0])
139 elif err.error_verb == 'nosuchrevision':
140 raise NoSuchRevision(find('repository'), err.error_args[0])
141- elif err.error_tuple == ('nobranch',):
142- raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
143+ elif err.error_verb == 'nobranch':
144+ if len(err.error_args) >= 1:
145+ extra = err.error_args[0]
146+ else:
147+ extra = None
148+ raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
149+ detail=extra)
150 elif err.error_verb == 'norepository':
151 raise errors.NoRepositoryPresent(find('bzrdir'))
152 elif err.error_verb == 'LockContention':
153
154=== modified file 'bzrlib/smart/bzrdir.py'
155--- bzrlib/smart/bzrdir.py 2009-09-22 00:34:10 +0000
156+++ bzrlib/smart/bzrdir.py 2010-01-15 05:14:10 +0000
157@@ -88,8 +88,8 @@
158 try:
159 self._bzrdir = BzrDir.open_from_transport(
160 self.transport_from_client_path(path))
161- except errors.NotBranchError:
162- return FailedSmartServerResponse(('nobranch', ))
163+ except errors.NotBranchError, e:
164+ return FailedSmartServerResponse(('nobranch',))
165 return self.do_bzrdir_request(*args)
166
167 def _boolean_to_yes_no(self, a_boolean):
168@@ -465,8 +465,8 @@
169 return SuccessfulSmartServerResponse(('ok', ''))
170 else:
171 return SuccessfulSmartServerResponse(('ok', reference_url))
172- except errors.NotBranchError:
173- return FailedSmartServerResponse(('nobranch', ))
174+ except errors.NotBranchError, e:
175+ return FailedSmartServerResponse(('nobranch',))
176
177
178 class SmartServerRequestOpenBranchV2(SmartServerRequestBzrDir):
179@@ -481,5 +481,39 @@
180 return SuccessfulSmartServerResponse(('branch', format))
181 else:
182 return SuccessfulSmartServerResponse(('ref', reference_url))
183- except errors.NotBranchError:
184- return FailedSmartServerResponse(('nobranch', ))
185+ except errors.NotBranchError, e:
186+ return FailedSmartServerResponse(('nobranch',))
187+
188+
189+class SmartServerRequestOpenBranchV3(SmartServerRequestBzrDir):
190+
191+ def do_bzrdir_request(self):
192+ """Open a branch at path and return the reference or format.
193+
194+ This version introduced in 2.1.
195+
196+ Differences to SmartServerRequestOpenBranchV2:
197+ * can return 2-element ('nobranch', extra), where 'extra' is a string
198+ with an explanation like 'location is a repository'. Previously
199+ a 'nobranch' response would never have more than one element.
200+ """
201+ try:
202+ reference_url = self._bzrdir.get_branch_reference()
203+ if reference_url is None:
204+ br = self._bzrdir.open_branch(ignore_fallbacks=True)
205+ format = br._format.network_name()
206+ return SuccessfulSmartServerResponse(('branch', format))
207+ else:
208+ return SuccessfulSmartServerResponse(('ref', reference_url))
209+ except errors.NotBranchError, e:
210+ # Stringify the exception so that its .detail attribute will be
211+ # filled out.
212+ str(e)
213+ resp = ('nobranch',)
214+ detail = e.detail
215+ if detail:
216+ if detail.startswith(': '):
217+ detail = detail[2:]
218+ resp += (detail,)
219+ return FailedSmartServerResponse(resp)
220+
221
222=== modified file 'bzrlib/smart/request.py'
223--- bzrlib/smart/request.py 2009-12-21 17:00:29 +0000
224+++ bzrlib/smart/request.py 2010-01-15 05:14:10 +0000
225@@ -561,6 +561,9 @@
226 'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
227 'SmartServerRequestOpenBranchV2')
228 request_handlers.register_lazy(
229+ 'BzrDir.open_branchV3', 'bzrlib.smart.bzrdir',
230+ 'SmartServerRequestOpenBranchV3')
231+request_handlers.register_lazy(
232 'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
233 request_handlers.register_lazy(
234 'get', 'bzrlib.smart.vfs', 'GetRequest')
235
236=== modified file 'bzrlib/tests/blackbox/test_shared_repository.py'
237--- bzrlib/tests/blackbox/test_shared_repository.py 2009-09-23 23:58:10 +0000
238+++ bzrlib/tests/blackbox/test_shared_repository.py 2010-01-15 05:14:10 +0000
239@@ -18,7 +18,7 @@
240
241 import os
242
243-from bzrlib.bzrdir import BzrDir
244+from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
245 import bzrlib.errors as errors
246 from bzrlib.tests import TestCaseInTempDir
247
248@@ -120,3 +120,21 @@
249 # become necessary for this use case. Please do not adjust this number
250 # upwards without agreement from bzr's network support maintainers.
251 self.assertLength(15, self.hpss_calls)
252+
253+ def test_notification_on_branch_from_repository(self):
254+ out, err = self.run_bzr("init-repository -q a")
255+ self.assertEqual(out, "")
256+ self.assertEqual(err, "")
257+ dir = BzrDir.open('a')
258+ dir.open_repository() # there is a repository there
259+ e = self.assertRaises(errors.NotBranchError, dir.open_branch)
260+ self.assertContainsRe(str(e), "location is a repository")
261+
262+ def test_notification_on_branch_from_nonrepository(self):
263+ fmt = BzrDirMetaFormat1()
264+ t = self.get_transport()
265+ t.mkdir('a')
266+ dir = fmt.initialize_on_transport(t.clone('a'))
267+ self.assertRaises(errors.NoRepositoryPresent, dir.open_repository)
268+ e = self.assertRaises(errors.NotBranchError, dir.open_branch)
269+ self.assertNotContainsRe(str(e), "location is a repository")
270
271=== modified file 'bzrlib/tests/per_branch/test_push.py'
272--- bzrlib/tests/per_branch/test_push.py 2009-09-24 05:31:23 +0000
273+++ bzrlib/tests/per_branch/test_push.py 2010-01-15 05:14:10 +0000
274@@ -414,7 +414,7 @@
275 self.empty_branch.push(target)
276 self.assertEqual(
277 ['BzrDir.open_2.1',
278- 'BzrDir.open_branchV2',
279+ 'BzrDir.open_branchV3',
280 'BzrDir.find_repositoryV3',
281 'Branch.get_stacked_on_url',
282 'Branch.lock_write',
283
284=== modified file 'bzrlib/tests/test_errors.py'
285--- bzrlib/tests/test_errors.py 2010-01-08 06:33:05 +0000
286+++ bzrlib/tests/test_errors.py 2010-01-15 05:14:10 +0000
287@@ -623,6 +623,38 @@
288 self.assertEqual(
289 'Repository dummy repo cannot suspend a write group.', str(err))
290
291+ def test_not_branch_no_args(self):
292+ err = errors.NotBranchError('path')
293+ self.assertEqual('Not a branch: "path".', str(err))
294+
295+ def test_not_branch_bzrdir_with_repo(self):
296+ bzrdir = self.make_repository('repo').bzrdir
297+ err = errors.NotBranchError('path', bzrdir=bzrdir)
298+ self.assertEqual(
299+ 'Not a branch: "path": location is a repository.', str(err))
300+
301+ def test_not_branch_bzrdir_without_repo(self):
302+ bzrdir = self.make_bzrdir('bzrdir')
303+ err = errors.NotBranchError('path', bzrdir=bzrdir)
304+ self.assertEqual('Not a branch: "path".', str(err))
305+
306+ def test_not_branch_laziness(self):
307+ real_bzrdir = self.make_bzrdir('path')
308+ class FakeBzrDir(object):
309+ def __init__(self):
310+ self.calls = []
311+ def open_repository(self):
312+ self.calls.append('open_repository')
313+ raise errors.NoRepositoryPresent(real_bzrdir)
314+ fake_bzrdir = FakeBzrDir()
315+ err = errors.NotBranchError('path', bzrdir=fake_bzrdir)
316+ self.assertEqual([], fake_bzrdir.calls)
317+ str(err)
318+ self.assertEqual(['open_repository'], fake_bzrdir.calls)
319+ # Stringifying twice doesn't try to open a repository twice.
320+ str(err)
321+ self.assertEqual(['open_repository'], fake_bzrdir.calls)
322+
323
324 class PassThroughError(errors.BzrError):
325
326
327=== modified file 'bzrlib/tests/test_remote.py'
328--- bzrlib/tests/test_remote.py 2009-12-16 22:29:31 +0000
329+++ bzrlib/tests/test_remote.py 2010-01-15 05:14:10 +0000
330@@ -440,7 +440,7 @@
331 'BzrDir.cloning_metadir', ('quack/', 'False'),
332 'error', ('BranchReference',)),
333 client.add_expected_call(
334- 'BzrDir.open_branchV2', ('quack/',),
335+ 'BzrDir.open_branchV3', ('quack/',),
336 'success', ('ref', self.get_url('referenced'))),
337 a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
338 _client=client)
339@@ -531,7 +531,7 @@
340 self.make_branch('.')
341 a_dir = BzrDir.open(self.get_url('.'))
342 self.reset_smart_call_log()
343- verb = 'BzrDir.open_branchV2'
344+ verb = 'BzrDir.open_branchV3'
345 self.disable_verb(verb)
346 format = a_dir.open_branch()
347 call_count = len([call for call in self.hpss_calls if
348@@ -547,7 +547,7 @@
349 transport = transport.clone('quack')
350 client = FakeClient(transport.base)
351 client.add_expected_call(
352- 'BzrDir.open_branchV2', ('quack/',),
353+ 'BzrDir.open_branchV3', ('quack/',),
354 'success', ('branch', branch_network_name))
355 client.add_expected_call(
356 'BzrDir.find_repositoryV3', ('quack/',),
357@@ -572,7 +572,7 @@
358 _client=client)
359 self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
360 self.assertEqual(
361- [('call', 'BzrDir.open_branchV2', ('quack/',))],
362+ [('call', 'BzrDir.open_branchV3', ('quack/',))],
363 client._calls)
364
365 def test__get_tree_branch(self):
366@@ -602,7 +602,7 @@
367 network_name = reference_format.network_name()
368 branch_network_name = self.get_branch_format().network_name()
369 client.add_expected_call(
370- 'BzrDir.open_branchV2', ('~hello/',),
371+ 'BzrDir.open_branchV3', ('~hello/',),
372 'success', ('branch', branch_network_name))
373 client.add_expected_call(
374 'BzrDir.find_repositoryV3', ('~hello/',),
375@@ -1190,7 +1190,7 @@
376 client = FakeClient(self.get_url())
377 branch_network_name = self.get_branch_format().network_name()
378 client.add_expected_call(
379- 'BzrDir.open_branchV2', ('stacked/',),
380+ 'BzrDir.open_branchV3', ('stacked/',),
381 'success', ('branch', branch_network_name))
382 client.add_expected_call(
383 'BzrDir.find_repositoryV3', ('stacked/',),
384@@ -1226,7 +1226,7 @@
385 client = FakeClient(self.get_url())
386 branch_network_name = self.get_branch_format().network_name()
387 client.add_expected_call(
388- 'BzrDir.open_branchV2', ('stacked/',),
389+ 'BzrDir.open_branchV3', ('stacked/',),
390 'success', ('branch', branch_network_name))
391 client.add_expected_call(
392 'BzrDir.find_repositoryV3', ('stacked/',),
393@@ -2775,6 +2775,15 @@
394 expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
395 self.assertEqual(expected_error, translated_error)
396
397+ def test_nobranch_one_arg(self):
398+ bzrdir = self.make_bzrdir('')
399+ translated_error = self.translateTuple(
400+ ('nobranch', 'extra detail'), bzrdir=bzrdir)
401+ expected_error = errors.NotBranchError(
402+ path=bzrdir.root_transport.base,
403+ detail='extra detail')
404+ self.assertEqual(expected_error, translated_error)
405+
406 def test_LockContention(self):
407 translated_error = self.translateTuple(('LockContention',))
408 expected_error = errors.LockContention('(remote lock)')
409
410=== modified file 'bzrlib/tests/test_smart.py'
411--- bzrlib/tests/test_smart.py 2009-10-29 00:16:50 +0000
412+++ bzrlib/tests/test_smart.py 2010-01-15 05:14:10 +0000
413@@ -524,6 +524,14 @@
414 self.assertEqual(SmartServerResponse(('ok', reference_url)),
415 request.execute('reference'))
416
417+ def test_notification_on_branch_from_repository(self):
418+ """When there is a repository, the error should return details."""
419+ backing = self.get_transport()
420+ request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
421+ repo = self.make_repository('.')
422+ self.assertEqual(SmartServerResponse(('nobranch',)),
423+ request.execute(''))
424+
425
426 class TestSmartServerRequestOpenBranchV2(TestCaseWithChrootedTransport):
427
428@@ -575,6 +583,74 @@
429 response)
430 self.assertLength(1, opened_branches)
431
432+ def test_notification_on_branch_from_repository(self):
433+ """When there is a repository, the error should return details."""
434+ backing = self.get_transport()
435+ request = smart.bzrdir.SmartServerRequestOpenBranchV2(backing)
436+ repo = self.make_repository('.')
437+ self.assertEqual(SmartServerResponse(('nobranch',)),
438+ request.execute(''))
439+
440+
441+class TestSmartServerRequestOpenBranchV3(TestCaseWithChrootedTransport):
442+
443+ def test_no_branch(self):
444+ """When there is no branch, ('nobranch', ) is returned."""
445+ backing = self.get_transport()
446+ self.make_bzrdir('.')
447+ request = smart.bzrdir.SmartServerRequestOpenBranchV3(backing)
448+ self.assertEqual(SmartServerResponse(('nobranch',)),
449+ request.execute(''))
450+
451+ def test_branch(self):
452+ """When there is a branch, 'ok' is returned."""
453+ backing = self.get_transport()
454+ expected = self.make_branch('.')._format.network_name()
455+ request = smart.bzrdir.SmartServerRequestOpenBranchV3(backing)
456+ self.assertEqual(SuccessfulSmartServerResponse(('branch', expected)),
457+ request.execute(''))
458+
459+ def test_branch_reference(self):
460+ """When there is a branch reference, the reference URL is returned."""
461+ self.vfs_transport_factory = local.LocalURLServer
462+ backing = self.get_transport()
463+ request = smart.bzrdir.SmartServerRequestOpenBranchV3(backing)
464+ branch = self.make_branch('branch')
465+ checkout = branch.create_checkout('reference',lightweight=True)
466+ reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
467+ self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
468+ self.assertEqual(SuccessfulSmartServerResponse(('ref', reference_url)),
469+ request.execute('reference'))
470+
471+ def test_stacked_branch(self):
472+ """Opening a stacked branch does not open the stacked-on branch."""
473+ trunk = self.make_branch('trunk')
474+ feature = self.make_branch('feature')
475+ feature.set_stacked_on_url(trunk.base)
476+ opened_branches = []
477+ Branch.hooks.install_named_hook('open', opened_branches.append, None)
478+ backing = self.get_transport()
479+ request = smart.bzrdir.SmartServerRequestOpenBranchV3(backing)
480+ request.setup_jail()
481+ try:
482+ response = request.execute('feature')
483+ finally:
484+ request.teardown_jail()
485+ expected_format = feature._format.network_name()
486+ self.assertEqual(
487+ SuccessfulSmartServerResponse(('branch', expected_format)),
488+ response)
489+ self.assertLength(1, opened_branches)
490+
491+ def test_notification_on_branch_from_repository(self):
492+ """When there is a repository, the error should return details."""
493+ backing = self.get_transport()
494+ request = smart.bzrdir.SmartServerRequestOpenBranchV3(backing)
495+ repo = self.make_repository('.')
496+ self.assertEqual(
497+ SmartServerResponse(('nobranch', 'location is a repository')),
498+ request.execute(''))
499+
500
501 class TestSmartServerRequestRevisionHistory(tests.TestCaseWithMemoryTransport):
502
503@@ -1771,6 +1847,8 @@
504 smart.bzrdir.SmartServerRequestOpenBranch)
505 self.assertHandlerEqual('BzrDir.open_branchV2',
506 smart.bzrdir.SmartServerRequestOpenBranchV2)
507+ self.assertHandlerEqual('BzrDir.open_branchV3',
508+ smart.bzrdir.SmartServerRequestOpenBranchV3)
509 self.assertHandlerEqual('PackRepository.autopack',
510 smart.packrepository.SmartServerPackRepositoryAutopack)
511 self.assertHandlerEqual('Repository.gather_stats',