102
135
b = BzrDir.open_from_transport(self.transport).open_branch()
103
136
self.assertStartsWith(str(b), 'RemoteBranch(')
138
def test_remote_branch_format_supports_stacking(self):
140
self.make_branch('unstackable', format='pack-0.92')
141
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
142
self.assertFalse(b._format.supports_stacking())
143
self.make_branch('stackable', format='1.9')
144
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
145
self.assertTrue(b._format.supports_stacking())
147
def test_remote_repo_format_supports_external_references(self):
149
bd = self.make_bzrdir('unstackable', format='pack-0.92')
150
r = bd.create_repository()
151
self.assertFalse(r._format.supports_external_lookups)
152
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
153
self.assertFalse(r._format.supports_external_lookups)
154
bd = self.make_bzrdir('stackable', format='1.9')
155
r = bd.create_repository()
156
self.assertTrue(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
158
self.assertTrue(r._format.supports_external_lookups)
160
def test_remote_branch_set_append_revisions_only(self):
161
# Make a format 1.9 branch, which supports append_revisions_only
162
branch = self.make_branch('branch', format='1.9')
163
config = branch.get_config()
164
branch.set_append_revisions_only(True)
166
'True', config.get_user_option('append_revisions_only'))
167
branch.set_append_revisions_only(False)
169
'False', config.get_user_option('append_revisions_only'))
171
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
172
branch = self.make_branch('branch', format='knit')
173
config = branch.get_config()
175
errors.UpgradeRequired, branch.set_append_revisions_only, True)
106
178
class FakeProtocol(object):
107
179
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
109
def __init__(self, body):
110
self._body_buffer = StringIO(body)
181
def __init__(self, body, fake_client):
183
self._body_buffer = None
184
self._fake_client = fake_client
112
186
def read_body_bytes(self, count=-1):
113
return self._body_buffer.read(count)
187
if self._body_buffer is None:
188
self._body_buffer = StringIO(self.body)
189
bytes = self._body_buffer.read(count)
190
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
191
self._fake_client.expecting_body = False
194
def cancel_read_body(self):
195
self._fake_client.expecting_body = False
197
def read_streamed_body(self):
116
201
class FakeClient(_SmartClient):
117
202
"""Lookalike for _SmartClient allowing testing."""
119
def __init__(self, responses):
120
# We don't call the super init because there is no medium.
121
"""create a FakeClient.
123
:param respones: A list of response-tuple, body-data pairs to be sent
126
self.responses = responses
204
def __init__(self, fake_medium_base='fake base'):
205
"""Create a FakeClient."""
208
self.expecting_body = False
209
# if non-None, this is the list of expected calls, with only the
210
# method name and arguments included. the body might be hard to
211
# compute so is not included. If a call is None, that call can
213
self._expected_calls = None
214
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
216
def add_expected_call(self, call_name, call_args, response_type,
217
response_args, response_body=None):
218
if self._expected_calls is None:
219
self._expected_calls = []
220
self._expected_calls.append((call_name, call_args))
221
self.responses.append((response_type, response_args, response_body))
223
def add_success_response(self, *args):
224
self.responses.append(('success', args, None))
226
def add_success_response_with_body(self, body, *args):
227
self.responses.append(('success', args, body))
228
if self._expected_calls is not None:
229
self._expected_calls.append(None)
231
def add_error_response(self, *args):
232
self.responses.append(('error', args))
234
def add_unknown_method_response(self, verb):
235
self.responses.append(('unknown', verb))
237
def finished_test(self):
238
if self._expected_calls:
239
raise AssertionError("%r finished but was still expecting %r"
240
% (self, self._expected_calls[0]))
242
def _get_next_response(self):
244
response_tuple = self.responses.pop(0)
245
except IndexError, e:
246
raise AssertionError("%r didn't expect any more calls"
248
if response_tuple[0] == 'unknown':
249
raise errors.UnknownSmartMethod(response_tuple[1])
250
elif response_tuple[0] == 'error':
251
raise errors.ErrorFromSmartServer(response_tuple[1])
252
return response_tuple
254
def _check_call(self, method, args):
255
if self._expected_calls is None:
256
# the test should be updated to say what it expects
259
next_call = self._expected_calls.pop(0)
261
raise AssertionError("%r didn't expect any more calls "
263
% (self, method, args,))
264
if next_call is None:
266
if method != next_call[0] or args != next_call[1]:
267
raise AssertionError("%r expected %r%r "
269
% (self, next_call[0], next_call[1], method, args,))
129
271
def call(self, method, *args):
272
self._check_call(method, args)
130
273
self._calls.append(('call', method, args))
131
return self.responses.pop(0)[0]
274
return self._get_next_response()[1]
133
276
def call_expecting_body(self, method, *args):
277
self._check_call(method, args)
134
278
self._calls.append(('call_expecting_body', method, args))
135
result = self.responses.pop(0)
136
return result[0], FakeProtocol(result[1])
139
class TestBzrDirOpenBranch(tests.TestCase):
279
result = self._get_next_response()
280
self.expecting_body = True
281
return result[1], FakeProtocol(result[2], self)
283
def call_with_body_bytes(self, method, args, body):
284
self._check_call(method, args)
285
self._calls.append(('call_with_body_bytes', method, args, body))
286
result = self._get_next_response()
287
return result[1], FakeProtocol(result[2], self)
289
def call_with_body_bytes_expecting_body(self, method, args, body):
290
self._check_call(method, args)
291
self._calls.append(('call_with_body_bytes_expecting_body', method,
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_stream(self, args, stream):
298
# Explicitly consume the stream before checking for an error, because
299
# that's what happens a real medium.
300
stream = list(stream)
301
self._check_call(args[0], args[1:])
302
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
303
result = self._get_next_response()
304
# The second value returned from call_with_body_stream is supposed to
305
# be a response_handler object, but so far no tests depend on that.
306
response_handler = None
307
return result[1], response_handler
310
class FakeMedium(medium.SmartClientMedium):
312
def __init__(self, client_calls, base):
313
medium.SmartClientMedium.__init__(self, base)
314
self._client_calls = client_calls
316
def disconnect(self):
317
self._client_calls.append(('disconnect medium',))
320
class TestVfsHas(tests.TestCase):
322
def test_unicode_path(self):
323
client = FakeClient('/')
324
client.add_success_response('yes',)
325
transport = RemoteTransport('bzr://localhost/', _client=client)
326
filename = u'/hell\u00d8'.encode('utf8')
327
result = transport.has(filename)
329
[('call', 'has', (filename,))],
331
self.assertTrue(result)
334
class TestRemote(tests.TestCaseWithMemoryTransport):
336
def get_branch_format(self):
337
reference_bzrdir_format = bzrdir.format_registry.get('default')()
338
return reference_bzrdir_format.get_branch_format()
340
def get_repo_format(self):
341
reference_bzrdir_format = bzrdir.format_registry.get('default')()
342
return reference_bzrdir_format.repository_format
344
def assertFinished(self, fake_client):
345
"""Assert that all of a FakeClient's expected calls have occurred."""
346
fake_client.finished_test()
349
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
350
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
352
def assertRemotePath(self, expected, client_base, transport_base):
353
"""Assert that the result of
354
SmartClientMedium.remote_path_from_transport is the expected value for
355
a given client_base and transport_base.
357
client_medium = medium.SmartClientMedium(client_base)
358
transport = get_transport(transport_base)
359
result = client_medium.remote_path_from_transport(transport)
360
self.assertEqual(expected, result)
362
def test_remote_path_from_transport(self):
363
"""SmartClientMedium.remote_path_from_transport calculates a URL for
364
the given transport relative to the root of the client base URL.
366
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
367
self.assertRemotePath(
368
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
370
def assertRemotePathHTTP(self, expected, transport_base, relpath):
371
"""Assert that the result of
372
HttpTransportBase.remote_path_from_transport is the expected value for
373
a given transport_base and relpath of that transport. (Note that
374
HttpTransportBase is a subclass of SmartClientMedium)
376
base_transport = get_transport(transport_base)
377
client_medium = base_transport.get_smart_medium()
378
cloned_transport = base_transport.clone(relpath)
379
result = client_medium.remote_path_from_transport(cloned_transport)
380
self.assertEqual(expected, result)
382
def test_remote_path_from_transport_http(self):
383
"""Remote paths for HTTP transports are calculated differently to other
384
transports. They are just relative to the client base, not the root
385
directory of the host.
387
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
388
self.assertRemotePathHTTP(
389
'../xyz/', scheme + '//host/path', '../xyz/')
390
self.assertRemotePathHTTP(
391
'xyz/', scheme + '//host/path', 'xyz/')
394
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
395
"""Tests for the behaviour of client_medium.remote_is_at_least."""
397
def test_initially_unlimited(self):
398
"""A fresh medium assumes that the remote side supports all
401
client_medium = medium.SmartClientMedium('dummy base')
402
self.assertFalse(client_medium._is_remote_before((99, 99)))
404
def test__remember_remote_is_before(self):
405
"""Calling _remember_remote_is_before ratchets down the known remote
408
client_medium = medium.SmartClientMedium('dummy base')
409
# Mark the remote side as being less than 1.6. The remote side may
411
client_medium._remember_remote_is_before((1, 6))
412
self.assertTrue(client_medium._is_remote_before((1, 6)))
413
self.assertFalse(client_medium._is_remote_before((1, 5)))
414
# Calling _remember_remote_is_before again with a lower value works.
415
client_medium._remember_remote_is_before((1, 5))
416
self.assertTrue(client_medium._is_remote_before((1, 5)))
417
# You cannot call _remember_remote_is_before with a larger value.
419
AssertionError, client_medium._remember_remote_is_before, (1, 9))
422
class TestBzrDirCloningMetaDir(TestRemote):
424
def test_backwards_compat(self):
425
self.setup_smart_server_with_call_log()
426
a_dir = self.make_bzrdir('.')
427
self.reset_smart_call_log()
428
verb = 'BzrDir.cloning_metadir'
429
self.disable_verb(verb)
430
format = a_dir.cloning_metadir()
431
call_count = len([call for call in self.hpss_calls if
432
call.call.method == verb])
433
self.assertEqual(1, call_count)
435
def test_branch_reference(self):
436
transport = self.get_transport('quack')
437
referenced = self.make_branch('referenced')
438
expected = referenced.bzrdir.cloning_metadir()
439
client = FakeClient(transport.base)
440
client.add_expected_call(
441
'BzrDir.cloning_metadir', ('quack/', 'False'),
442
'error', ('BranchReference',)),
443
client.add_expected_call(
444
'BzrDir.open_branchV2', ('quack/',),
445
'success', ('ref', self.get_url('referenced'))),
446
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
448
result = a_bzrdir.cloning_metadir()
449
# We should have got a control dir matching the referenced branch.
450
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
451
self.assertEqual(expected._repository_format, result._repository_format)
452
self.assertEqual(expected._branch_format, result._branch_format)
453
self.assertFinished(client)
455
def test_current_server(self):
456
transport = self.get_transport('.')
457
transport = transport.clone('quack')
458
self.make_bzrdir('quack')
459
client = FakeClient(transport.base)
460
reference_bzrdir_format = bzrdir.format_registry.get('default')()
461
control_name = reference_bzrdir_format.network_name()
462
client.add_expected_call(
463
'BzrDir.cloning_metadir', ('quack/', 'False'),
464
'success', (control_name, '', ('branch', ''))),
465
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
467
result = a_bzrdir.cloning_metadir()
468
# We should have got a reference control dir with default branch and
469
# repository formats.
470
# This pokes a little, just to be sure.
471
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
472
self.assertEqual(None, result._repository_format)
473
self.assertEqual(None, result._branch_format)
474
self.assertFinished(client)
477
class TestBzrDirOpenBranch(TestRemote):
479
def test_backwards_compat(self):
480
self.setup_smart_server_with_call_log()
481
self.make_branch('.')
482
a_dir = BzrDir.open(self.get_url('.'))
483
self.reset_smart_call_log()
484
verb = 'BzrDir.open_branchV2'
485
self.disable_verb(verb)
486
format = a_dir.open_branch()
487
call_count = len([call for call in self.hpss_calls if
488
call.call.method == verb])
489
self.assertEqual(1, call_count)
141
491
def test_branch_present(self):
142
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
492
reference_format = self.get_repo_format()
493
network_name = reference_format.network_name()
494
branch_network_name = self.get_branch_format().network_name()
143
495
transport = MemoryTransport()
144
496
transport.mkdir('quack')
145
497
transport = transport.clone('quack')
146
bzrdir = RemoteBzrDir(transport, _client=client)
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.open_branchV2', ('quack/',),
501
'success', ('branch', branch_network_name))
502
client.add_expected_call(
503
'BzrDir.find_repositoryV3', ('quack/',),
504
'success', ('ok', '', 'no', 'no', 'no', network_name))
505
client.add_expected_call(
506
'Branch.get_stacked_on_url', ('quack/',),
507
'error', ('NotStacked',))
508
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
147
510
result = bzrdir.open_branch()
149
[('call', 'BzrDir.open_branch', ('///quack/',)),
150
('call', 'BzrDir.find_repository', ('///quack/',))],
152
511
self.assertIsInstance(result, RemoteBranch)
153
512
self.assertEqual(bzrdir, result.bzrdir)
513
self.assertFinished(client)
155
515
def test_branch_missing(self):
156
client = FakeClient([(('nobranch',), )])
157
516
transport = MemoryTransport()
158
517
transport.mkdir('quack')
159
518
transport = transport.clone('quack')
160
bzrdir = RemoteBzrDir(transport, _client=client)
519
client = FakeClient(transport.base)
520
client.add_error_response('nobranch')
521
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
161
523
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
162
524
self.assertEqual(
163
[('call', 'BzrDir.open_branch', ('///quack/',))],
525
[('call', 'BzrDir.open_branchV2', ('quack/',))],
166
def check_open_repository(self, rich_root, subtrees):
528
def test__get_tree_branch(self):
529
# _get_tree_branch is a form of open_branch, but it should only ask for
530
# branch opening, not any other network requests.
533
calls.append("Called")
535
transport = MemoryTransport()
536
# no requests on the network - catches other api calls being made.
537
client = FakeClient(transport.base)
538
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
540
# patch the open_branch call to record that it was called.
541
bzrdir.open_branch = open_branch
542
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
543
self.assertEqual(["Called"], calls)
544
self.assertEqual([], client._calls)
546
def test_url_quoting_of_path(self):
547
# Relpaths on the wire should not be URL-escaped. So "~" should be
548
# transmitted as "~", not "%7E".
549
transport = RemoteTCPTransport('bzr://localhost/~hello/')
550
client = FakeClient(transport.base)
551
reference_format = self.get_repo_format()
552
network_name = reference_format.network_name()
553
branch_network_name = self.get_branch_format().network_name()
554
client.add_expected_call(
555
'BzrDir.open_branchV2', ('~hello/',),
556
'success', ('branch', branch_network_name))
557
client.add_expected_call(
558
'BzrDir.find_repositoryV3', ('~hello/',),
559
'success', ('ok', '', 'no', 'no', 'no', network_name))
560
client.add_expected_call(
561
'Branch.get_stacked_on_url', ('~hello/',),
562
'error', ('NotStacked',))
563
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
result = bzrdir.open_branch()
566
self.assertFinished(client)
568
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
569
reference_format = self.get_repo_format()
570
network_name = reference_format.network_name()
571
transport = MemoryTransport()
572
transport.mkdir('quack')
573
transport = transport.clone('quack')
168
575
rich_response = 'yes'
200
609
RemoteBzrDirFormat.probe_transport, OldServerTransport())
612
class TestBzrDirCreateBranch(TestRemote):
614
def test_backwards_compat(self):
615
self.setup_smart_server_with_call_log()
616
repo = self.make_repository('.')
617
self.reset_smart_call_log()
618
self.disable_verb('BzrDir.create_branch')
619
branch = repo.bzrdir.create_branch()
620
create_branch_call_count = len([call for call in self.hpss_calls if
621
call.call.method == 'BzrDir.create_branch'])
622
self.assertEqual(1, create_branch_call_count)
624
def test_current_server(self):
625
transport = self.get_transport('.')
626
transport = transport.clone('quack')
627
self.make_repository('quack')
628
client = FakeClient(transport.base)
629
reference_bzrdir_format = bzrdir.format_registry.get('default')()
630
reference_format = reference_bzrdir_format.get_branch_format()
631
network_name = reference_format.network_name()
632
reference_repo_fmt = reference_bzrdir_format.repository_format
633
reference_repo_name = reference_repo_fmt.network_name()
634
client.add_expected_call(
635
'BzrDir.create_branch', ('quack/', network_name),
636
'success', ('ok', network_name, '', 'no', 'no', 'yes',
637
reference_repo_name))
638
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
640
branch = a_bzrdir.create_branch()
641
# We should have got a remote branch
642
self.assertIsInstance(branch, remote.RemoteBranch)
643
# its format should have the settings from the response
644
format = branch._format
645
self.assertEqual(network_name, format.network_name())
648
class TestBzrDirCreateRepository(TestRemote):
650
def test_backwards_compat(self):
651
self.setup_smart_server_with_call_log()
652
bzrdir = self.make_bzrdir('.')
653
self.reset_smart_call_log()
654
self.disable_verb('BzrDir.create_repository')
655
repo = bzrdir.create_repository()
656
create_repo_call_count = len([call for call in self.hpss_calls if
657
call.call.method == 'BzrDir.create_repository'])
658
self.assertEqual(1, create_repo_call_count)
660
def test_current_server(self):
661
transport = self.get_transport('.')
662
transport = transport.clone('quack')
663
self.make_bzrdir('quack')
664
client = FakeClient(transport.base)
665
reference_bzrdir_format = bzrdir.format_registry.get('default')()
666
reference_format = reference_bzrdir_format.repository_format
667
network_name = reference_format.network_name()
668
client.add_expected_call(
669
'BzrDir.create_repository', ('quack/',
670
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
672
'success', ('ok', 'yes', 'yes', 'yes', network_name))
673
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
675
repo = a_bzrdir.create_repository()
676
# We should have got a remote repository
677
self.assertIsInstance(repo, remote.RemoteRepository)
678
# its format should have the settings from the response
679
format = repo._format
680
self.assertTrue(format.rich_root_data)
681
self.assertTrue(format.supports_tree_reference)
682
self.assertTrue(format.supports_external_lookups)
683
self.assertEqual(network_name, format.network_name())
686
class TestBzrDirOpenRepository(TestRemote):
688
def test_backwards_compat_1_2_3(self):
689
# fallback all the way to the first version.
690
reference_format = self.get_repo_format()
691
network_name = reference_format.network_name()
692
client = FakeClient('bzr://example.com/')
693
client.add_unknown_method_response('BzrDir.find_repositoryV3')
694
client.add_unknown_method_response('BzrDir.find_repositoryV2')
695
client.add_success_response('ok', '', 'no', 'no')
696
# A real repository instance will be created to determine the network
698
client.add_success_response_with_body(
699
"Bazaar-NG meta directory, format 1\n", 'ok')
700
client.add_success_response_with_body(
701
reference_format.get_format_string(), 'ok')
702
# PackRepository wants to do a stat
703
client.add_success_response('stat', '0', '65535')
704
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
706
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
708
repo = bzrdir.open_repository()
710
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
711
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
712
('call', 'BzrDir.find_repository', ('quack/',)),
713
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
714
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
715
('call', 'stat', ('/quack/.bzr/repository',)),
718
self.assertEqual(network_name, repo._format.network_name())
720
def test_backwards_compat_2(self):
721
# fallback to find_repositoryV2
722
reference_format = self.get_repo_format()
723
network_name = reference_format.network_name()
724
client = FakeClient('bzr://example.com/')
725
client.add_unknown_method_response('BzrDir.find_repositoryV3')
726
client.add_success_response('ok', '', 'no', 'no', 'no')
727
# A real repository instance will be created to determine the network
729
client.add_success_response_with_body(
730
"Bazaar-NG meta directory, format 1\n", 'ok')
731
client.add_success_response_with_body(
732
reference_format.get_format_string(), 'ok')
733
# PackRepository wants to do a stat
734
client.add_success_response('stat', '0', '65535')
735
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
737
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
739
repo = bzrdir.open_repository()
741
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
742
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
743
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
744
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
745
('call', 'stat', ('/quack/.bzr/repository',)),
748
self.assertEqual(network_name, repo._format.network_name())
750
def test_current_server(self):
751
reference_format = self.get_repo_format()
752
network_name = reference_format.network_name()
753
transport = MemoryTransport()
754
transport.mkdir('quack')
755
transport = transport.clone('quack')
756
client = FakeClient(transport.base)
757
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
758
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
760
repo = bzrdir.open_repository()
762
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
764
self.assertEqual(network_name, repo._format.network_name())
767
class TestBzrDirFormatInitializeEx(TestRemote):
769
def test_success(self):
770
"""Simple test for typical successful call."""
771
fmt = bzrdir.RemoteBzrDirFormat()
772
default_format_name = BzrDirFormat.get_default_format().network_name()
773
transport = self.get_transport()
774
client = FakeClient(transport.base)
775
client.add_expected_call(
776
'BzrDirFormat.initialize_ex_1.16',
777
(default_format_name, 'path', 'False', 'False', 'False', '',
778
'', '', '', 'False'),
780
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
781
'bzrdir fmt', 'False', '', '', 'repo lock token'))
782
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
783
# it's currently hard to test that without supplying a real remote
784
# transport connected to a real server.
785
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
786
transport, False, False, False, None, None, None, None, False)
787
self.assertFinished(client)
789
def test_error(self):
790
"""Error responses are translated, e.g. 'PermissionDenied' raises the
791
corresponding error from the client.
793
fmt = bzrdir.RemoteBzrDirFormat()
794
default_format_name = BzrDirFormat.get_default_format().network_name()
795
transport = self.get_transport()
796
client = FakeClient(transport.base)
797
client.add_expected_call(
798
'BzrDirFormat.initialize_ex_1.16',
799
(default_format_name, 'path', 'False', 'False', 'False', '',
800
'', '', '', 'False'),
802
('PermissionDenied', 'path', 'extra info'))
803
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
804
# it's currently hard to test that without supplying a real remote
805
# transport connected to a real server.
806
err = self.assertRaises(errors.PermissionDenied,
807
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
808
False, False, False, None, None, None, None, False)
809
self.assertEqual('path', err.path)
810
self.assertEqual(': extra info', err.extra)
811
self.assertFinished(client)
813
def test_error_from_real_server(self):
814
"""Integration test for error translation."""
815
transport = self.make_smart_server('foo')
816
transport = transport.clone('no-such-path')
817
fmt = bzrdir.RemoteBzrDirFormat()
818
err = self.assertRaises(errors.NoSuchFile,
819
fmt.initialize_on_transport_ex, transport, create_prefix=False)
203
822
class OldSmartClient(object):
204
823
"""A fake smart client for test_old_version that just returns a version one
205
824
response to the 'hello' (query version) command.
225
847
return OldSmartClient()
228
class TestBranchLastRevisionInfo(tests.TestCase):
850
class RemoteBzrDirTestCase(TestRemote):
852
def make_remote_bzrdir(self, transport, client):
853
"""Make a RemotebzrDir using 'client' as the _client."""
854
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
858
class RemoteBranchTestCase(RemoteBzrDirTestCase):
860
def lock_remote_branch(self, branch):
861
"""Trick a RemoteBranch into thinking it is locked."""
862
branch._lock_mode = 'w'
863
branch._lock_count = 2
864
branch._lock_token = 'branch token'
865
branch._repo_lock_token = 'repo token'
866
branch.repository._lock_mode = 'w'
867
branch.repository._lock_count = 2
868
branch.repository._lock_token = 'repo token'
870
def make_remote_branch(self, transport, client):
871
"""Make a RemoteBranch using 'client' as its _SmartClient.
873
A RemoteBzrDir and RemoteRepository will also be created to fill out
874
the RemoteBranch, albeit with stub values for some of their attributes.
876
# we do not want bzrdir to make any remote calls, so use False as its
877
# _client. If it tries to make a remote call, this will fail
879
bzrdir = self.make_remote_bzrdir(transport, False)
880
repo = RemoteRepository(bzrdir, None, _client=client)
881
branch_format = self.get_branch_format()
882
format = RemoteBranchFormat(network_name=branch_format.network_name())
883
return RemoteBranch(bzrdir, repo, _client=client, format=format)
886
class TestBranchGetParent(RemoteBranchTestCase):
888
def test_no_parent(self):
889
# in an empty branch we decode the response properly
890
transport = MemoryTransport()
891
client = FakeClient(transport.base)
892
client.add_expected_call(
893
'Branch.get_stacked_on_url', ('quack/',),
894
'error', ('NotStacked',))
895
client.add_expected_call(
896
'Branch.get_parent', ('quack/',),
898
transport.mkdir('quack')
899
transport = transport.clone('quack')
900
branch = self.make_remote_branch(transport, client)
901
result = branch.get_parent()
902
self.assertFinished(client)
903
self.assertEqual(None, result)
905
def test_parent_relative(self):
906
transport = MemoryTransport()
907
client = FakeClient(transport.base)
908
client.add_expected_call(
909
'Branch.get_stacked_on_url', ('kwaak/',),
910
'error', ('NotStacked',))
911
client.add_expected_call(
912
'Branch.get_parent', ('kwaak/',),
913
'success', ('../foo/',))
914
transport.mkdir('kwaak')
915
transport = transport.clone('kwaak')
916
branch = self.make_remote_branch(transport, client)
917
result = branch.get_parent()
918
self.assertEqual(transport.clone('../foo').base, result)
920
def test_parent_absolute(self):
921
transport = MemoryTransport()
922
client = FakeClient(transport.base)
923
client.add_expected_call(
924
'Branch.get_stacked_on_url', ('kwaak/',),
925
'error', ('NotStacked',))
926
client.add_expected_call(
927
'Branch.get_parent', ('kwaak/',),
928
'success', ('http://foo/',))
929
transport.mkdir('kwaak')
930
transport = transport.clone('kwaak')
931
branch = self.make_remote_branch(transport, client)
932
result = branch.get_parent()
933
self.assertEqual('http://foo/', result)
934
self.assertFinished(client)
937
class TestBranchSetParentLocation(RemoteBranchTestCase):
939
def test_no_parent(self):
940
# We call the verb when setting parent to None
941
transport = MemoryTransport()
942
client = FakeClient(transport.base)
943
client.add_expected_call(
944
'Branch.get_stacked_on_url', ('quack/',),
945
'error', ('NotStacked',))
946
client.add_expected_call(
947
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
949
transport.mkdir('quack')
950
transport = transport.clone('quack')
951
branch = self.make_remote_branch(transport, client)
952
branch._lock_token = 'b'
953
branch._repo_lock_token = 'r'
954
branch._set_parent_location(None)
955
self.assertFinished(client)
957
def test_parent(self):
958
transport = MemoryTransport()
959
client = FakeClient(transport.base)
960
client.add_expected_call(
961
'Branch.get_stacked_on_url', ('kwaak/',),
962
'error', ('NotStacked',))
963
client.add_expected_call(
964
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
966
transport.mkdir('kwaak')
967
transport = transport.clone('kwaak')
968
branch = self.make_remote_branch(transport, client)
969
branch._lock_token = 'b'
970
branch._repo_lock_token = 'r'
971
branch._set_parent_location('foo')
972
self.assertFinished(client)
974
def test_backwards_compat(self):
975
self.setup_smart_server_with_call_log()
976
branch = self.make_branch('.')
977
self.reset_smart_call_log()
978
verb = 'Branch.set_parent_location'
979
self.disable_verb(verb)
980
branch.set_parent('http://foo/')
981
self.assertLength(12, self.hpss_calls)
984
class TestBranchGetTagsBytes(RemoteBranchTestCase):
986
def test_backwards_compat(self):
987
self.setup_smart_server_with_call_log()
988
branch = self.make_branch('.')
989
self.reset_smart_call_log()
990
verb = 'Branch.get_tags_bytes'
991
self.disable_verb(verb)
992
branch.tags.get_tag_dict()
993
call_count = len([call for call in self.hpss_calls if
994
call.call.method == verb])
995
self.assertEqual(1, call_count)
997
def test_trivial(self):
998
transport = MemoryTransport()
999
client = FakeClient(transport.base)
1000
client.add_expected_call(
1001
'Branch.get_stacked_on_url', ('quack/',),
1002
'error', ('NotStacked',))
1003
client.add_expected_call(
1004
'Branch.get_tags_bytes', ('quack/',),
1006
transport.mkdir('quack')
1007
transport = transport.clone('quack')
1008
branch = self.make_remote_branch(transport, client)
1009
result = branch.tags.get_tag_dict()
1010
self.assertFinished(client)
1011
self.assertEqual({}, result)
1014
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1016
def test_trivial(self):
1017
transport = MemoryTransport()
1018
client = FakeClient(transport.base)
1019
client.add_expected_call(
1020
'Branch.get_stacked_on_url', ('quack/',),
1021
'error', ('NotStacked',))
1022
client.add_expected_call(
1023
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1025
transport.mkdir('quack')
1026
transport = transport.clone('quack')
1027
branch = self.make_remote_branch(transport, client)
1028
self.lock_remote_branch(branch)
1029
branch._set_tags_bytes('tags bytes')
1030
self.assertFinished(client)
1031
self.assertEqual('tags bytes', client._calls[-1][-1])
1033
def test_backwards_compatible(self):
1034
transport = MemoryTransport()
1035
client = FakeClient(transport.base)
1036
client.add_expected_call(
1037
'Branch.get_stacked_on_url', ('quack/',),
1038
'error', ('NotStacked',))
1039
client.add_expected_call(
1040
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1041
'unknown', ('Branch.set_tags_bytes',))
1042
transport.mkdir('quack')
1043
transport = transport.clone('quack')
1044
branch = self.make_remote_branch(transport, client)
1045
self.lock_remote_branch(branch)
1046
class StubRealBranch(object):
1049
def _set_tags_bytes(self, bytes):
1050
self.calls.append(('set_tags_bytes', bytes))
1051
real_branch = StubRealBranch()
1052
branch._real_branch = real_branch
1053
branch._set_tags_bytes('tags bytes')
1054
# Call a second time, to exercise the 'remote version already inferred'
1056
branch._set_tags_bytes('tags bytes')
1057
self.assertFinished(client)
1059
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1062
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
230
1064
def test_empty_branch(self):
231
1065
# in an empty branch we decode the response properly
232
client = FakeClient([(('ok', '0', 'null:'), )])
233
1066
transport = MemoryTransport()
1067
client = FakeClient(transport.base)
1068
client.add_expected_call(
1069
'Branch.get_stacked_on_url', ('quack/',),
1070
'error', ('NotStacked',))
1071
client.add_expected_call(
1072
'Branch.last_revision_info', ('quack/',),
1073
'success', ('ok', '0', 'null:'))
234
1074
transport.mkdir('quack')
235
1075
transport = transport.clone('quack')
236
# we do not want bzrdir to make any remote calls
237
bzrdir = RemoteBzrDir(transport, _client=False)
238
branch = RemoteBranch(bzrdir, None, _client=client)
1076
branch = self.make_remote_branch(transport, client)
239
1077
result = branch.last_revision_info()
242
[('call', 'Branch.last_revision_info', ('///quack/',))],
1078
self.assertFinished(client)
244
1079
self.assertEqual((0, NULL_REVISION), result)
246
1081
def test_non_empty_branch(self):
247
1082
# in a non-empty branch we also decode the response properly
248
1083
revid = u'\xc8'.encode('utf8')
249
client = FakeClient([(('ok', '2', revid), )])
250
1084
transport = MemoryTransport()
1085
client = FakeClient(transport.base)
1086
client.add_expected_call(
1087
'Branch.get_stacked_on_url', ('kwaak/',),
1088
'error', ('NotStacked',))
1089
client.add_expected_call(
1090
'Branch.last_revision_info', ('kwaak/',),
1091
'success', ('ok', '2', revid))
251
1092
transport.mkdir('kwaak')
252
1093
transport = transport.clone('kwaak')
253
# we do not want bzrdir to make any remote calls
254
bzrdir = RemoteBzrDir(transport, _client=False)
255
branch = RemoteBranch(bzrdir, None, _client=client)
1094
branch = self.make_remote_branch(transport, client)
256
1095
result = branch.last_revision_info()
259
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
261
1096
self.assertEqual((2, revid), result)
264
class TestBranchSetLastRevision(tests.TestCase):
1099
class TestBranch_get_stacked_on_url(TestRemote):
1100
"""Test Branch._get_stacked_on_url rpc"""
1102
def test_get_stacked_on_invalid_url(self):
1103
# test that asking for a stacked on url the server can't access works.
1104
# This isn't perfect, but then as we're in the same process there
1105
# really isn't anything we can do to be 100% sure that the server
1106
# doesn't just open in - this test probably needs to be rewritten using
1107
# a spawn()ed server.
1108
stacked_branch = self.make_branch('stacked', format='1.9')
1109
memory_branch = self.make_branch('base', format='1.9')
1110
vfs_url = self.get_vfs_only_url('base')
1111
stacked_branch.set_stacked_on_url(vfs_url)
1112
transport = stacked_branch.bzrdir.root_transport
1113
client = FakeClient(transport.base)
1114
client.add_expected_call(
1115
'Branch.get_stacked_on_url', ('stacked/',),
1116
'success', ('ok', vfs_url))
1117
# XXX: Multiple calls are bad, this second call documents what is
1119
client.add_expected_call(
1120
'Branch.get_stacked_on_url', ('stacked/',),
1121
'success', ('ok', vfs_url))
1122
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1124
repo_fmt = remote.RemoteRepositoryFormat()
1125
repo_fmt._custom_format = stacked_branch.repository._format
1126
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1128
result = branch.get_stacked_on_url()
1129
self.assertEqual(vfs_url, result)
1131
def test_backwards_compatible(self):
1132
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1133
base_branch = self.make_branch('base', format='1.6')
1134
stacked_branch = self.make_branch('stacked', format='1.6')
1135
stacked_branch.set_stacked_on_url('../base')
1136
client = FakeClient(self.get_url())
1137
branch_network_name = self.get_branch_format().network_name()
1138
client.add_expected_call(
1139
'BzrDir.open_branchV2', ('stacked/',),
1140
'success', ('branch', branch_network_name))
1141
client.add_expected_call(
1142
'BzrDir.find_repositoryV3', ('stacked/',),
1143
'success', ('ok', '', 'no', 'no', 'yes',
1144
stacked_branch.repository._format.network_name()))
1145
# called twice, once from constructor and then again by us
1146
client.add_expected_call(
1147
'Branch.get_stacked_on_url', ('stacked/',),
1148
'unknown', ('Branch.get_stacked_on_url',))
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('stacked/',),
1151
'unknown', ('Branch.get_stacked_on_url',))
1152
# this will also do vfs access, but that goes direct to the transport
1153
# and isn't seen by the FakeClient.
1154
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1155
remote.RemoteBzrDirFormat(), _client=client)
1156
branch = bzrdir.open_branch()
1157
result = branch.get_stacked_on_url()
1158
self.assertEqual('../base', result)
1159
self.assertFinished(client)
1160
# it's in the fallback list both for the RemoteRepository and its vfs
1162
self.assertEqual(1, len(branch.repository._fallback_repositories))
1164
len(branch.repository._real_repository._fallback_repositories))
1166
def test_get_stacked_on_real_branch(self):
1167
base_branch = self.make_branch('base', format='1.6')
1168
stacked_branch = self.make_branch('stacked', format='1.6')
1169
stacked_branch.set_stacked_on_url('../base')
1170
reference_format = self.get_repo_format()
1171
network_name = reference_format.network_name()
1172
client = FakeClient(self.get_url())
1173
branch_network_name = self.get_branch_format().network_name()
1174
client.add_expected_call(
1175
'BzrDir.open_branchV2', ('stacked/',),
1176
'success', ('branch', branch_network_name))
1177
client.add_expected_call(
1178
'BzrDir.find_repositoryV3', ('stacked/',),
1179
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1180
# called twice, once from constructor and then again by us
1181
client.add_expected_call(
1182
'Branch.get_stacked_on_url', ('stacked/',),
1183
'success', ('ok', '../base'))
1184
client.add_expected_call(
1185
'Branch.get_stacked_on_url', ('stacked/',),
1186
'success', ('ok', '../base'))
1187
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1188
remote.RemoteBzrDirFormat(), _client=client)
1189
branch = bzrdir.open_branch()
1190
result = branch.get_stacked_on_url()
1191
self.assertEqual('../base', result)
1192
self.assertFinished(client)
1193
# it's in the fallback list both for the RemoteRepository.
1194
self.assertEqual(1, len(branch.repository._fallback_repositories))
1195
# And we haven't had to construct a real repository.
1196
self.assertEqual(None, branch.repository._real_repository)
1199
class TestBranchSetLastRevision(RemoteBranchTestCase):
266
1201
def test_set_empty(self):
267
1202
# set_revision_history([]) is translated to calling
268
1203
# Branch.set_last_revision(path, '') on the wire.
269
client = FakeClient([
271
(('ok', 'branch token', 'repo token'), ),
276
1204
transport = MemoryTransport()
277
1205
transport.mkdir('branch')
278
1206
transport = transport.clone('branch')
280
bzrdir = RemoteBzrDir(transport, _client=False)
281
branch = RemoteBranch(bzrdir, None, _client=client)
1208
client = FakeClient(transport.base)
1209
client.add_expected_call(
1210
'Branch.get_stacked_on_url', ('branch/',),
1211
'error', ('NotStacked',))
1212
client.add_expected_call(
1213
'Branch.lock_write', ('branch/', '', ''),
1214
'success', ('ok', 'branch token', 'repo token'))
1215
client.add_expected_call(
1216
'Branch.last_revision_info',
1218
'success', ('ok', '0', 'null:'))
1219
client.add_expected_call(
1220
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1222
client.add_expected_call(
1223
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1225
branch = self.make_remote_branch(transport, client)
282
1226
# This is a hack to work around the problem that RemoteBranch currently
283
1227
# unnecessarily invokes _ensure_real upon a call to lock_write.
284
1228
branch._ensure_real = lambda: None
285
1229
branch.lock_write()
287
1230
result = branch.set_revision_history([])
289
[('call', 'Branch.set_last_revision',
290
('///branch/', 'branch token', 'repo token', 'null:'))],
293
1232
self.assertEqual(None, result)
1233
self.assertFinished(client)
295
1235
def test_set_nonempty(self):
296
1236
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
297
1237
# Branch.set_last_revision(path, rev-idN) on the wire.
298
client = FakeClient([
300
(('ok', 'branch token', 'repo token'), ),
305
1238
transport = MemoryTransport()
306
1239
transport.mkdir('branch')
307
1240
transport = transport.clone('branch')
309
bzrdir = RemoteBzrDir(transport, _client=False)
310
branch = RemoteBranch(bzrdir, None, _client=client)
1242
client = FakeClient(transport.base)
1243
client.add_expected_call(
1244
'Branch.get_stacked_on_url', ('branch/',),
1245
'error', ('NotStacked',))
1246
client.add_expected_call(
1247
'Branch.lock_write', ('branch/', '', ''),
1248
'success', ('ok', 'branch token', 'repo token'))
1249
client.add_expected_call(
1250
'Branch.last_revision_info',
1252
'success', ('ok', '0', 'null:'))
1254
encoded_body = bz2.compress('\n'.join(lines))
1255
client.add_success_response_with_body(encoded_body, 'ok')
1256
client.add_expected_call(
1257
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1259
client.add_expected_call(
1260
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1262
branch = self.make_remote_branch(transport, client)
311
1263
# This is a hack to work around the problem that RemoteBranch currently
312
1264
# unnecessarily invokes _ensure_real upon a call to lock_write.
313
1265
branch._ensure_real = lambda: None
314
1266
# Lock the branch, reset the record of remote calls.
315
1267
branch.lock_write()
318
1268
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
320
[('call', 'Branch.set_last_revision',
321
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
324
1270
self.assertEqual(None, result)
1271
self.assertFinished(client)
326
1273
def test_no_such_revision(self):
327
# A response of 'NoSuchRevision' is translated into an exception.
328
client = FakeClient([
330
(('ok', 'branch token', 'repo token'), ),
332
(('NoSuchRevision', 'rev-id'), ),
335
1274
transport = MemoryTransport()
336
1275
transport.mkdir('branch')
337
1276
transport = transport.clone('branch')
1277
# A response of 'NoSuchRevision' is translated into an exception.
1278
client = FakeClient(transport.base)
1279
client.add_expected_call(
1280
'Branch.get_stacked_on_url', ('branch/',),
1281
'error', ('NotStacked',))
1282
client.add_expected_call(
1283
'Branch.lock_write', ('branch/', '', ''),
1284
'success', ('ok', 'branch token', 'repo token'))
1285
client.add_expected_call(
1286
'Branch.last_revision_info',
1288
'success', ('ok', '0', 'null:'))
1289
# get_graph calls to construct the revision history, for the set_rh
1292
encoded_body = bz2.compress('\n'.join(lines))
1293
client.add_success_response_with_body(encoded_body, 'ok')
1294
client.add_expected_call(
1295
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1296
'error', ('NoSuchRevision', 'rev-id'))
1297
client.add_expected_call(
1298
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
339
bzrdir = RemoteBzrDir(transport, _client=False)
340
branch = RemoteBranch(bzrdir, None, _client=client)
341
branch._ensure_real = lambda: None
1301
branch = self.make_remote_branch(transport, client)
342
1302
branch.lock_write()
345
1303
self.assertRaises(
346
1304
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
350
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
351
"""Test branch.control_files api munging...
353
We special case RemoteBranch.control_files.get('branch.conf') to
354
call a specific API so that RemoteBranch's can intercept configuration
355
file reading, allowing them to signal to the client about things like
356
'email is configured for commits'.
1306
self.assertFinished(client)
1308
def test_tip_change_rejected(self):
1309
"""TipChangeRejected responses cause a TipChangeRejected exception to
1312
transport = MemoryTransport()
1313
transport.mkdir('branch')
1314
transport = transport.clone('branch')
1315
client = FakeClient(transport.base)
1316
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1317
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1318
client.add_expected_call(
1319
'Branch.get_stacked_on_url', ('branch/',),
1320
'error', ('NotStacked',))
1321
client.add_expected_call(
1322
'Branch.lock_write', ('branch/', '', ''),
1323
'success', ('ok', 'branch token', 'repo token'))
1324
client.add_expected_call(
1325
'Branch.last_revision_info',
1327
'success', ('ok', '0', 'null:'))
1329
encoded_body = bz2.compress('\n'.join(lines))
1330
client.add_success_response_with_body(encoded_body, 'ok')
1331
client.add_expected_call(
1332
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1333
'error', ('TipChangeRejected', rejection_msg_utf8))
1334
client.add_expected_call(
1335
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1337
branch = self.make_remote_branch(transport, client)
1338
branch._ensure_real = lambda: None
1340
# The 'TipChangeRejected' error response triggered by calling
1341
# set_revision_history causes a TipChangeRejected exception.
1342
err = self.assertRaises(
1343
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1344
# The UTF-8 message from the response has been decoded into a unicode
1346
self.assertIsInstance(err.msg, unicode)
1347
self.assertEqual(rejection_msg_unicode, err.msg)
1349
self.assertFinished(client)
1352
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1354
def test_set_last_revision_info(self):
1355
# set_last_revision_info(num, 'rev-id') is translated to calling
1356
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1357
transport = MemoryTransport()
1358
transport.mkdir('branch')
1359
transport = transport.clone('branch')
1360
client = FakeClient(transport.base)
1361
# get_stacked_on_url
1362
client.add_error_response('NotStacked')
1364
client.add_success_response('ok', 'branch token', 'repo token')
1365
# query the current revision
1366
client.add_success_response('ok', '0', 'null:')
1368
client.add_success_response('ok')
1370
client.add_success_response('ok')
1372
branch = self.make_remote_branch(transport, client)
1373
# Lock the branch, reset the record of remote calls.
1376
result = branch.set_last_revision_info(1234, 'a-revision-id')
1378
[('call', 'Branch.last_revision_info', ('branch/',)),
1379
('call', 'Branch.set_last_revision_info',
1380
('branch/', 'branch token', 'repo token',
1381
'1234', 'a-revision-id'))],
1383
self.assertEqual(None, result)
1385
def test_no_such_revision(self):
1386
# A response of 'NoSuchRevision' is translated into an exception.
1387
transport = MemoryTransport()
1388
transport.mkdir('branch')
1389
transport = transport.clone('branch')
1390
client = FakeClient(transport.base)
1391
# get_stacked_on_url
1392
client.add_error_response('NotStacked')
1394
client.add_success_response('ok', 'branch token', 'repo token')
1396
client.add_error_response('NoSuchRevision', 'revid')
1398
client.add_success_response('ok')
1400
branch = self.make_remote_branch(transport, client)
1401
# Lock the branch, reset the record of remote calls.
1406
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1409
def test_backwards_compatibility(self):
1410
"""If the server does not support the Branch.set_last_revision_info
1411
verb (which is new in 1.4), then the client falls back to VFS methods.
1413
# This test is a little messy. Unlike most tests in this file, it
1414
# doesn't purely test what a Remote* object sends over the wire, and
1415
# how it reacts to responses from the wire. It instead relies partly
1416
# on asserting that the RemoteBranch will call
1417
# self._real_branch.set_last_revision_info(...).
1419
# First, set up our RemoteBranch with a FakeClient that raises
1420
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1421
transport = MemoryTransport()
1422
transport.mkdir('branch')
1423
transport = transport.clone('branch')
1424
client = FakeClient(transport.base)
1425
client.add_expected_call(
1426
'Branch.get_stacked_on_url', ('branch/',),
1427
'error', ('NotStacked',))
1428
client.add_expected_call(
1429
'Branch.last_revision_info',
1431
'success', ('ok', '0', 'null:'))
1432
client.add_expected_call(
1433
'Branch.set_last_revision_info',
1434
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1435
'unknown', 'Branch.set_last_revision_info')
1437
branch = self.make_remote_branch(transport, client)
1438
class StubRealBranch(object):
1441
def set_last_revision_info(self, revno, revision_id):
1443
('set_last_revision_info', revno, revision_id))
1444
def _clear_cached_state(self):
1446
real_branch = StubRealBranch()
1447
branch._real_branch = real_branch
1448
self.lock_remote_branch(branch)
1450
# Call set_last_revision_info, and verify it behaved as expected.
1451
result = branch.set_last_revision_info(1234, 'a-revision-id')
1453
[('set_last_revision_info', 1234, 'a-revision-id')],
1455
self.assertFinished(client)
1457
def test_unexpected_error(self):
1458
# If the server sends an error the client doesn't understand, it gets
1459
# turned into an UnknownErrorFromSmartServer, which is presented as a
1460
# non-internal error to the user.
1461
transport = MemoryTransport()
1462
transport.mkdir('branch')
1463
transport = transport.clone('branch')
1464
client = FakeClient(transport.base)
1465
# get_stacked_on_url
1466
client.add_error_response('NotStacked')
1468
client.add_success_response('ok', 'branch token', 'repo token')
1470
client.add_error_response('UnexpectedError')
1472
client.add_success_response('ok')
1474
branch = self.make_remote_branch(transport, client)
1475
# Lock the branch, reset the record of remote calls.
1479
err = self.assertRaises(
1480
errors.UnknownErrorFromSmartServer,
1481
branch.set_last_revision_info, 123, 'revid')
1482
self.assertEqual(('UnexpectedError',), err.error_tuple)
1485
def test_tip_change_rejected(self):
1486
"""TipChangeRejected responses cause a TipChangeRejected exception to
1489
transport = MemoryTransport()
1490
transport.mkdir('branch')
1491
transport = transport.clone('branch')
1492
client = FakeClient(transport.base)
1493
# get_stacked_on_url
1494
client.add_error_response('NotStacked')
1496
client.add_success_response('ok', 'branch token', 'repo token')
1498
client.add_error_response('TipChangeRejected', 'rejection message')
1500
client.add_success_response('ok')
1502
branch = self.make_remote_branch(transport, client)
1503
# Lock the branch, reset the record of remote calls.
1505
self.addCleanup(branch.unlock)
1508
# The 'TipChangeRejected' error response triggered by calling
1509
# set_last_revision_info causes a TipChangeRejected exception.
1510
err = self.assertRaises(
1511
errors.TipChangeRejected,
1512
branch.set_last_revision_info, 123, 'revid')
1513
self.assertEqual('rejection message', err.msg)
1516
class TestBranchGetSetConfig(RemoteBranchTestCase):
359
1518
def test_get_branch_conf(self):
360
1519
# in an empty branch we decode the response properly
361
client = FakeClient([(('ok', ), 'config file body')])
362
# we need to make a real branch because the remote_branch.control_files
363
# will trigger _ensure_real.
364
branch = self.make_branch('quack')
365
transport = branch.bzrdir.root_transport
366
# we do not want bzrdir to make any remote calls
367
bzrdir = RemoteBzrDir(transport, _client=False)
368
branch = RemoteBranch(bzrdir, None, _client=client)
369
result = branch.control_files.get('branch.conf')
1520
client = FakeClient()
1521
client.add_expected_call(
1522
'Branch.get_stacked_on_url', ('memory:///',),
1523
'error', ('NotStacked',),)
1524
client.add_success_response_with_body('# config file body', 'ok')
1525
transport = MemoryTransport()
1526
branch = self.make_remote_branch(transport, client)
1527
config = branch.get_config()
1528
config.has_explicit_nickname()
370
1529
self.assertEqual(
371
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
1530
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1531
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
373
self.assertEqual('config file body', result.read())
376
class TestBranchLockWrite(tests.TestCase):
1534
def test_get_multi_line_branch_conf(self):
1535
# Make sure that multiple-line branch.conf files are supported
1537
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1538
client = FakeClient()
1539
client.add_expected_call(
1540
'Branch.get_stacked_on_url', ('memory:///',),
1541
'error', ('NotStacked',),)
1542
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1543
transport = MemoryTransport()
1544
branch = self.make_remote_branch(transport, client)
1545
config = branch.get_config()
1546
self.assertEqual(u'2', config.get_user_option('b'))
1548
def test_set_option(self):
1549
client = FakeClient()
1550
client.add_expected_call(
1551
'Branch.get_stacked_on_url', ('memory:///',),
1552
'error', ('NotStacked',),)
1553
client.add_expected_call(
1554
'Branch.lock_write', ('memory:///', '', ''),
1555
'success', ('ok', 'branch token', 'repo token'))
1556
client.add_expected_call(
1557
'Branch.set_config_option', ('memory:///', 'branch token',
1558
'repo token', 'foo', 'bar', ''),
1560
client.add_expected_call(
1561
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1563
transport = MemoryTransport()
1564
branch = self.make_remote_branch(transport, client)
1566
config = branch._get_config()
1567
config.set_option('foo', 'bar')
1569
self.assertFinished(client)
1571
def test_backwards_compat_set_option(self):
1572
self.setup_smart_server_with_call_log()
1573
branch = self.make_branch('.')
1574
verb = 'Branch.set_config_option'
1575
self.disable_verb(verb)
1577
self.addCleanup(branch.unlock)
1578
self.reset_smart_call_log()
1579
branch._get_config().set_option('value', 'name')
1580
self.assertLength(10, self.hpss_calls)
1581
self.assertEqual('value', branch._get_config().get_option('name'))
1584
class TestBranchLockWrite(RemoteBranchTestCase):
378
1586
def test_lock_write_unlockable(self):
379
client = FakeClient([(('UnlockableTransport', ), '')])
380
1587
transport = MemoryTransport()
1588
client = FakeClient(transport.base)
1589
client.add_expected_call(
1590
'Branch.get_stacked_on_url', ('quack/',),
1591
'error', ('NotStacked',),)
1592
client.add_expected_call(
1593
'Branch.lock_write', ('quack/', '', ''),
1594
'error', ('UnlockableTransport',))
381
1595
transport.mkdir('quack')
382
1596
transport = transport.clone('quack')
383
# we do not want bzrdir to make any remote calls
384
bzrdir = RemoteBzrDir(transport, _client=False)
385
branch = RemoteBranch(bzrdir, None, _client=client)
1597
branch = self.make_remote_branch(transport, client)
386
1598
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1599
self.assertFinished(client)
1602
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1604
def test__get_config(self):
1605
client = FakeClient()
1606
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1607
transport = MemoryTransport()
1608
bzrdir = self.make_remote_bzrdir(transport, client)
1609
config = bzrdir.get_config()
1610
self.assertEqual('/', config.get_default_stack_on())
387
1611
self.assertEqual(
388
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
1612
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1615
def test_set_option_uses_vfs(self):
1616
self.setup_smart_server_with_call_log()
1617
bzrdir = self.make_bzrdir('.')
1618
self.reset_smart_call_log()
1619
config = bzrdir.get_config()
1620
config.set_default_stack_on('/')
1621
self.assertLength(3, self.hpss_calls)
1623
def test_backwards_compat_get_option(self):
1624
self.setup_smart_server_with_call_log()
1625
bzrdir = self.make_bzrdir('.')
1626
verb = 'BzrDir.get_config_file'
1627
self.disable_verb(verb)
1628
self.reset_smart_call_log()
1629
self.assertEqual(None,
1630
bzrdir._get_config().get_option('default_stack_on'))
1631
self.assertLength(3, self.hpss_calls)
392
1634
class TestTransportIsReadonly(tests.TestCase):
394
1636
def test_true(self):
395
client = FakeClient([(('yes',), '')])
1637
client = FakeClient()
1638
client.add_success_response('yes')
396
1639
transport = RemoteTransport('bzr://example.com/', medium=False,
398
1641
self.assertEqual(True, transport.is_readonly())
1801
class TestRepositoryGetGraph(TestRemoteRepository):
1803
def test_get_graph(self):
1804
# get_graph returns a graph with a custom parents provider.
1805
transport_path = 'quack'
1806
repo, client = self.setup_fake_client_and_repository(transport_path)
1807
graph = repo.get_graph()
1808
self.assertNotEqual(graph._parents_provider, repo)
1811
class TestRepositoryGetParentMap(TestRemoteRepository):
1813
def test_get_parent_map_caching(self):
1814
# get_parent_map returns from cache until unlock()
1815
# setup a reponse with two revisions
1816
r1 = u'\u0e33'.encode('utf8')
1817
r2 = u'\u0dab'.encode('utf8')
1818
lines = [' '.join([r2, r1]), r1]
1819
encoded_body = bz2.compress('\n'.join(lines))
1821
transport_path = 'quack'
1822
repo, client = self.setup_fake_client_and_repository(transport_path)
1823
client.add_success_response_with_body(encoded_body, 'ok')
1824
client.add_success_response_with_body(encoded_body, 'ok')
1826
graph = repo.get_graph()
1827
parents = graph.get_parent_map([r2])
1828
self.assertEqual({r2: (r1,)}, parents)
1829
# locking and unlocking deeper should not reset
1832
parents = graph.get_parent_map([r1])
1833
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1835
[('call_with_body_bytes_expecting_body',
1836
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1840
# now we call again, and it should use the second response.
1842
graph = repo.get_graph()
1843
parents = graph.get_parent_map([r1])
1844
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1846
[('call_with_body_bytes_expecting_body',
1847
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1849
('call_with_body_bytes_expecting_body',
1850
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1856
def test_get_parent_map_reconnects_if_unknown_method(self):
1857
transport_path = 'quack'
1858
rev_id = 'revision-id'
1859
repo, client = self.setup_fake_client_and_repository(transport_path)
1860
client.add_unknown_method_response('Repository.get_parent_map')
1861
client.add_success_response_with_body(rev_id, 'ok')
1862
self.assertFalse(client._medium._is_remote_before((1, 2)))
1863
parents = repo.get_parent_map([rev_id])
1865
[('call_with_body_bytes_expecting_body',
1866
'Repository.get_parent_map', ('quack/', 'include-missing:',
1868
('disconnect medium',),
1869
('call_expecting_body', 'Repository.get_revision_graph',
1872
# The medium is now marked as being connected to an older server
1873
self.assertTrue(client._medium._is_remote_before((1, 2)))
1874
self.assertEqual({rev_id: ('null:',)}, parents)
1876
def test_get_parent_map_fallback_parentless_node(self):
1877
"""get_parent_map falls back to get_revision_graph on old servers. The
1878
results from get_revision_graph are tweaked to match the get_parent_map
1881
Specifically, a {key: ()} result from get_revision_graph means "no
1882
parents" for that key, which in get_parent_map results should be
1883
represented as {key: ('null:',)}.
1885
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1887
rev_id = 'revision-id'
1888
transport_path = 'quack'
1889
repo, client = self.setup_fake_client_and_repository(transport_path)
1890
client.add_success_response_with_body(rev_id, 'ok')
1891
client._medium._remember_remote_is_before((1, 2))
1892
parents = repo.get_parent_map([rev_id])
1894
[('call_expecting_body', 'Repository.get_revision_graph',
1897
self.assertEqual({rev_id: ('null:',)}, parents)
1899
def test_get_parent_map_unexpected_response(self):
1900
repo, client = self.setup_fake_client_and_repository('path')
1901
client.add_success_response('something unexpected!')
1903
errors.UnexpectedSmartServerResponse,
1904
repo.get_parent_map, ['a-revision-id'])
1906
def test_get_parent_map_negative_caches_missing_keys(self):
1907
self.setup_smart_server_with_call_log()
1908
repo = self.make_repository('foo')
1909
self.assertIsInstance(repo, RemoteRepository)
1911
self.addCleanup(repo.unlock)
1912
self.reset_smart_call_log()
1913
graph = repo.get_graph()
1914
self.assertEqual({},
1915
graph.get_parent_map(['some-missing', 'other-missing']))
1916
self.assertLength(1, self.hpss_calls)
1917
# No call if we repeat this
1918
self.reset_smart_call_log()
1919
graph = repo.get_graph()
1920
self.assertEqual({},
1921
graph.get_parent_map(['some-missing', 'other-missing']))
1922
self.assertLength(0, self.hpss_calls)
1923
# Asking for more unknown keys makes a request.
1924
self.reset_smart_call_log()
1925
graph = repo.get_graph()
1926
self.assertEqual({},
1927
graph.get_parent_map(['some-missing', 'other-missing',
1929
self.assertLength(1, self.hpss_calls)
1931
def disableExtraResults(self):
1932
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1933
SmartServerRepositoryGetParentMap.no_extra_results = True
1935
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1936
self.addCleanup(reset_values)
1938
def test_null_cached_missing_and_stop_key(self):
1939
self.setup_smart_server_with_call_log()
1940
# Make a branch with a single revision.
1941
builder = self.make_branch_builder('foo')
1942
builder.start_series()
1943
builder.build_snapshot('first', None, [
1944
('add', ('', 'root-id', 'directory', ''))])
1945
builder.finish_series()
1946
branch = builder.get_branch()
1947
repo = branch.repository
1948
self.assertIsInstance(repo, RemoteRepository)
1949
# Stop the server from sending extra results.
1950
self.disableExtraResults()
1952
self.addCleanup(repo.unlock)
1953
self.reset_smart_call_log()
1954
graph = repo.get_graph()
1955
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1956
# 'first' it will be a candidate for the stop_keys of subsequent
1957
# requests, and because 'null:' was queried but not returned it will be
1958
# cached as missing.
1959
self.assertEqual({'first': ('null:',)},
1960
graph.get_parent_map(['first', 'null:']))
1961
# Now query for another key. This request will pass along a recipe of
1962
# start and stop keys describing the already cached results, and this
1963
# recipe's revision count must be correct (or else it will trigger an
1964
# error from the server).
1965
self.assertEqual({}, graph.get_parent_map(['another-key']))
1966
# This assertion guards against disableExtraResults silently failing to
1967
# work, thus invalidating the test.
1968
self.assertLength(2, self.hpss_calls)
1970
def test_get_parent_map_gets_ghosts_from_result(self):
1971
# asking for a revision should negatively cache close ghosts in its
1973
self.setup_smart_server_with_call_log()
1974
tree = self.make_branch_and_memory_tree('foo')
1977
builder = treebuilder.TreeBuilder()
1978
builder.start_tree(tree)
1980
builder.finish_tree()
1981
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1982
rev_id = tree.commit('')
1986
self.addCleanup(tree.unlock)
1987
repo = tree.branch.repository
1988
self.assertIsInstance(repo, RemoteRepository)
1990
repo.get_parent_map([rev_id])
1991
self.reset_smart_call_log()
1992
# Now asking for rev_id's ghost parent should not make calls
1993
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1994
self.assertLength(0, self.hpss_calls)
1997
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1999
def test_allows_new_revisions(self):
2000
"""get_parent_map's results can be updated by commit."""
2001
smart_server = server.SmartTCPServer_for_testing()
2002
smart_server.setUp()
2003
self.addCleanup(smart_server.tearDown)
2004
self.make_branch('branch')
2005
branch = Branch.open(smart_server.get_url() + '/branch')
2006
tree = branch.create_checkout('tree', lightweight=True)
2008
self.addCleanup(tree.unlock)
2009
graph = tree.branch.repository.get_graph()
2010
# This provides an opportunity for the missing rev-id to be cached.
2011
self.assertEqual({}, graph.get_parent_map(['rev1']))
2012
tree.commit('message', rev_id='rev1')
2013
graph = tree.branch.repository.get_graph()
2014
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
531
2017
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
533
2019
def test_null_revision(self):
534
2020
# a null revision has the predictable result {}, we should have no wire
535
2021
# traffic when calling it with this argument
536
responses = [(('notused', ), '')]
537
2022
transport_path = 'empty'
538
repo, client = self.setup_fake_client_and_repository(
539
responses, transport_path)
540
result = repo.get_revision_graph(NULL_REVISION)
2023
repo, client = self.setup_fake_client_and_repository(transport_path)
2024
client.add_success_response('notused')
2025
# actual RemoteRepository.get_revision_graph is gone, but there's an
2026
# equivalent private method for testing
2027
result = repo._get_revision_graph(NULL_REVISION)
541
2028
self.assertEqual([], client._calls)
542
2029
self.assertEqual({}, result)
686
2279
def test_none(self):
687
2280
# repo.has_revision(None) should not cause any traffic.
688
2281
transport_path = 'quack'
690
repo, client = self.setup_fake_client_and_repository(
691
responses, transport_path)
2282
repo, client = self.setup_fake_client_and_repository(transport_path)
693
2284
# The null revision is always there, so has_revision(None) == True.
694
self.assertEqual(True, repo.has_revision(None))
2285
self.assertEqual(True, repo.has_revision(NULL_REVISION))
696
2287
# The remote repo shouldn't be accessed.
697
2288
self.assertEqual([], client._calls)
2291
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2292
"""Base class for Repository.insert_stream and .insert_stream_1.19
2296
def checkInsertEmptyStream(self, repo, client):
2297
"""Insert an empty stream, checking the result.
2299
This checks that there are no resume_tokens or missing_keys, and that
2300
the client is finished.
2302
sink = repo._get_sink()
2303
fmt = repository.RepositoryFormat.get_default_format()
2304
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2305
self.assertEqual([], resume_tokens)
2306
self.assertEqual(set(), missing_keys)
2307
self.assertFinished(client)
2310
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2311
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2314
This test case is very similar to TestRepositoryInsertStream_1_19.
2318
TestRemoteRepository.setUp(self)
2319
self.disable_verb('Repository.insert_stream_1.19')
2321
def test_unlocked_repo(self):
2322
transport_path = 'quack'
2323
repo, client = self.setup_fake_client_and_repository(transport_path)
2324
client.add_expected_call(
2325
'Repository.insert_stream_1.19', ('quack/', ''),
2326
'unknown', ('Repository.insert_stream_1.19',))
2327
client.add_expected_call(
2328
'Repository.insert_stream', ('quack/', ''),
2330
client.add_expected_call(
2331
'Repository.insert_stream', ('quack/', ''),
2333
self.checkInsertEmptyStream(repo, client)
2335
def test_locked_repo_with_no_lock_token(self):
2336
transport_path = 'quack'
2337
repo, client = self.setup_fake_client_and_repository(transport_path)
2338
client.add_expected_call(
2339
'Repository.lock_write', ('quack/', ''),
2340
'success', ('ok', ''))
2341
client.add_expected_call(
2342
'Repository.insert_stream_1.19', ('quack/', ''),
2343
'unknown', ('Repository.insert_stream_1.19',))
2344
client.add_expected_call(
2345
'Repository.insert_stream', ('quack/', ''),
2347
client.add_expected_call(
2348
'Repository.insert_stream', ('quack/', ''),
2351
self.checkInsertEmptyStream(repo, client)
2353
def test_locked_repo_with_lock_token(self):
2354
transport_path = 'quack'
2355
repo, client = self.setup_fake_client_and_repository(transport_path)
2356
client.add_expected_call(
2357
'Repository.lock_write', ('quack/', ''),
2358
'success', ('ok', 'a token'))
2359
client.add_expected_call(
2360
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2361
'unknown', ('Repository.insert_stream_1.19',))
2362
client.add_expected_call(
2363
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2365
client.add_expected_call(
2366
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2369
self.checkInsertEmptyStream(repo, client)
2371
def test_stream_with_inventory_deltas(self):
2372
"""'inventory-deltas' substreams cannot be sent to the
2373
Repository.insert_stream verb, because not all servers that implement
2374
that verb will accept them. So when one is encountered the RemoteSink
2375
immediately stops using that verb and falls back to VFS insert_stream.
2377
transport_path = 'quack'
2378
repo, client = self.setup_fake_client_and_repository(transport_path)
2379
client.add_expected_call(
2380
'Repository.insert_stream_1.19', ('quack/', ''),
2381
'unknown', ('Repository.insert_stream_1.19',))
2382
client.add_expected_call(
2383
'Repository.insert_stream', ('quack/', ''),
2385
client.add_expected_call(
2386
'Repository.insert_stream', ('quack/', ''),
2388
# Create a fake real repository for insert_stream to fall back on, so
2389
# that we can directly see the records the RemoteSink passes to the
2394
def insert_stream(self, stream, src_format, resume_tokens):
2395
for substream_kind, substream in stream:
2396
self.records.append(
2397
(substream_kind, [record.key for record in substream]))
2398
return ['fake tokens'], ['fake missing keys']
2399
fake_real_sink = FakeRealSink()
2400
class FakeRealRepository:
2401
def _get_sink(self):
2402
return fake_real_sink
2403
def is_in_write_group(self):
2405
def refresh_data(self):
2407
repo._real_repository = FakeRealRepository()
2408
sink = repo._get_sink()
2409
fmt = repository.RepositoryFormat.get_default_format()
2410
stream = self.make_stream_with_inv_deltas(fmt)
2411
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2412
# Every record from the first inventory delta should have been sent to
2414
expected_records = [
2415
('inventory-deltas', [('rev2',), ('rev3',)]),
2416
('texts', [('some-rev', 'some-file')])]
2417
self.assertEqual(expected_records, fake_real_sink.records)
2418
# The return values from the real sink's insert_stream are propagated
2419
# back to the original caller.
2420
self.assertEqual(['fake tokens'], resume_tokens)
2421
self.assertEqual(['fake missing keys'], missing_keys)
2422
self.assertFinished(client)
2424
def make_stream_with_inv_deltas(self, fmt):
2425
"""Make a simple stream with an inventory delta followed by more
2426
records and more substreams to test that all records and substreams
2427
from that point on are used.
2429
This sends, in order:
2430
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2432
* texts substream: (some-rev, some-file)
2434
# Define a stream using generators so that it isn't rewindable.
2435
inv = inventory.Inventory(revision_id='rev1')
2436
inv.root.revision = 'rev1'
2437
def stream_with_inv_delta():
2438
yield ('inventories', inventories_substream())
2439
yield ('inventory-deltas', inventory_delta_substream())
2441
versionedfile.FulltextContentFactory(
2442
('some-rev', 'some-file'), (), None, 'content')])
2443
def inventories_substream():
2444
# An empty inventory fulltext. This will be streamed normally.
2445
text = fmt._serializer.write_inventory_to_string(inv)
2446
yield versionedfile.FulltextContentFactory(
2447
('rev1',), (), None, text)
2448
def inventory_delta_substream():
2449
# An inventory delta. This can't be streamed via this verb, so it
2450
# will trigger a fallback to VFS insert_stream.
2451
entry = inv.make_entry(
2452
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2453
entry.revision = 'ghost'
2454
delta = [(None, 'newdir', 'newdir-id', entry)]
2455
serializer = inventory_delta.InventoryDeltaSerializer(
2456
versioned_root=True, tree_references=False)
2457
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2458
yield versionedfile.ChunkedContentFactory(
2459
('rev2',), (('rev1',)), None, lines)
2461
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2462
yield versionedfile.ChunkedContentFactory(
2463
('rev3',), (('rev1',)), None, lines)
2464
return stream_with_inv_delta()
2467
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2469
def test_unlocked_repo(self):
2470
transport_path = 'quack'
2471
repo, client = self.setup_fake_client_and_repository(transport_path)
2472
client.add_expected_call(
2473
'Repository.insert_stream_1.19', ('quack/', ''),
2475
client.add_expected_call(
2476
'Repository.insert_stream_1.19', ('quack/', ''),
2478
self.checkInsertEmptyStream(repo, client)
2480
def test_locked_repo_with_no_lock_token(self):
2481
transport_path = 'quack'
2482
repo, client = self.setup_fake_client_and_repository(transport_path)
2483
client.add_expected_call(
2484
'Repository.lock_write', ('quack/', ''),
2485
'success', ('ok', ''))
2486
client.add_expected_call(
2487
'Repository.insert_stream_1.19', ('quack/', ''),
2489
client.add_expected_call(
2490
'Repository.insert_stream_1.19', ('quack/', ''),
2493
self.checkInsertEmptyStream(repo, client)
2495
def test_locked_repo_with_lock_token(self):
2496
transport_path = 'quack'
2497
repo, client = self.setup_fake_client_and_repository(transport_path)
2498
client.add_expected_call(
2499
'Repository.lock_write', ('quack/', ''),
2500
'success', ('ok', 'a token'))
2501
client.add_expected_call(
2502
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2504
client.add_expected_call(
2505
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2508
self.checkInsertEmptyStream(repo, client)
700
2511
class TestRepositoryTarball(TestRemoteRepository):
702
2513
# This is a canned tarball reponse we can validate against
769
2560
self.assertFalse(isinstance(dest_repo, RemoteRepository))
770
2561
self.assertTrue(isinstance(src_repo, RemoteRepository))
771
2562
src_repo.copy_content_into(dest_repo)
2565
class _StubRealPackRepository(object):
2567
def __init__(self, calls):
2569
self._pack_collection = _StubPackCollection(calls)
2571
def is_in_write_group(self):
2574
def refresh_data(self):
2575
self.calls.append(('pack collection reload_pack_names',))
2578
class _StubPackCollection(object):
2580
def __init__(self, calls):
2584
self.calls.append(('pack collection autopack',))
2587
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2588
"""Tests for RemoteRepository.autopack implementation."""
2591
"""When the server returns 'ok' and there's no _real_repository, then
2592
nothing else happens: the autopack method is done.
2594
transport_path = 'quack'
2595
repo, client = self.setup_fake_client_and_repository(transport_path)
2596
client.add_expected_call(
2597
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2599
self.assertFinished(client)
2601
def test_ok_with_real_repo(self):
2602
"""When the server returns 'ok' and there is a _real_repository, then
2603
the _real_repository's reload_pack_name's method will be called.
2605
transport_path = 'quack'
2606
repo, client = self.setup_fake_client_and_repository(transport_path)
2607
client.add_expected_call(
2608
'PackRepository.autopack', ('quack/',),
2610
repo._real_repository = _StubRealPackRepository(client._calls)
2613
[('call', 'PackRepository.autopack', ('quack/',)),
2614
('pack collection reload_pack_names',)],
2617
def test_backwards_compatibility(self):
2618
"""If the server does not recognise the PackRepository.autopack verb,
2619
fallback to the real_repository's implementation.
2621
transport_path = 'quack'
2622
repo, client = self.setup_fake_client_and_repository(transport_path)
2623
client.add_unknown_method_response('PackRepository.autopack')
2624
def stub_ensure_real():
2625
client._calls.append(('_ensure_real',))
2626
repo._real_repository = _StubRealPackRepository(client._calls)
2627
repo._ensure_real = stub_ensure_real
2630
[('call', 'PackRepository.autopack', ('quack/',)),
2632
('pack collection autopack',)],
2636
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2637
"""Base class for unit tests for bzrlib.remote._translate_error."""
2639
def translateTuple(self, error_tuple, **context):
2640
"""Call _translate_error with an ErrorFromSmartServer built from the
2643
:param error_tuple: A tuple of a smart server response, as would be
2644
passed to an ErrorFromSmartServer.
2645
:kwargs context: context items to call _translate_error with.
2647
:returns: The error raised by _translate_error.
2649
# Raise the ErrorFromSmartServer before passing it as an argument,
2650
# because _translate_error may need to re-raise it with a bare 'raise'
2652
server_error = errors.ErrorFromSmartServer(error_tuple)
2653
translated_error = self.translateErrorFromSmartServer(
2654
server_error, **context)
2655
return translated_error
2657
def translateErrorFromSmartServer(self, error_object, **context):
2658
"""Like translateTuple, but takes an already constructed
2659
ErrorFromSmartServer rather than a tuple.
2663
except errors.ErrorFromSmartServer, server_error:
2664
translated_error = self.assertRaises(
2665
errors.BzrError, remote._translate_error, server_error,
2667
return translated_error
2670
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2671
"""Unit tests for bzrlib.remote._translate_error.
2673
Given an ErrorFromSmartServer (which has an error tuple from a smart
2674
server) and some context, _translate_error raises more specific errors from
2677
This test case covers the cases where _translate_error succeeds in
2678
translating an ErrorFromSmartServer to something better. See
2679
TestErrorTranslationRobustness for other cases.
2682
def test_NoSuchRevision(self):
2683
branch = self.make_branch('')
2685
translated_error = self.translateTuple(
2686
('NoSuchRevision', revid), branch=branch)
2687
expected_error = errors.NoSuchRevision(branch, revid)
2688
self.assertEqual(expected_error, translated_error)
2690
def test_nosuchrevision(self):
2691
repository = self.make_repository('')
2693
translated_error = self.translateTuple(
2694
('nosuchrevision', revid), repository=repository)
2695
expected_error = errors.NoSuchRevision(repository, revid)
2696
self.assertEqual(expected_error, translated_error)
2698
def test_nobranch(self):
2699
bzrdir = self.make_bzrdir('')
2700
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2701
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2702
self.assertEqual(expected_error, translated_error)
2704
def test_LockContention(self):
2705
translated_error = self.translateTuple(('LockContention',))
2706
expected_error = errors.LockContention('(remote lock)')
2707
self.assertEqual(expected_error, translated_error)
2709
def test_UnlockableTransport(self):
2710
bzrdir = self.make_bzrdir('')
2711
translated_error = self.translateTuple(
2712
('UnlockableTransport',), bzrdir=bzrdir)
2713
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2714
self.assertEqual(expected_error, translated_error)
2716
def test_LockFailed(self):
2717
lock = 'str() of a server lock'
2718
why = 'str() of why'
2719
translated_error = self.translateTuple(('LockFailed', lock, why))
2720
expected_error = errors.LockFailed(lock, why)
2721
self.assertEqual(expected_error, translated_error)
2723
def test_TokenMismatch(self):
2724
token = 'a lock token'
2725
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2726
expected_error = errors.TokenMismatch(token, '(remote token)')
2727
self.assertEqual(expected_error, translated_error)
2729
def test_Diverged(self):
2730
branch = self.make_branch('a')
2731
other_branch = self.make_branch('b')
2732
translated_error = self.translateTuple(
2733
('Diverged',), branch=branch, other_branch=other_branch)
2734
expected_error = errors.DivergedBranches(branch, other_branch)
2735
self.assertEqual(expected_error, translated_error)
2737
def test_ReadError_no_args(self):
2739
translated_error = self.translateTuple(('ReadError',), path=path)
2740
expected_error = errors.ReadError(path)
2741
self.assertEqual(expected_error, translated_error)
2743
def test_ReadError(self):
2745
translated_error = self.translateTuple(('ReadError', path))
2746
expected_error = errors.ReadError(path)
2747
self.assertEqual(expected_error, translated_error)
2749
def test_IncompatibleRepositories(self):
2750
translated_error = self.translateTuple(('IncompatibleRepositories',
2751
"repo1", "repo2", "details here"))
2752
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2754
self.assertEqual(expected_error, translated_error)
2756
def test_PermissionDenied_no_args(self):
2758
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2759
expected_error = errors.PermissionDenied(path)
2760
self.assertEqual(expected_error, translated_error)
2762
def test_PermissionDenied_one_arg(self):
2764
translated_error = self.translateTuple(('PermissionDenied', path))
2765
expected_error = errors.PermissionDenied(path)
2766
self.assertEqual(expected_error, translated_error)
2768
def test_PermissionDenied_one_arg_and_context(self):
2769
"""Given a choice between a path from the local context and a path on
2770
the wire, _translate_error prefers the path from the local context.
2772
local_path = 'local path'
2773
remote_path = 'remote path'
2774
translated_error = self.translateTuple(
2775
('PermissionDenied', remote_path), path=local_path)
2776
expected_error = errors.PermissionDenied(local_path)
2777
self.assertEqual(expected_error, translated_error)
2779
def test_PermissionDenied_two_args(self):
2781
extra = 'a string with extra info'
2782
translated_error = self.translateTuple(
2783
('PermissionDenied', path, extra))
2784
expected_error = errors.PermissionDenied(path, extra)
2785
self.assertEqual(expected_error, translated_error)
2788
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2789
"""Unit tests for bzrlib.remote._translate_error's robustness.
2791
TestErrorTranslationSuccess is for cases where _translate_error can
2792
translate successfully. This class about how _translate_err behaves when
2793
it fails to translate: it re-raises the original error.
2796
def test_unrecognised_server_error(self):
2797
"""If the error code from the server is not recognised, the original
2798
ErrorFromSmartServer is propagated unmodified.
2800
error_tuple = ('An unknown error tuple',)
2801
server_error = errors.ErrorFromSmartServer(error_tuple)
2802
translated_error = self.translateErrorFromSmartServer(server_error)
2803
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2804
self.assertEqual(expected_error, translated_error)
2806
def test_context_missing_a_key(self):
2807
"""In case of a bug in the client, or perhaps an unexpected response
2808
from a server, _translate_error returns the original error tuple from
2809
the server and mutters a warning.
2811
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2812
# in the context dict. So let's give it an empty context dict instead
2813
# to exercise its error recovery.
2815
error_tuple = ('NoSuchRevision', 'revid')
2816
server_error = errors.ErrorFromSmartServer(error_tuple)
2817
translated_error = self.translateErrorFromSmartServer(server_error)
2818
self.assertEqual(server_error, translated_error)
2819
# In addition to re-raising ErrorFromSmartServer, some debug info has
2820
# been muttered to the log file for developer to look at.
2821
self.assertContainsRe(
2822
self._get_log(keep_log_file=True),
2823
"Missing key 'branch' in context")
2825
def test_path_missing(self):
2826
"""Some translations (PermissionDenied, ReadError) can determine the
2827
'path' variable from either the wire or the local context. If neither
2828
has it, then an error is raised.
2830
error_tuple = ('ReadError',)
2831
server_error = errors.ErrorFromSmartServer(error_tuple)
2832
translated_error = self.translateErrorFromSmartServer(server_error)
2833
self.assertEqual(server_error, translated_error)
2834
# In addition to re-raising ErrorFromSmartServer, some debug info has
2835
# been muttered to the log file for developer to look at.
2836
self.assertContainsRe(
2837
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2840
class TestStacking(tests.TestCaseWithTransport):
2841
"""Tests for operations on stacked remote repositories.
2843
The underlying format type must support stacking.
2846
def test_access_stacked_remote(self):
2847
# based on <http://launchpad.net/bugs/261315>
2848
# make a branch stacked on another repository containing an empty
2849
# revision, then open it over hpss - we should be able to see that
2851
base_transport = self.get_transport()
2852
base_builder = self.make_branch_builder('base', format='1.9')
2853
base_builder.start_series()
2854
base_revid = base_builder.build_snapshot('rev-id', None,
2855
[('add', ('', None, 'directory', None))],
2857
base_builder.finish_series()
2858
stacked_branch = self.make_branch('stacked', format='1.9')
2859
stacked_branch.set_stacked_on_url('../base')
2860
# start a server looking at this
2861
smart_server = server.SmartTCPServer_for_testing()
2862
smart_server.setUp()
2863
self.addCleanup(smart_server.tearDown)
2864
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2865
# can get its branch and repository
2866
remote_branch = remote_bzrdir.open_branch()
2867
remote_repo = remote_branch.repository
2868
remote_repo.lock_read()
2870
# it should have an appropriate fallback repository, which should also
2871
# be a RemoteRepository
2872
self.assertLength(1, remote_repo._fallback_repositories)
2873
self.assertIsInstance(remote_repo._fallback_repositories[0],
2875
# and it has the revision committed to the underlying repository;
2876
# these have varying implementations so we try several of them
2877
self.assertTrue(remote_repo.has_revisions([base_revid]))
2878
self.assertTrue(remote_repo.has_revision(base_revid))
2879
self.assertEqual(remote_repo.get_revision(base_revid).message,
2882
remote_repo.unlock()
2884
def prepare_stacked_remote_branch(self):
2885
"""Get stacked_upon and stacked branches with content in each."""
2886
self.setup_smart_server_with_call_log()
2887
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2888
tree1.commit('rev1', rev_id='rev1')
2889
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2890
).open_workingtree()
2891
local_tree = tree2.branch.create_checkout('local')
2892
local_tree.commit('local changes make me feel good.')
2893
branch2 = Branch.open(self.get_url('tree2'))
2895
self.addCleanup(branch2.unlock)
2896
return tree1.branch, branch2
2898
def test_stacked_get_parent_map(self):
2899
# the public implementation of get_parent_map obeys stacking
2900
_, branch = self.prepare_stacked_remote_branch()
2901
repo = branch.repository
2902
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2904
def test_unstacked_get_parent_map(self):
2905
# _unstacked_provider.get_parent_map ignores stacking
2906
_, branch = self.prepare_stacked_remote_branch()
2907
provider = branch.repository._unstacked_provider
2908
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2910
def fetch_stream_to_rev_order(self, stream):
2912
for kind, substream in stream:
2913
if not kind == 'revisions':
2916
for content in substream:
2917
result.append(content.key[-1])
2920
def get_ordered_revs(self, format, order, branch_factory=None):
2921
"""Get a list of the revisions in a stream to format format.
2923
:param format: The format of the target.
2924
:param order: the order that target should have requested.
2925
:param branch_factory: A callable to create a trunk and stacked branch
2926
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2927
:result: The revision ids in the stream, in the order seen,
2928
the topological order of revisions in the source.
2930
unordered_format = bzrdir.format_registry.get(format)()
2931
target_repository_format = unordered_format.repository_format
2933
self.assertEqual(order, target_repository_format._fetch_order)
2934
if branch_factory is None:
2935
branch_factory = self.prepare_stacked_remote_branch
2936
_, stacked = branch_factory()
2937
source = stacked.repository._get_source(target_repository_format)
2938
tip = stacked.last_revision()
2939
revs = stacked.repository.get_ancestry(tip)
2940
search = graph.PendingAncestryResult([tip], stacked.repository)
2941
self.reset_smart_call_log()
2942
stream = source.get_stream(search)
2945
# We trust that if a revision is in the stream the rest of the new
2946
# content for it is too, as per our main fetch tests; here we are
2947
# checking that the revisions are actually included at all, and their
2949
return self.fetch_stream_to_rev_order(stream), revs
2951
def test_stacked_get_stream_unordered(self):
2952
# Repository._get_source.get_stream() from a stacked repository with
2953
# unordered yields the full data from both stacked and stacked upon
2955
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2956
self.assertEqual(set(expected_revs), set(rev_ord))
2957
# Getting unordered results should have made a streaming data request
2958
# from the server, then one from the backing branch.
2959
self.assertLength(2, self.hpss_calls)
2961
def test_stacked_on_stacked_get_stream_unordered(self):
2962
# Repository._get_source.get_stream() from a stacked repository which
2963
# is itself stacked yields the full data from all three sources.
2964
def make_stacked_stacked():
2965
_, stacked = self.prepare_stacked_remote_branch()
2966
tree = stacked.bzrdir.sprout('tree3', stacked=True
2967
).open_workingtree()
2968
local_tree = tree.branch.create_checkout('local-tree3')
2969
local_tree.commit('more local changes are better')
2970
branch = Branch.open(self.get_url('tree3'))
2973
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2974
branch_factory=make_stacked_stacked)
2975
self.assertEqual(set(expected_revs), set(rev_ord))
2976
# Getting unordered results should have made a streaming data request
2977
# from the server, and one from each backing repo
2978
self.assertLength(3, self.hpss_calls)
2980
def test_stacked_get_stream_topological(self):
2981
# Repository._get_source.get_stream() from a stacked repository with
2982
# topological sorting yields the full data from both stacked and
2983
# stacked upon sources in topological order.
2984
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2985
self.assertEqual(expected_revs, rev_ord)
2986
# Getting topological sort requires VFS calls still - one of which is
2987
# pushing up from the bound branch.
2988
self.assertLength(13, self.hpss_calls)
2990
def test_stacked_get_stream_groupcompress(self):
2991
# Repository._get_source.get_stream() from a stacked repository with
2992
# groupcompress sorting yields the full data from both stacked and
2993
# stacked upon sources in groupcompress order.
2994
raise tests.TestSkipped('No groupcompress ordered format available')
2995
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2996
self.assertEqual(expected_revs, reversed(rev_ord))
2997
# Getting unordered results should have made a streaming data request
2998
# from the backing branch, and one from the stacked on branch.
2999
self.assertLength(2, self.hpss_calls)
3001
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3002
# When pulling some fixed amount of content that is more than the
3003
# source has (because some is coming from a fallback branch, no error
3004
# should be received. This was reported as bug 360791.
3005
# Need three branches: a trunk, a stacked branch, and a preexisting
3006
# branch pulling content from stacked and trunk.
3007
self.setup_smart_server_with_call_log()
3008
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3009
r1 = trunk.commit('start')
3010
stacked_branch = trunk.branch.create_clone_on_transport(
3011
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3012
local = self.make_branch('local', format='1.9-rich-root')
3013
local.repository.fetch(stacked_branch.repository,
3014
stacked_branch.last_revision())
3017
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3020
super(TestRemoteBranchEffort, self).setUp()
3021
# Create a smart server that publishes whatever the backing VFS server
3023
self.smart_server = server.SmartTCPServer_for_testing()
3024
self.smart_server.setUp(self.get_server())
3025
self.addCleanup(self.smart_server.tearDown)
3026
# Log all HPSS calls into self.hpss_calls.
3027
_SmartClient.hooks.install_named_hook(
3028
'call', self.capture_hpss_call, None)
3029
self.hpss_calls = []
3031
def capture_hpss_call(self, params):
3032
self.hpss_calls.append(params.method)
3034
def test_copy_content_into_avoids_revision_history(self):
3035
local = self.make_branch('local')
3036
remote_backing_tree = self.make_branch_and_tree('remote')
3037
remote_backing_tree.commit("Commit.")
3038
remote_branch_url = self.smart_server.get_url() + 'remote'
3039
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3040
local.repository.fetch(remote_branch.repository)
3041
self.hpss_calls = []
3042
remote_branch.copy_content_into(local)
3043
self.assertFalse('Branch.revision_history' in self.hpss_calls)