339
191
self.assertTrue(result)
342
class TestRemote(tests.TestCaseWithMemoryTransport):
344
def get_branch_format(self):
345
reference_bzrdir_format = bzrdir.format_registry.get('default')()
346
return reference_bzrdir_format.get_branch_format()
348
def get_repo_format(self):
349
reference_bzrdir_format = bzrdir.format_registry.get('default')()
350
return reference_bzrdir_format.repository_format
352
def assertFinished(self, fake_client):
353
"""Assert that all of a FakeClient's expected calls have occurred."""
354
fake_client.finished_test()
357
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
358
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
360
def assertRemotePath(self, expected, client_base, transport_base):
361
"""Assert that the result of
362
SmartClientMedium.remote_path_from_transport is the expected value for
363
a given client_base and transport_base.
365
client_medium = medium.SmartClientMedium(client_base)
366
t = transport.get_transport(transport_base)
367
result = client_medium.remote_path_from_transport(t)
368
self.assertEqual(expected, result)
370
def test_remote_path_from_transport(self):
371
"""SmartClientMedium.remote_path_from_transport calculates a URL for
372
the given transport relative to the root of the client base URL.
374
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
375
self.assertRemotePath(
376
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
378
def assertRemotePathHTTP(self, expected, transport_base, relpath):
379
"""Assert that the result of
380
HttpTransportBase.remote_path_from_transport is the expected value for
381
a given transport_base and relpath of that transport. (Note that
382
HttpTransportBase is a subclass of SmartClientMedium)
384
base_transport = transport.get_transport(transport_base)
385
client_medium = base_transport.get_smart_medium()
386
cloned_transport = base_transport.clone(relpath)
387
result = client_medium.remote_path_from_transport(cloned_transport)
388
self.assertEqual(expected, result)
390
def test_remote_path_from_transport_http(self):
391
"""Remote paths for HTTP transports are calculated differently to other
392
transports. They are just relative to the client base, not the root
393
directory of the host.
395
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
396
self.assertRemotePathHTTP(
397
'../xyz/', scheme + '//host/path', '../xyz/')
398
self.assertRemotePathHTTP(
399
'xyz/', scheme + '//host/path', 'xyz/')
402
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
403
"""Tests for the behaviour of client_medium.remote_is_at_least."""
405
def test_initially_unlimited(self):
406
"""A fresh medium assumes that the remote side supports all
409
client_medium = medium.SmartClientMedium('dummy base')
410
self.assertFalse(client_medium._is_remote_before((99, 99)))
412
def test__remember_remote_is_before(self):
413
"""Calling _remember_remote_is_before ratchets down the known remote
416
client_medium = medium.SmartClientMedium('dummy base')
417
# Mark the remote side as being less than 1.6. The remote side may
419
client_medium._remember_remote_is_before((1, 6))
420
self.assertTrue(client_medium._is_remote_before((1, 6)))
421
self.assertFalse(client_medium._is_remote_before((1, 5)))
422
# Calling _remember_remote_is_before again with a lower value works.
423
client_medium._remember_remote_is_before((1, 5))
424
self.assertTrue(client_medium._is_remote_before((1, 5)))
425
# If you call _remember_remote_is_before with a higher value it logs a
426
# warning, and continues to remember the lower value.
427
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
428
client_medium._remember_remote_is_before((1, 9))
429
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
430
self.assertTrue(client_medium._is_remote_before((1, 5)))
433
class TestBzrDirCloningMetaDir(TestRemote):
435
def test_backwards_compat(self):
436
self.setup_smart_server_with_call_log()
437
a_dir = self.make_bzrdir('.')
438
self.reset_smart_call_log()
439
verb = 'BzrDir.cloning_metadir'
440
self.disable_verb(verb)
441
format = a_dir.cloning_metadir()
442
call_count = len([call for call in self.hpss_calls if
443
call.call.method == verb])
444
self.assertEqual(1, call_count)
446
def test_branch_reference(self):
447
transport = self.get_transport('quack')
448
referenced = self.make_branch('referenced')
449
expected = referenced.bzrdir.cloning_metadir()
450
client = FakeClient(transport.base)
451
client.add_expected_call(
452
'BzrDir.cloning_metadir', ('quack/', 'False'),
453
'error', ('BranchReference',)),
454
client.add_expected_call(
455
'BzrDir.open_branchV3', ('quack/',),
456
'success', ('ref', self.get_url('referenced'))),
457
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
459
result = a_bzrdir.cloning_metadir()
460
# We should have got a control dir matching the referenced branch.
461
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
462
self.assertEqual(expected._repository_format, result._repository_format)
463
self.assertEqual(expected._branch_format, result._branch_format)
464
self.assertFinished(client)
466
def test_current_server(self):
467
transport = self.get_transport('.')
468
transport = transport.clone('quack')
469
self.make_bzrdir('quack')
470
client = FakeClient(transport.base)
471
reference_bzrdir_format = bzrdir.format_registry.get('default')()
472
control_name = reference_bzrdir_format.network_name()
473
client.add_expected_call(
474
'BzrDir.cloning_metadir', ('quack/', 'False'),
475
'success', (control_name, '', ('branch', ''))),
476
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
478
result = a_bzrdir.cloning_metadir()
479
# We should have got a reference control dir with default branch and
480
# repository formats.
481
# This pokes a little, just to be sure.
482
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
483
self.assertEqual(None, result._repository_format)
484
self.assertEqual(None, result._branch_format)
485
self.assertFinished(client)
488
class TestBzrDirOpen(TestRemote):
490
def make_fake_client_and_transport(self, path='quack'):
491
transport = MemoryTransport()
492
transport.mkdir(path)
493
transport = transport.clone(path)
494
client = FakeClient(transport.base)
495
return client, transport
497
def test_absent(self):
498
client, transport = self.make_fake_client_and_transport()
499
client.add_expected_call(
500
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
501
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
502
RemoteBzrDirFormat(), _client=client, _force_probe=True)
503
self.assertFinished(client)
505
def test_present_without_workingtree(self):
506
client, transport = self.make_fake_client_and_transport()
507
client.add_expected_call(
508
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
509
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
510
_client=client, _force_probe=True)
511
self.assertIsInstance(bd, RemoteBzrDir)
512
self.assertFalse(bd.has_workingtree())
513
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
514
self.assertFinished(client)
516
def test_present_with_workingtree(self):
517
client, transport = self.make_fake_client_and_transport()
518
client.add_expected_call(
519
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
520
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
521
_client=client, _force_probe=True)
522
self.assertIsInstance(bd, RemoteBzrDir)
523
self.assertTrue(bd.has_workingtree())
524
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
525
self.assertFinished(client)
527
def test_backwards_compat(self):
528
client, transport = self.make_fake_client_and_transport()
529
client.add_expected_call(
530
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
531
client.add_expected_call(
532
'BzrDir.open', ('quack/',), 'success', ('yes',))
533
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
534
_client=client, _force_probe=True)
535
self.assertIsInstance(bd, RemoteBzrDir)
536
self.assertFinished(client)
538
def test_backwards_compat_hpss_v2(self):
539
client, transport = self.make_fake_client_and_transport()
540
# Monkey-patch fake client to simulate real-world behaviour with v2
541
# server: upon first RPC call detect the protocol version, and because
542
# the version is 2 also do _remember_remote_is_before((1, 6)) before
543
# continuing with the RPC.
544
orig_check_call = client._check_call
545
def check_call(method, args):
546
client._medium._protocol_version = 2
547
client._medium._remember_remote_is_before((1, 6))
548
client._check_call = orig_check_call
549
client._check_call(method, args)
550
client._check_call = check_call
551
client.add_expected_call(
552
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
553
client.add_expected_call(
554
'BzrDir.open', ('quack/',), 'success', ('yes',))
555
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
556
_client=client, _force_probe=True)
557
self.assertIsInstance(bd, RemoteBzrDir)
558
self.assertFinished(client)
561
class TestBzrDirOpenBranch(TestRemote):
563
def test_backwards_compat(self):
564
self.setup_smart_server_with_call_log()
565
self.make_branch('.')
566
a_dir = BzrDir.open(self.get_url('.'))
567
self.reset_smart_call_log()
568
verb = 'BzrDir.open_branchV3'
569
self.disable_verb(verb)
570
format = a_dir.open_branch()
571
call_count = len([call for call in self.hpss_calls if
572
call.call.method == verb])
573
self.assertEqual(1, call_count)
194
class TestBzrDirOpenBranch(tests.TestCase):
575
196
def test_branch_present(self):
576
reference_format = self.get_repo_format()
577
network_name = reference_format.network_name()
578
branch_network_name = self.get_branch_format().network_name()
579
197
transport = MemoryTransport()
580
198
transport.mkdir('quack')
581
199
transport = transport.clone('quack')
582
client = FakeClient(transport.base)
583
client.add_expected_call(
584
'BzrDir.open_branchV3', ('quack/',),
585
'success', ('branch', branch_network_name))
586
client.add_expected_call(
587
'BzrDir.find_repositoryV3', ('quack/',),
588
'success', ('ok', '', 'no', 'no', 'no', network_name))
589
client.add_expected_call(
590
'Branch.get_stacked_on_url', ('quack/',),
591
'error', ('NotStacked',))
592
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
200
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
202
bzrdir = RemoteBzrDir(transport, _client=client)
594
203
result = bzrdir.open_branch()
205
[('call', 'BzrDir.open_branch', ('quack/',)),
206
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
595
208
self.assertIsInstance(result, RemoteBranch)
596
209
self.assertEqual(bzrdir, result.bzrdir)
597
self.assertFinished(client)
599
211
def test_branch_missing(self):
600
212
transport = MemoryTransport()
601
213
transport.mkdir('quack')
602
214
transport = transport.clone('quack')
603
client = FakeClient(transport.base)
604
client.add_error_response('nobranch')
605
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
215
client = FakeClient([(('nobranch',), )], transport.base)
216
bzrdir = RemoteBzrDir(transport, _client=client)
607
217
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
608
218
self.assertEqual(
609
[('call', 'BzrDir.open_branchV3', ('quack/',))],
219
[('call', 'BzrDir.open_branch', ('quack/',))],
612
222
def test__get_tree_branch(self):
613
223
# _get_tree_branch is a form of open_branch, but it should only ask for
614
224
# branch opening, not any other network requests.
616
def open_branch(name=None):
617
227
calls.append("Called")
618
228
return "a-branch"
619
229
transport = MemoryTransport()
620
230
# no requests on the network - catches other api calls being made.
621
client = FakeClient(transport.base)
622
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
231
client = FakeClient([], transport.base)
232
bzrdir = RemoteBzrDir(transport, _client=client)
624
233
# patch the open_branch call to record that it was called.
625
234
bzrdir.open_branch = open_branch
626
235
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
692
288
self.assertRaises(errors.NotBranchError,
693
RemoteBzrProber.probe_transport, OldServerTransport())
696
class TestBzrDirCreateBranch(TestRemote):
698
def test_backwards_compat(self):
699
self.setup_smart_server_with_call_log()
700
repo = self.make_repository('.')
701
self.reset_smart_call_log()
702
self.disable_verb('BzrDir.create_branch')
703
branch = repo.bzrdir.create_branch()
704
create_branch_call_count = len([call for call in self.hpss_calls if
705
call.call.method == 'BzrDir.create_branch'])
706
self.assertEqual(1, create_branch_call_count)
708
def test_current_server(self):
709
transport = self.get_transport('.')
710
transport = transport.clone('quack')
711
self.make_repository('quack')
712
client = FakeClient(transport.base)
713
reference_bzrdir_format = bzrdir.format_registry.get('default')()
714
reference_format = reference_bzrdir_format.get_branch_format()
715
network_name = reference_format.network_name()
716
reference_repo_fmt = reference_bzrdir_format.repository_format
717
reference_repo_name = reference_repo_fmt.network_name()
718
client.add_expected_call(
719
'BzrDir.create_branch', ('quack/', network_name),
720
'success', ('ok', network_name, '', 'no', 'no', 'yes',
721
reference_repo_name))
722
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
724
branch = a_bzrdir.create_branch()
725
# We should have got a remote branch
726
self.assertIsInstance(branch, remote.RemoteBranch)
727
# its format should have the settings from the response
728
format = branch._format
729
self.assertEqual(network_name, format.network_name())
731
def test_already_open_repo_and_reused_medium(self):
732
"""Bug 726584: create_branch(..., repository=repo) should work
733
regardless of what the smart medium's base URL is.
735
self.transport_server = test_server.SmartTCPServer_for_testing
736
transport = self.get_transport('.')
737
repo = self.make_repository('quack')
738
# Client's medium rooted a transport root (not at the bzrdir)
739
client = FakeClient(transport.base)
740
transport = transport.clone('quack')
741
reference_bzrdir_format = bzrdir.format_registry.get('default')()
742
reference_format = reference_bzrdir_format.get_branch_format()
743
network_name = reference_format.network_name()
744
reference_repo_fmt = reference_bzrdir_format.repository_format
745
reference_repo_name = reference_repo_fmt.network_name()
746
client.add_expected_call(
747
'BzrDir.create_branch', ('extra/quack/', network_name),
748
'success', ('ok', network_name, '', 'no', 'no', 'yes',
749
reference_repo_name))
750
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
752
branch = a_bzrdir.create_branch(repository=repo)
753
# We should have got a remote branch
754
self.assertIsInstance(branch, remote.RemoteBranch)
755
# its format should have the settings from the response
756
format = branch._format
757
self.assertEqual(network_name, format.network_name())
760
class TestBzrDirCreateRepository(TestRemote):
762
def test_backwards_compat(self):
763
self.setup_smart_server_with_call_log()
764
bzrdir = self.make_bzrdir('.')
765
self.reset_smart_call_log()
766
self.disable_verb('BzrDir.create_repository')
767
repo = bzrdir.create_repository()
768
create_repo_call_count = len([call for call in self.hpss_calls if
769
call.call.method == 'BzrDir.create_repository'])
770
self.assertEqual(1, create_repo_call_count)
772
def test_current_server(self):
773
transport = self.get_transport('.')
774
transport = transport.clone('quack')
775
self.make_bzrdir('quack')
776
client = FakeClient(transport.base)
777
reference_bzrdir_format = bzrdir.format_registry.get('default')()
778
reference_format = reference_bzrdir_format.repository_format
779
network_name = reference_format.network_name()
780
client.add_expected_call(
781
'BzrDir.create_repository', ('quack/',
782
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
784
'success', ('ok', 'yes', 'yes', 'yes', network_name))
785
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
787
repo = a_bzrdir.create_repository()
788
# We should have got a remote repository
789
self.assertIsInstance(repo, remote.RemoteRepository)
790
# its format should have the settings from the response
791
format = repo._format
792
self.assertTrue(format.rich_root_data)
793
self.assertTrue(format.supports_tree_reference)
794
self.assertTrue(format.supports_external_lookups)
795
self.assertEqual(network_name, format.network_name())
798
class TestBzrDirOpenRepository(TestRemote):
800
def test_backwards_compat_1_2_3(self):
801
# fallback all the way to the first version.
802
reference_format = self.get_repo_format()
803
network_name = reference_format.network_name()
804
server_url = 'bzr://example.com/'
805
self.permit_url(server_url)
806
client = FakeClient(server_url)
807
client.add_unknown_method_response('BzrDir.find_repositoryV3')
808
client.add_unknown_method_response('BzrDir.find_repositoryV2')
809
client.add_success_response('ok', '', 'no', 'no')
810
# A real repository instance will be created to determine the network
812
client.add_success_response_with_body(
813
"Bazaar-NG meta directory, format 1\n", 'ok')
814
client.add_success_response_with_body(
815
reference_format.get_format_string(), 'ok')
816
# PackRepository wants to do a stat
817
client.add_success_response('stat', '0', '65535')
818
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
820
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
822
repo = bzrdir.open_repository()
824
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
825
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
826
('call', 'BzrDir.find_repository', ('quack/',)),
827
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
828
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
829
('call', 'stat', ('/quack/.bzr/repository',)),
832
self.assertEqual(network_name, repo._format.network_name())
834
def test_backwards_compat_2(self):
835
# fallback to find_repositoryV2
836
reference_format = self.get_repo_format()
837
network_name = reference_format.network_name()
838
server_url = 'bzr://example.com/'
839
self.permit_url(server_url)
840
client = FakeClient(server_url)
841
client.add_unknown_method_response('BzrDir.find_repositoryV3')
842
client.add_success_response('ok', '', 'no', 'no', 'no')
843
# A real repository instance will be created to determine the network
845
client.add_success_response_with_body(
846
"Bazaar-NG meta directory, format 1\n", 'ok')
847
client.add_success_response_with_body(
848
reference_format.get_format_string(), 'ok')
849
# PackRepository wants to do a stat
850
client.add_success_response('stat', '0', '65535')
851
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
853
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
855
repo = bzrdir.open_repository()
857
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
858
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
859
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
860
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
861
('call', 'stat', ('/quack/.bzr/repository',)),
864
self.assertEqual(network_name, repo._format.network_name())
866
def test_current_server(self):
867
reference_format = self.get_repo_format()
868
network_name = reference_format.network_name()
869
transport = MemoryTransport()
870
transport.mkdir('quack')
871
transport = transport.clone('quack')
872
client = FakeClient(transport.base)
873
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
874
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
876
repo = bzrdir.open_repository()
878
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
880
self.assertEqual(network_name, repo._format.network_name())
883
class TestBzrDirFormatInitializeEx(TestRemote):
885
def test_success(self):
886
"""Simple test for typical successful call."""
887
fmt = RemoteBzrDirFormat()
888
default_format_name = BzrDirFormat.get_default_format().network_name()
889
transport = self.get_transport()
890
client = FakeClient(transport.base)
891
client.add_expected_call(
892
'BzrDirFormat.initialize_ex_1.16',
893
(default_format_name, 'path', 'False', 'False', 'False', '',
894
'', '', '', 'False'),
896
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
897
'bzrdir fmt', 'False', '', '', 'repo lock token'))
898
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
899
# it's currently hard to test that without supplying a real remote
900
# transport connected to a real server.
901
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
902
transport, False, False, False, None, None, None, None, False)
903
self.assertFinished(client)
905
def test_error(self):
906
"""Error responses are translated, e.g. 'PermissionDenied' raises the
907
corresponding error from the client.
909
fmt = RemoteBzrDirFormat()
910
default_format_name = BzrDirFormat.get_default_format().network_name()
911
transport = self.get_transport()
912
client = FakeClient(transport.base)
913
client.add_expected_call(
914
'BzrDirFormat.initialize_ex_1.16',
915
(default_format_name, 'path', 'False', 'False', 'False', '',
916
'', '', '', 'False'),
918
('PermissionDenied', 'path', 'extra info'))
919
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
920
# it's currently hard to test that without supplying a real remote
921
# transport connected to a real server.
922
err = self.assertRaises(errors.PermissionDenied,
923
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
924
False, False, False, None, None, None, None, False)
925
self.assertEqual('path', err.path)
926
self.assertEqual(': extra info', err.extra)
927
self.assertFinished(client)
929
def test_error_from_real_server(self):
930
"""Integration test for error translation."""
931
transport = self.make_smart_server('foo')
932
transport = transport.clone('no-such-path')
933
fmt = RemoteBzrDirFormat()
934
err = self.assertRaises(errors.NoSuchFile,
935
fmt.initialize_on_transport_ex, transport, create_prefix=False)
289
RemoteBzrDirFormat.probe_transport, OldServerTransport())
938
292
class OldSmartClient(object):
963
314
return OldSmartClient()
966
class RemoteBzrDirTestCase(TestRemote):
968
def make_remote_bzrdir(self, transport, client):
969
"""Make a RemotebzrDir using 'client' as the _client."""
970
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
974
class RemoteBranchTestCase(RemoteBzrDirTestCase):
976
def lock_remote_branch(self, branch):
977
"""Trick a RemoteBranch into thinking it is locked."""
978
branch._lock_mode = 'w'
979
branch._lock_count = 2
980
branch._lock_token = 'branch token'
981
branch._repo_lock_token = 'repo token'
982
branch.repository._lock_mode = 'w'
983
branch.repository._lock_count = 2
984
branch.repository._lock_token = 'repo token'
986
def make_remote_branch(self, transport, client):
987
"""Make a RemoteBranch using 'client' as its _SmartClient.
989
A RemoteBzrDir and RemoteRepository will also be created to fill out
990
the RemoteBranch, albeit with stub values for some of their attributes.
992
# we do not want bzrdir to make any remote calls, so use False as its
993
# _client. If it tries to make a remote call, this will fail
995
bzrdir = self.make_remote_bzrdir(transport, False)
996
repo = RemoteRepository(bzrdir, None, _client=client)
997
branch_format = self.get_branch_format()
998
format = RemoteBranchFormat(network_name=branch_format.network_name())
999
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1002
class TestBranchGetParent(RemoteBranchTestCase):
1004
def test_no_parent(self):
1005
# in an empty branch we decode the response properly
1006
transport = MemoryTransport()
1007
client = FakeClient(transport.base)
1008
client.add_expected_call(
1009
'Branch.get_stacked_on_url', ('quack/',),
1010
'error', ('NotStacked',))
1011
client.add_expected_call(
1012
'Branch.get_parent', ('quack/',),
1014
transport.mkdir('quack')
1015
transport = transport.clone('quack')
1016
branch = self.make_remote_branch(transport, client)
1017
result = branch.get_parent()
1018
self.assertFinished(client)
1019
self.assertEqual(None, result)
1021
def test_parent_relative(self):
1022
transport = MemoryTransport()
1023
client = FakeClient(transport.base)
1024
client.add_expected_call(
1025
'Branch.get_stacked_on_url', ('kwaak/',),
1026
'error', ('NotStacked',))
1027
client.add_expected_call(
1028
'Branch.get_parent', ('kwaak/',),
1029
'success', ('../foo/',))
1030
transport.mkdir('kwaak')
1031
transport = transport.clone('kwaak')
1032
branch = self.make_remote_branch(transport, client)
1033
result = branch.get_parent()
1034
self.assertEqual(transport.clone('../foo').base, result)
1036
def test_parent_absolute(self):
1037
transport = MemoryTransport()
1038
client = FakeClient(transport.base)
1039
client.add_expected_call(
1040
'Branch.get_stacked_on_url', ('kwaak/',),
1041
'error', ('NotStacked',))
1042
client.add_expected_call(
1043
'Branch.get_parent', ('kwaak/',),
1044
'success', ('http://foo/',))
1045
transport.mkdir('kwaak')
1046
transport = transport.clone('kwaak')
1047
branch = self.make_remote_branch(transport, client)
1048
result = branch.get_parent()
1049
self.assertEqual('http://foo/', result)
1050
self.assertFinished(client)
1053
class TestBranchSetParentLocation(RemoteBranchTestCase):
1055
def test_no_parent(self):
1056
# We call the verb when setting parent to None
1057
transport = MemoryTransport()
1058
client = FakeClient(transport.base)
1059
client.add_expected_call(
1060
'Branch.get_stacked_on_url', ('quack/',),
1061
'error', ('NotStacked',))
1062
client.add_expected_call(
1063
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1065
transport.mkdir('quack')
1066
transport = transport.clone('quack')
1067
branch = self.make_remote_branch(transport, client)
1068
branch._lock_token = 'b'
1069
branch._repo_lock_token = 'r'
1070
branch._set_parent_location(None)
1071
self.assertFinished(client)
1073
def test_parent(self):
1074
transport = MemoryTransport()
1075
client = FakeClient(transport.base)
1076
client.add_expected_call(
1077
'Branch.get_stacked_on_url', ('kwaak/',),
1078
'error', ('NotStacked',))
1079
client.add_expected_call(
1080
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1082
transport.mkdir('kwaak')
1083
transport = transport.clone('kwaak')
1084
branch = self.make_remote_branch(transport, client)
1085
branch._lock_token = 'b'
1086
branch._repo_lock_token = 'r'
1087
branch._set_parent_location('foo')
1088
self.assertFinished(client)
1090
def test_backwards_compat(self):
1091
self.setup_smart_server_with_call_log()
1092
branch = self.make_branch('.')
1093
self.reset_smart_call_log()
1094
verb = 'Branch.set_parent_location'
1095
self.disable_verb(verb)
1096
branch.set_parent('http://foo/')
1097
self.assertLength(12, self.hpss_calls)
1100
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1102
def test_backwards_compat(self):
1103
self.setup_smart_server_with_call_log()
1104
branch = self.make_branch('.')
1105
self.reset_smart_call_log()
1106
verb = 'Branch.get_tags_bytes'
1107
self.disable_verb(verb)
1108
branch.tags.get_tag_dict()
1109
call_count = len([call for call in self.hpss_calls if
1110
call.call.method == verb])
1111
self.assertEqual(1, call_count)
1113
def test_trivial(self):
1114
transport = MemoryTransport()
1115
client = FakeClient(transport.base)
1116
client.add_expected_call(
1117
'Branch.get_stacked_on_url', ('quack/',),
1118
'error', ('NotStacked',))
1119
client.add_expected_call(
1120
'Branch.get_tags_bytes', ('quack/',),
1122
transport.mkdir('quack')
1123
transport = transport.clone('quack')
1124
branch = self.make_remote_branch(transport, client)
1125
result = branch.tags.get_tag_dict()
1126
self.assertFinished(client)
1127
self.assertEqual({}, result)
1130
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1132
def test_trivial(self):
1133
transport = MemoryTransport()
1134
client = FakeClient(transport.base)
1135
client.add_expected_call(
1136
'Branch.get_stacked_on_url', ('quack/',),
1137
'error', ('NotStacked',))
1138
client.add_expected_call(
1139
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1141
transport.mkdir('quack')
1142
transport = transport.clone('quack')
1143
branch = self.make_remote_branch(transport, client)
1144
self.lock_remote_branch(branch)
1145
branch._set_tags_bytes('tags bytes')
1146
self.assertFinished(client)
1147
self.assertEqual('tags bytes', client._calls[-1][-1])
1149
def test_backwards_compatible(self):
1150
transport = MemoryTransport()
1151
client = FakeClient(transport.base)
1152
client.add_expected_call(
1153
'Branch.get_stacked_on_url', ('quack/',),
1154
'error', ('NotStacked',))
1155
client.add_expected_call(
1156
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1157
'unknown', ('Branch.set_tags_bytes',))
1158
transport.mkdir('quack')
1159
transport = transport.clone('quack')
1160
branch = self.make_remote_branch(transport, client)
1161
self.lock_remote_branch(branch)
1162
class StubRealBranch(object):
1165
def _set_tags_bytes(self, bytes):
1166
self.calls.append(('set_tags_bytes', bytes))
1167
real_branch = StubRealBranch()
1168
branch._real_branch = real_branch
1169
branch._set_tags_bytes('tags bytes')
1170
# Call a second time, to exercise the 'remote version already inferred'
1172
branch._set_tags_bytes('tags bytes')
1173
self.assertFinished(client)
1175
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1178
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1180
def test_uses_last_revision_info_and_tags_by_default(self):
1181
transport = MemoryTransport()
1182
client = FakeClient(transport.base)
1183
client.add_expected_call(
1184
'Branch.get_stacked_on_url', ('quack/',),
1185
'error', ('NotStacked',))
1186
client.add_expected_call(
1187
'Branch.last_revision_info', ('quack/',),
1188
'success', ('ok', '1', 'rev-tip'))
1189
client.add_expected_call(
1190
'Branch.get_config_file', ('quack/',),
1191
'success', ('ok',), '')
1192
transport.mkdir('quack')
1193
transport = transport.clone('quack')
1194
branch = self.make_remote_branch(transport, client)
1195
result = branch.heads_to_fetch()
1196
self.assertFinished(client)
1197
self.assertEqual((set(['rev-tip']), set()), result)
1199
def test_uses_last_revision_info_and_tags_when_set(self):
1200
transport = MemoryTransport()
1201
client = FakeClient(transport.base)
1202
client.add_expected_call(
1203
'Branch.get_stacked_on_url', ('quack/',),
1204
'error', ('NotStacked',))
1205
client.add_expected_call(
1206
'Branch.last_revision_info', ('quack/',),
1207
'success', ('ok', '1', 'rev-tip'))
1208
client.add_expected_call(
1209
'Branch.get_config_file', ('quack/',),
1210
'success', ('ok',), 'branch.fetch_tags = True')
1211
# XXX: this will break if the default format's serialization of tags
1212
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1213
client.add_expected_call(
1214
'Branch.get_tags_bytes', ('quack/',),
1215
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1216
transport.mkdir('quack')
1217
transport = transport.clone('quack')
1218
branch = self.make_remote_branch(transport, client)
1219
result = branch.heads_to_fetch()
1220
self.assertFinished(client)
1222
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1224
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1225
transport = MemoryTransport()
1226
client = FakeClient(transport.base)
1227
client.add_expected_call(
1228
'Branch.get_stacked_on_url', ('quack/',),
1229
'error', ('NotStacked',))
1230
client.add_expected_call(
1231
'Branch.heads_to_fetch', ('quack/',),
1232
'success', (['tip'], ['tagged-1', 'tagged-2']))
1233
transport.mkdir('quack')
1234
transport = transport.clone('quack')
1235
branch = self.make_remote_branch(transport, client)
1236
branch._format._use_default_local_heads_to_fetch = lambda: False
1237
result = branch.heads_to_fetch()
1238
self.assertFinished(client)
1239
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1241
def make_branch_with_tags(self):
1242
self.setup_smart_server_with_call_log()
1243
# Make a branch with a single revision.
1244
builder = self.make_branch_builder('foo')
1245
builder.start_series()
1246
builder.build_snapshot('tip', None, [
1247
('add', ('', 'root-id', 'directory', ''))])
1248
builder.finish_series()
1249
branch = builder.get_branch()
1250
# Add two tags to that branch
1251
branch.tags.set_tag('tag-1', 'rev-1')
1252
branch.tags.set_tag('tag-2', 'rev-2')
1255
def test_backwards_compatible(self):
1256
branch = self.make_branch_with_tags()
1257
c = branch.get_config()
1258
c.set_user_option('branch.fetch_tags', 'True')
1259
self.addCleanup(branch.lock_read().unlock)
1260
# Disable the heads_to_fetch verb
1261
verb = 'Branch.heads_to_fetch'
1262
self.disable_verb(verb)
1263
self.reset_smart_call_log()
1264
result = branch.heads_to_fetch()
1265
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1267
['Branch.last_revision_info', 'Branch.get_config_file',
1268
'Branch.get_tags_bytes'],
1269
[call.call.method for call in self.hpss_calls])
1271
def test_backwards_compatible_no_tags(self):
1272
branch = self.make_branch_with_tags()
1273
c = branch.get_config()
1274
c.set_user_option('branch.fetch_tags', 'False')
1275
self.addCleanup(branch.lock_read().unlock)
1276
# Disable the heads_to_fetch verb
1277
verb = 'Branch.heads_to_fetch'
1278
self.disable_verb(verb)
1279
self.reset_smart_call_log()
1280
result = branch.heads_to_fetch()
1281
self.assertEqual((set(['tip']), set()), result)
1283
['Branch.last_revision_info', 'Branch.get_config_file'],
1284
[call.call.method for call in self.hpss_calls])
1287
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
317
class TestBranchLastRevisionInfo(tests.TestCase):
1289
319
def test_empty_branch(self):
1290
320
# in an empty branch we decode the response properly
1291
321
transport = MemoryTransport()
1292
client = FakeClient(transport.base)
1293
client.add_expected_call(
1294
'Branch.get_stacked_on_url', ('quack/',),
1295
'error', ('NotStacked',))
1296
client.add_expected_call(
1297
'Branch.last_revision_info', ('quack/',),
1298
'success', ('ok', '0', 'null:'))
322
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
1299
323
transport.mkdir('quack')
1300
324
transport = transport.clone('quack')
1301
branch = self.make_remote_branch(transport, client)
325
# we do not want bzrdir to make any remote calls
326
bzrdir = RemoteBzrDir(transport, _client=False)
327
branch = RemoteBranch(bzrdir, None, _client=client)
1302
328
result = branch.last_revision_info()
1303
self.assertFinished(client)
331
[('call', 'Branch.last_revision_info', ('quack/',))],
1304
333
self.assertEqual((0, NULL_REVISION), result)
1306
335
def test_non_empty_branch(self):
1307
336
# in a non-empty branch we also decode the response properly
1308
337
revid = u'\xc8'.encode('utf8')
1309
338
transport = MemoryTransport()
1310
client = FakeClient(transport.base)
1311
client.add_expected_call(
1312
'Branch.get_stacked_on_url', ('kwaak/',),
1313
'error', ('NotStacked',))
1314
client.add_expected_call(
1315
'Branch.last_revision_info', ('kwaak/',),
1316
'success', ('ok', '2', revid))
339
client = FakeClient([(('ok', '2', revid), )], transport.base)
1317
340
transport.mkdir('kwaak')
1318
341
transport = transport.clone('kwaak')
1319
branch = self.make_remote_branch(transport, client)
342
# we do not want bzrdir to make any remote calls
343
bzrdir = RemoteBzrDir(transport, _client=False)
344
branch = RemoteBranch(bzrdir, None, _client=client)
1320
345
result = branch.last_revision_info()
348
[('call', 'Branch.last_revision_info', ('kwaak/',))],
1321
350
self.assertEqual((2, revid), result)
1324
class TestBranch_get_stacked_on_url(TestRemote):
1325
"""Test Branch._get_stacked_on_url rpc"""
1327
def test_get_stacked_on_invalid_url(self):
1328
# test that asking for a stacked on url the server can't access works.
1329
# This isn't perfect, but then as we're in the same process there
1330
# really isn't anything we can do to be 100% sure that the server
1331
# doesn't just open in - this test probably needs to be rewritten using
1332
# a spawn()ed server.
1333
stacked_branch = self.make_branch('stacked', format='1.9')
1334
memory_branch = self.make_branch('base', format='1.9')
1335
vfs_url = self.get_vfs_only_url('base')
1336
stacked_branch.set_stacked_on_url(vfs_url)
1337
transport = stacked_branch.bzrdir.root_transport
1338
client = FakeClient(transport.base)
1339
client.add_expected_call(
1340
'Branch.get_stacked_on_url', ('stacked/',),
1341
'success', ('ok', vfs_url))
1342
# XXX: Multiple calls are bad, this second call documents what is
1344
client.add_expected_call(
1345
'Branch.get_stacked_on_url', ('stacked/',),
1346
'success', ('ok', vfs_url))
1347
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1349
repo_fmt = remote.RemoteRepositoryFormat()
1350
repo_fmt._custom_format = stacked_branch.repository._format
1351
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1353
result = branch.get_stacked_on_url()
1354
self.assertEqual(vfs_url, result)
1356
def test_backwards_compatible(self):
1357
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1358
base_branch = self.make_branch('base', format='1.6')
1359
stacked_branch = self.make_branch('stacked', format='1.6')
1360
stacked_branch.set_stacked_on_url('../base')
1361
client = FakeClient(self.get_url())
1362
branch_network_name = self.get_branch_format().network_name()
1363
client.add_expected_call(
1364
'BzrDir.open_branchV3', ('stacked/',),
1365
'success', ('branch', branch_network_name))
1366
client.add_expected_call(
1367
'BzrDir.find_repositoryV3', ('stacked/',),
1368
'success', ('ok', '', 'no', 'no', 'yes',
1369
stacked_branch.repository._format.network_name()))
1370
# called twice, once from constructor and then again by us
1371
client.add_expected_call(
1372
'Branch.get_stacked_on_url', ('stacked/',),
1373
'unknown', ('Branch.get_stacked_on_url',))
1374
client.add_expected_call(
1375
'Branch.get_stacked_on_url', ('stacked/',),
1376
'unknown', ('Branch.get_stacked_on_url',))
1377
# this will also do vfs access, but that goes direct to the transport
1378
# and isn't seen by the FakeClient.
1379
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1380
RemoteBzrDirFormat(), _client=client)
1381
branch = bzrdir.open_branch()
1382
result = branch.get_stacked_on_url()
1383
self.assertEqual('../base', result)
1384
self.assertFinished(client)
1385
# it's in the fallback list both for the RemoteRepository and its vfs
1387
self.assertEqual(1, len(branch.repository._fallback_repositories))
1389
len(branch.repository._real_repository._fallback_repositories))
1391
def test_get_stacked_on_real_branch(self):
1392
base_branch = self.make_branch('base')
1393
stacked_branch = self.make_branch('stacked')
1394
stacked_branch.set_stacked_on_url('../base')
1395
reference_format = self.get_repo_format()
1396
network_name = reference_format.network_name()
1397
client = FakeClient(self.get_url())
1398
branch_network_name = self.get_branch_format().network_name()
1399
client.add_expected_call(
1400
'BzrDir.open_branchV3', ('stacked/',),
1401
'success', ('branch', branch_network_name))
1402
client.add_expected_call(
1403
'BzrDir.find_repositoryV3', ('stacked/',),
1404
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1405
# called twice, once from constructor and then again by us
1406
client.add_expected_call(
1407
'Branch.get_stacked_on_url', ('stacked/',),
1408
'success', ('ok', '../base'))
1409
client.add_expected_call(
1410
'Branch.get_stacked_on_url', ('stacked/',),
1411
'success', ('ok', '../base'))
1412
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1413
RemoteBzrDirFormat(), _client=client)
1414
branch = bzrdir.open_branch()
1415
result = branch.get_stacked_on_url()
1416
self.assertEqual('../base', result)
1417
self.assertFinished(client)
1418
# it's in the fallback list both for the RemoteRepository.
1419
self.assertEqual(1, len(branch.repository._fallback_repositories))
1420
# And we haven't had to construct a real repository.
1421
self.assertEqual(None, branch.repository._real_repository)
1424
class TestBranchSetLastRevision(RemoteBranchTestCase):
353
class TestBranchSetLastRevision(tests.TestCase):
1426
355
def test_set_empty(self):
1427
# _set_last_revision_info('null:') is translated to calling
356
# set_revision_history([]) is translated to calling
1428
357
# Branch.set_last_revision(path, '') on the wire.
1429
358
transport = MemoryTransport()
1430
359
transport.mkdir('branch')
1431
360
transport = transport.clone('branch')
1433
client = FakeClient(transport.base)
1434
client.add_expected_call(
1435
'Branch.get_stacked_on_url', ('branch/',),
1436
'error', ('NotStacked',))
1437
client.add_expected_call(
1438
'Branch.lock_write', ('branch/', '', ''),
1439
'success', ('ok', 'branch token', 'repo token'))
1440
client.add_expected_call(
1441
'Branch.last_revision_info',
1443
'success', ('ok', '0', 'null:'))
1444
client.add_expected_call(
1445
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1447
client.add_expected_call(
1448
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1450
branch = self.make_remote_branch(transport, client)
362
client = FakeClient([
364
(('ok', 'branch token', 'repo token'), ),
370
bzrdir = RemoteBzrDir(transport, _client=False)
371
branch = RemoteBranch(bzrdir, None, _client=client)
1451
372
# This is a hack to work around the problem that RemoteBranch currently
1452
373
# unnecessarily invokes _ensure_real upon a call to lock_write.
1453
374
branch._ensure_real = lambda: None
1454
375
branch.lock_write()
1455
result = branch._set_last_revision(NULL_REVISION)
377
result = branch.set_revision_history([])
379
[('call', 'Branch.set_last_revision',
380
('branch/', 'branch token', 'repo token', 'null:'))],
1457
383
self.assertEqual(None, result)
1458
self.assertFinished(client)
1460
385
def test_set_nonempty(self):
1461
# set_last_revision_info(N, rev-idN) is translated to calling
386
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1462
387
# Branch.set_last_revision(path, rev-idN) on the wire.
1463
388
transport = MemoryTransport()
1464
389
transport.mkdir('branch')
1465
390
transport = transport.clone('branch')
1467
client = FakeClient(transport.base)
1468
client.add_expected_call(
1469
'Branch.get_stacked_on_url', ('branch/',),
1470
'error', ('NotStacked',))
1471
client.add_expected_call(
1472
'Branch.lock_write', ('branch/', '', ''),
1473
'success', ('ok', 'branch token', 'repo token'))
1474
client.add_expected_call(
1475
'Branch.last_revision_info',
1477
'success', ('ok', '0', 'null:'))
1479
encoded_body = bz2.compress('\n'.join(lines))
1480
client.add_success_response_with_body(encoded_body, 'ok')
1481
client.add_expected_call(
1482
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1484
client.add_expected_call(
1485
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1487
branch = self.make_remote_branch(transport, client)
392
client = FakeClient([
394
(('ok', 'branch token', 'repo token'), ),
400
bzrdir = RemoteBzrDir(transport, _client=False)
401
branch = RemoteBranch(bzrdir, None, _client=client)
1488
402
# This is a hack to work around the problem that RemoteBranch currently
1489
403
# unnecessarily invokes _ensure_real upon a call to lock_write.
1490
404
branch._ensure_real = lambda: None
1491
405
# Lock the branch, reset the record of remote calls.
1492
406
branch.lock_write()
1493
result = branch._set_last_revision('rev-id2')
1495
self.assertEqual(None, result)
1496
self.assertFinished(client)
1498
def test_no_such_revision(self):
1499
transport = MemoryTransport()
1500
transport.mkdir('branch')
1501
transport = transport.clone('branch')
1502
# A response of 'NoSuchRevision' is translated into an exception.
1503
client = FakeClient(transport.base)
1504
client.add_expected_call(
1505
'Branch.get_stacked_on_url', ('branch/',),
1506
'error', ('NotStacked',))
1507
client.add_expected_call(
1508
'Branch.lock_write', ('branch/', '', ''),
1509
'success', ('ok', 'branch token', 'repo token'))
1510
client.add_expected_call(
1511
'Branch.last_revision_info',
1513
'success', ('ok', '0', 'null:'))
1514
# get_graph calls to construct the revision history, for the set_rh
1517
encoded_body = bz2.compress('\n'.join(lines))
1518
client.add_success_response_with_body(encoded_body, 'ok')
1519
client.add_expected_call(
1520
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1521
'error', ('NoSuchRevision', 'rev-id'))
1522
client.add_expected_call(
1523
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1526
branch = self.make_remote_branch(transport, client)
1529
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1531
self.assertFinished(client)
1533
def test_tip_change_rejected(self):
1534
"""TipChangeRejected responses cause a TipChangeRejected exception to
1537
transport = MemoryTransport()
1538
transport.mkdir('branch')
1539
transport = transport.clone('branch')
1540
client = FakeClient(transport.base)
1541
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1542
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1543
client.add_expected_call(
1544
'Branch.get_stacked_on_url', ('branch/',),
1545
'error', ('NotStacked',))
1546
client.add_expected_call(
1547
'Branch.lock_write', ('branch/', '', ''),
1548
'success', ('ok', 'branch token', 'repo token'))
1549
client.add_expected_call(
1550
'Branch.last_revision_info',
1552
'success', ('ok', '0', 'null:'))
1554
encoded_body = bz2.compress('\n'.join(lines))
1555
client.add_success_response_with_body(encoded_body, 'ok')
1556
client.add_expected_call(
1557
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1558
'error', ('TipChangeRejected', rejection_msg_utf8))
1559
client.add_expected_call(
1560
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1562
branch = self.make_remote_branch(transport, client)
1563
branch._ensure_real = lambda: None
1565
# The 'TipChangeRejected' error response triggered by calling
1566
# set_last_revision_info causes a TipChangeRejected exception.
1567
err = self.assertRaises(
1568
errors.TipChangeRejected,
1569
branch._set_last_revision, 'rev-id')
1570
# The UTF-8 message from the response has been decoded into a unicode
1572
self.assertIsInstance(err.msg, unicode)
1573
self.assertEqual(rejection_msg_unicode, err.msg)
1575
self.assertFinished(client)
1578
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1580
def test_set_last_revision_info(self):
1581
# set_last_revision_info(num, 'rev-id') is translated to calling
1582
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1583
transport = MemoryTransport()
1584
transport.mkdir('branch')
1585
transport = transport.clone('branch')
1586
client = FakeClient(transport.base)
1587
# get_stacked_on_url
1588
client.add_error_response('NotStacked')
1590
client.add_success_response('ok', 'branch token', 'repo token')
1591
# query the current revision
1592
client.add_success_response('ok', '0', 'null:')
1594
client.add_success_response('ok')
1596
client.add_success_response('ok')
1598
branch = self.make_remote_branch(transport, client)
1599
# Lock the branch, reset the record of remote calls.
1601
407
client._calls = []
1602
result = branch.set_last_revision_info(1234, 'a-revision-id')
409
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1603
410
self.assertEqual(
1604
[('call', 'Branch.last_revision_info', ('branch/',)),
1605
('call', 'Branch.set_last_revision_info',
1606
('branch/', 'branch token', 'repo token',
1607
'1234', 'a-revision-id'))],
411
[('call', 'Branch.set_last_revision',
412
('branch/', 'branch token', 'repo token', 'rev-id2'))],
1609
415
self.assertEqual(None, result)
1611
417
def test_no_such_revision(self):
1612
418
# A response of 'NoSuchRevision' is translated into an exception.
419
client = FakeClient([
421
(('ok', 'branch token', 'repo token'), ),
423
(('NoSuchRevision', 'rev-id'), ),
1613
426
transport = MemoryTransport()
1614
427
transport.mkdir('branch')
1615
428
transport = transport.clone('branch')
1616
client = FakeClient(transport.base)
1617
# get_stacked_on_url
1618
client.add_error_response('NotStacked')
1620
client.add_success_response('ok', 'branch token', 'repo token')
1622
client.add_error_response('NoSuchRevision', 'revid')
1624
client.add_success_response('ok')
1626
branch = self.make_remote_branch(transport, client)
1627
# Lock the branch, reset the record of remote calls.
430
bzrdir = RemoteBzrDir(transport, _client=False)
431
branch = RemoteBranch(bzrdir, None, _client=client)
432
branch._ensure_real = lambda: None
1628
433
branch.lock_write()
1629
434
client._calls = []
1631
436
self.assertRaises(
1632
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1635
def test_backwards_compatibility(self):
1636
"""If the server does not support the Branch.set_last_revision_info
1637
verb (which is new in 1.4), then the client falls back to VFS methods.
1639
# This test is a little messy. Unlike most tests in this file, it
1640
# doesn't purely test what a Remote* object sends over the wire, and
1641
# how it reacts to responses from the wire. It instead relies partly
1642
# on asserting that the RemoteBranch will call
1643
# self._real_branch.set_last_revision_info(...).
1645
# First, set up our RemoteBranch with a FakeClient that raises
1646
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1647
transport = MemoryTransport()
1648
transport.mkdir('branch')
1649
transport = transport.clone('branch')
1650
client = FakeClient(transport.base)
1651
client.add_expected_call(
1652
'Branch.get_stacked_on_url', ('branch/',),
1653
'error', ('NotStacked',))
1654
client.add_expected_call(
1655
'Branch.last_revision_info',
1657
'success', ('ok', '0', 'null:'))
1658
client.add_expected_call(
1659
'Branch.set_last_revision_info',
1660
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1661
'unknown', 'Branch.set_last_revision_info')
1663
branch = self.make_remote_branch(transport, client)
1664
class StubRealBranch(object):
1667
def set_last_revision_info(self, revno, revision_id):
1669
('set_last_revision_info', revno, revision_id))
1670
def _clear_cached_state(self):
1672
real_branch = StubRealBranch()
1673
branch._real_branch = real_branch
1674
self.lock_remote_branch(branch)
1676
# Call set_last_revision_info, and verify it behaved as expected.
1677
result = branch.set_last_revision_info(1234, 'a-revision-id')
1679
[('set_last_revision_info', 1234, 'a-revision-id')],
1681
self.assertFinished(client)
1683
def test_unexpected_error(self):
1684
# If the server sends an error the client doesn't understand, it gets
1685
# turned into an UnknownErrorFromSmartServer, which is presented as a
1686
# non-internal error to the user.
1687
transport = MemoryTransport()
1688
transport.mkdir('branch')
1689
transport = transport.clone('branch')
1690
client = FakeClient(transport.base)
1691
# get_stacked_on_url
1692
client.add_error_response('NotStacked')
1694
client.add_success_response('ok', 'branch token', 'repo token')
1696
client.add_error_response('UnexpectedError')
1698
client.add_success_response('ok')
1700
branch = self.make_remote_branch(transport, client)
1701
# Lock the branch, reset the record of remote calls.
1705
err = self.assertRaises(
1706
errors.UnknownErrorFromSmartServer,
1707
branch.set_last_revision_info, 123, 'revid')
1708
self.assertEqual(('UnexpectedError',), err.error_tuple)
1711
def test_tip_change_rejected(self):
1712
"""TipChangeRejected responses cause a TipChangeRejected exception to
1715
transport = MemoryTransport()
1716
transport.mkdir('branch')
1717
transport = transport.clone('branch')
1718
client = FakeClient(transport.base)
1719
# get_stacked_on_url
1720
client.add_error_response('NotStacked')
1722
client.add_success_response('ok', 'branch token', 'repo token')
1724
client.add_error_response('TipChangeRejected', 'rejection message')
1726
client.add_success_response('ok')
1728
branch = self.make_remote_branch(transport, client)
1729
# Lock the branch, reset the record of remote calls.
1731
self.addCleanup(branch.unlock)
1734
# The 'TipChangeRejected' error response triggered by calling
1735
# set_last_revision_info causes a TipChangeRejected exception.
1736
err = self.assertRaises(
1737
errors.TipChangeRejected,
1738
branch.set_last_revision_info, 123, 'revid')
1739
self.assertEqual('rejection message', err.msg)
1742
class TestBranchGetSetConfig(RemoteBranchTestCase):
437
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
441
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
442
"""Test branch.control_files api munging...
444
We special case RemoteBranch.control_files.get('branch.conf') to
445
call a specific API so that RemoteBranch's can intercept configuration
446
file reading, allowing them to signal to the client about things like
447
'email is configured for commits'.
1744
450
def test_get_branch_conf(self):
1745
451
# in an empty branch we decode the response properly
1746
client = FakeClient()
1747
client.add_expected_call(
1748
'Branch.get_stacked_on_url', ('memory:///',),
1749
'error', ('NotStacked',),)
1750
client.add_success_response_with_body('# config file body', 'ok')
1751
transport = MemoryTransport()
1752
branch = self.make_remote_branch(transport, client)
1753
config = branch.get_config()
1754
config.has_explicit_nickname()
452
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
453
# we need to make a real branch because the remote_branch.control_files
454
# will trigger _ensure_real.
455
branch = self.make_branch('quack')
456
transport = branch.bzrdir.root_transport
457
# we do not want bzrdir to make any remote calls
458
bzrdir = RemoteBzrDir(transport, _client=False)
459
branch = RemoteBranch(bzrdir, None, _client=client)
460
result = branch.control_files.get('branch.conf')
1755
461
self.assertEqual(
1756
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1757
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
462
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1760
def test_get_multi_line_branch_conf(self):
1761
# Make sure that multiple-line branch.conf files are supported
1763
# https://bugs.launchpad.net/bzr/+bug/354075
1764
client = FakeClient()
1765
client.add_expected_call(
1766
'Branch.get_stacked_on_url', ('memory:///',),
1767
'error', ('NotStacked',),)
1768
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1769
transport = MemoryTransport()
1770
branch = self.make_remote_branch(transport, client)
1771
config = branch.get_config()
1772
self.assertEqual(u'2', config.get_user_option('b'))
1774
def test_set_option(self):
1775
client = FakeClient()
1776
client.add_expected_call(
1777
'Branch.get_stacked_on_url', ('memory:///',),
1778
'error', ('NotStacked',),)
1779
client.add_expected_call(
1780
'Branch.lock_write', ('memory:///', '', ''),
1781
'success', ('ok', 'branch token', 'repo token'))
1782
client.add_expected_call(
1783
'Branch.set_config_option', ('memory:///', 'branch token',
1784
'repo token', 'foo', 'bar', ''),
1786
client.add_expected_call(
1787
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1789
transport = MemoryTransport()
1790
branch = self.make_remote_branch(transport, client)
1792
config = branch._get_config()
1793
config.set_option('foo', 'bar')
1795
self.assertFinished(client)
1797
def test_set_option_with_dict(self):
1798
client = FakeClient()
1799
client.add_expected_call(
1800
'Branch.get_stacked_on_url', ('memory:///',),
1801
'error', ('NotStacked',),)
1802
client.add_expected_call(
1803
'Branch.lock_write', ('memory:///', '', ''),
1804
'success', ('ok', 'branch token', 'repo token'))
1805
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1806
client.add_expected_call(
1807
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1808
'repo token', encoded_dict_value, 'foo', ''),
1810
client.add_expected_call(
1811
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1813
transport = MemoryTransport()
1814
branch = self.make_remote_branch(transport, client)
1816
config = branch._get_config()
1818
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1821
self.assertFinished(client)
1823
def test_backwards_compat_set_option(self):
1824
self.setup_smart_server_with_call_log()
1825
branch = self.make_branch('.')
1826
verb = 'Branch.set_config_option'
1827
self.disable_verb(verb)
1829
self.addCleanup(branch.unlock)
1830
self.reset_smart_call_log()
1831
branch._get_config().set_option('value', 'name')
1832
self.assertLength(10, self.hpss_calls)
1833
self.assertEqual('value', branch._get_config().get_option('name'))
1835
def test_backwards_compat_set_option_with_dict(self):
1836
self.setup_smart_server_with_call_log()
1837
branch = self.make_branch('.')
1838
verb = 'Branch.set_config_option_dict'
1839
self.disable_verb(verb)
1841
self.addCleanup(branch.unlock)
1842
self.reset_smart_call_log()
1843
config = branch._get_config()
1844
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1845
config.set_option(value_dict, 'name')
1846
self.assertLength(10, self.hpss_calls)
1847
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1850
class TestBranchLockWrite(RemoteBranchTestCase):
464
self.assertEqual('config file body', result.read())
467
class TestBranchLockWrite(tests.TestCase):
1852
469
def test_lock_write_unlockable(self):
1853
470
transport = MemoryTransport()
1854
client = FakeClient(transport.base)
1855
client.add_expected_call(
1856
'Branch.get_stacked_on_url', ('quack/',),
1857
'error', ('NotStacked',),)
1858
client.add_expected_call(
1859
'Branch.lock_write', ('quack/', '', ''),
1860
'error', ('UnlockableTransport',))
471
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
1861
472
transport.mkdir('quack')
1862
473
transport = transport.clone('quack')
1863
branch = self.make_remote_branch(transport, client)
474
# we do not want bzrdir to make any remote calls
475
bzrdir = RemoteBzrDir(transport, _client=False)
476
branch = RemoteBranch(bzrdir, None, _client=client)
1864
477
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1865
self.assertFinished(client)
1868
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1870
def test__get_config(self):
1871
client = FakeClient()
1872
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1873
transport = MemoryTransport()
1874
bzrdir = self.make_remote_bzrdir(transport, client)
1875
config = bzrdir.get_config()
1876
self.assertEqual('/', config.get_default_stack_on())
1877
478
self.assertEqual(
1878
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
479
[('call', 'Branch.lock_write', ('quack/', '', ''))],
1881
def test_set_option_uses_vfs(self):
1882
self.setup_smart_server_with_call_log()
1883
bzrdir = self.make_bzrdir('.')
1884
self.reset_smart_call_log()
1885
config = bzrdir.get_config()
1886
config.set_default_stack_on('/')
1887
self.assertLength(3, self.hpss_calls)
1889
def test_backwards_compat_get_option(self):
1890
self.setup_smart_server_with_call_log()
1891
bzrdir = self.make_bzrdir('.')
1892
verb = 'BzrDir.get_config_file'
1893
self.disable_verb(verb)
1894
self.reset_smart_call_log()
1895
self.assertEqual(None,
1896
bzrdir._get_config().get_option('default_stack_on'))
1897
self.assertLength(3, self.hpss_calls)
1900
483
class TestTransportIsReadonly(tests.TestCase):
1902
485
def test_true(self):
1903
client = FakeClient()
1904
client.add_success_response('yes')
486
client = FakeClient([(('yes',), '')])
1905
487
transport = RemoteTransport('bzr://example.com/', medium=False,
1907
489
self.assertEqual(True, transport.is_readonly())
2599
865
self.assertEqual([], client._calls)
2602
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2603
"""Base class for Repository.insert_stream and .insert_stream_1.19
2607
def checkInsertEmptyStream(self, repo, client):
2608
"""Insert an empty stream, checking the result.
2610
This checks that there are no resume_tokens or missing_keys, and that
2611
the client is finished.
2613
sink = repo._get_sink()
2614
fmt = repository.format_registry.get_default()
2615
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2616
self.assertEqual([], resume_tokens)
2617
self.assertEqual(set(), missing_keys)
2618
self.assertFinished(client)
2621
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2622
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2625
This test case is very similar to TestRepositoryInsertStream_1_19.
2629
TestRemoteRepository.setUp(self)
2630
self.disable_verb('Repository.insert_stream_1.19')
2632
def test_unlocked_repo(self):
2633
transport_path = 'quack'
2634
repo, client = self.setup_fake_client_and_repository(transport_path)
2635
client.add_expected_call(
2636
'Repository.insert_stream_1.19', ('quack/', ''),
2637
'unknown', ('Repository.insert_stream_1.19',))
2638
client.add_expected_call(
2639
'Repository.insert_stream', ('quack/', ''),
2641
client.add_expected_call(
2642
'Repository.insert_stream', ('quack/', ''),
2644
self.checkInsertEmptyStream(repo, client)
2646
def test_locked_repo_with_no_lock_token(self):
2647
transport_path = 'quack'
2648
repo, client = self.setup_fake_client_and_repository(transport_path)
2649
client.add_expected_call(
2650
'Repository.lock_write', ('quack/', ''),
2651
'success', ('ok', ''))
2652
client.add_expected_call(
2653
'Repository.insert_stream_1.19', ('quack/', ''),
2654
'unknown', ('Repository.insert_stream_1.19',))
2655
client.add_expected_call(
2656
'Repository.insert_stream', ('quack/', ''),
2658
client.add_expected_call(
2659
'Repository.insert_stream', ('quack/', ''),
2662
self.checkInsertEmptyStream(repo, client)
2664
def test_locked_repo_with_lock_token(self):
2665
transport_path = 'quack'
2666
repo, client = self.setup_fake_client_and_repository(transport_path)
2667
client.add_expected_call(
2668
'Repository.lock_write', ('quack/', ''),
2669
'success', ('ok', 'a token'))
2670
client.add_expected_call(
2671
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2672
'unknown', ('Repository.insert_stream_1.19',))
2673
client.add_expected_call(
2674
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2676
client.add_expected_call(
2677
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2680
self.checkInsertEmptyStream(repo, client)
2682
def test_stream_with_inventory_deltas(self):
2683
"""'inventory-deltas' substreams cannot be sent to the
2684
Repository.insert_stream verb, because not all servers that implement
2685
that verb will accept them. So when one is encountered the RemoteSink
2686
immediately stops using that verb and falls back to VFS insert_stream.
2688
transport_path = 'quack'
2689
repo, client = self.setup_fake_client_and_repository(transport_path)
2690
client.add_expected_call(
2691
'Repository.insert_stream_1.19', ('quack/', ''),
2692
'unknown', ('Repository.insert_stream_1.19',))
2693
client.add_expected_call(
2694
'Repository.insert_stream', ('quack/', ''),
2696
client.add_expected_call(
2697
'Repository.insert_stream', ('quack/', ''),
2699
# Create a fake real repository for insert_stream to fall back on, so
2700
# that we can directly see the records the RemoteSink passes to the
2705
def insert_stream(self, stream, src_format, resume_tokens):
2706
for substream_kind, substream in stream:
2707
self.records.append(
2708
(substream_kind, [record.key for record in substream]))
2709
return ['fake tokens'], ['fake missing keys']
2710
fake_real_sink = FakeRealSink()
2711
class FakeRealRepository:
2712
def _get_sink(self):
2713
return fake_real_sink
2714
def is_in_write_group(self):
2716
def refresh_data(self):
2718
repo._real_repository = FakeRealRepository()
2719
sink = repo._get_sink()
2720
fmt = repository.format_registry.get_default()
2721
stream = self.make_stream_with_inv_deltas(fmt)
2722
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2723
# Every record from the first inventory delta should have been sent to
2725
expected_records = [
2726
('inventory-deltas', [('rev2',), ('rev3',)]),
2727
('texts', [('some-rev', 'some-file')])]
2728
self.assertEqual(expected_records, fake_real_sink.records)
2729
# The return values from the real sink's insert_stream are propagated
2730
# back to the original caller.
2731
self.assertEqual(['fake tokens'], resume_tokens)
2732
self.assertEqual(['fake missing keys'], missing_keys)
2733
self.assertFinished(client)
2735
def make_stream_with_inv_deltas(self, fmt):
2736
"""Make a simple stream with an inventory delta followed by more
2737
records and more substreams to test that all records and substreams
2738
from that point on are used.
2740
This sends, in order:
2741
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2743
* texts substream: (some-rev, some-file)
2745
# Define a stream using generators so that it isn't rewindable.
2746
inv = inventory.Inventory(revision_id='rev1')
2747
inv.root.revision = 'rev1'
2748
def stream_with_inv_delta():
2749
yield ('inventories', inventories_substream())
2750
yield ('inventory-deltas', inventory_delta_substream())
2752
versionedfile.FulltextContentFactory(
2753
('some-rev', 'some-file'), (), None, 'content')])
2754
def inventories_substream():
2755
# An empty inventory fulltext. This will be streamed normally.
2756
text = fmt._serializer.write_inventory_to_string(inv)
2757
yield versionedfile.FulltextContentFactory(
2758
('rev1',), (), None, text)
2759
def inventory_delta_substream():
2760
# An inventory delta. This can't be streamed via this verb, so it
2761
# will trigger a fallback to VFS insert_stream.
2762
entry = inv.make_entry(
2763
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2764
entry.revision = 'ghost'
2765
delta = [(None, 'newdir', 'newdir-id', entry)]
2766
serializer = inventory_delta.InventoryDeltaSerializer(
2767
versioned_root=True, tree_references=False)
2768
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2769
yield versionedfile.ChunkedContentFactory(
2770
('rev2',), (('rev1',)), None, lines)
2772
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2773
yield versionedfile.ChunkedContentFactory(
2774
('rev3',), (('rev1',)), None, lines)
2775
return stream_with_inv_delta()
2778
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2780
def test_unlocked_repo(self):
2781
transport_path = 'quack'
2782
repo, client = self.setup_fake_client_and_repository(transport_path)
2783
client.add_expected_call(
2784
'Repository.insert_stream_1.19', ('quack/', ''),
2786
client.add_expected_call(
2787
'Repository.insert_stream_1.19', ('quack/', ''),
2789
self.checkInsertEmptyStream(repo, client)
2791
def test_locked_repo_with_no_lock_token(self):
2792
transport_path = 'quack'
2793
repo, client = self.setup_fake_client_and_repository(transport_path)
2794
client.add_expected_call(
2795
'Repository.lock_write', ('quack/', ''),
2796
'success', ('ok', ''))
2797
client.add_expected_call(
2798
'Repository.insert_stream_1.19', ('quack/', ''),
2800
client.add_expected_call(
2801
'Repository.insert_stream_1.19', ('quack/', ''),
2804
self.checkInsertEmptyStream(repo, client)
2806
def test_locked_repo_with_lock_token(self):
2807
transport_path = 'quack'
2808
repo, client = self.setup_fake_client_and_repository(transport_path)
2809
client.add_expected_call(
2810
'Repository.lock_write', ('quack/', ''),
2811
'success', ('ok', 'a token'))
2812
client.add_expected_call(
2813
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2815
client.add_expected_call(
2816
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2819
self.checkInsertEmptyStream(repo, client)
2822
868
class TestRepositoryTarball(TestRemoteRepository):
2824
870
# This is a canned tarball reponse we can validate against
2873
921
src_repo.copy_content_into(dest_repo)
2876
class _StubRealPackRepository(object):
2878
def __init__(self, calls):
2880
self._pack_collection = _StubPackCollection(calls)
2882
def is_in_write_group(self):
2885
def refresh_data(self):
2886
self.calls.append(('pack collection reload_pack_names',))
2889
class _StubPackCollection(object):
2891
def __init__(self, calls):
2895
self.calls.append(('pack collection autopack',))
2898
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2899
"""Tests for RemoteRepository.autopack implementation."""
2902
"""When the server returns 'ok' and there's no _real_repository, then
2903
nothing else happens: the autopack method is done.
2905
transport_path = 'quack'
2906
repo, client = self.setup_fake_client_and_repository(transport_path)
2907
client.add_expected_call(
2908
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2910
self.assertFinished(client)
2912
def test_ok_with_real_repo(self):
2913
"""When the server returns 'ok' and there is a _real_repository, then
2914
the _real_repository's reload_pack_name's method will be called.
2916
transport_path = 'quack'
2917
repo, client = self.setup_fake_client_and_repository(transport_path)
2918
client.add_expected_call(
2919
'PackRepository.autopack', ('quack/',),
2921
repo._real_repository = _StubRealPackRepository(client._calls)
2924
[('call', 'PackRepository.autopack', ('quack/',)),
2925
('pack collection reload_pack_names',)],
924
class TestRepositoryStreamKnitData(TestRemoteRepository):
926
def make_pack_file(self, records):
927
pack_file = StringIO()
928
pack_writer = pack.ContainerWriter(pack_file.write)
930
for bytes, names in records:
931
pack_writer.add_bytes_record(bytes, names)
936
def make_pack_stream(self, records):
937
pack_serialiser = pack.ContainerSerialiser()
938
yield pack_serialiser.begin()
939
for bytes, names in records:
940
yield pack_serialiser.bytes_record(bytes, names)
941
yield pack_serialiser.end()
943
def test_bad_pack_from_server(self):
944
"""A response with invalid data (e.g. it has a record with multiple
945
names) triggers an exception.
947
Not all possible errors will be caught at this stage, but obviously
948
malformed data should be.
950
record = ('bytes', [('name1',), ('name2',)])
951
pack_stream = self.make_pack_stream([record])
952
responses = [(('ok',), pack_stream), ]
953
transport_path = 'quack'
954
repo, client = self.setup_fake_client_and_repository(
955
responses, transport_path)
956
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
957
stream = repo.get_data_stream_for_search(search)
958
self.assertRaises(errors.SmartProtocolError, list, stream)
2928
960
def test_backwards_compatibility(self):
2929
"""If the server does not recognise the PackRepository.autopack verb,
2930
fallback to the real_repository's implementation.
2932
transport_path = 'quack'
2933
repo, client = self.setup_fake_client_and_repository(transport_path)
2934
client.add_unknown_method_response('PackRepository.autopack')
2935
def stub_ensure_real():
2936
client._calls.append(('_ensure_real',))
2937
repo._real_repository = _StubRealPackRepository(client._calls)
2938
repo._ensure_real = stub_ensure_real
2941
[('call', 'PackRepository.autopack', ('quack/',)),
2943
('pack collection autopack',)],
2946
def test_oom_error_reporting(self):
2947
"""An out-of-memory condition on the server is reported clearly"""
2948
transport_path = 'quack'
2949
repo, client = self.setup_fake_client_and_repository(transport_path)
2950
client.add_expected_call(
2951
'PackRepository.autopack', ('quack/',),
2952
'error', ('MemoryError',))
2953
err = self.assertRaises(errors.BzrError, repo.autopack)
2954
self.assertContainsRe(str(err), "^remote server out of mem")
2957
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2958
"""Base class for unit tests for bzrlib.remote._translate_error."""
2960
def translateTuple(self, error_tuple, **context):
2961
"""Call _translate_error with an ErrorFromSmartServer built from the
2964
:param error_tuple: A tuple of a smart server response, as would be
2965
passed to an ErrorFromSmartServer.
2966
:kwargs context: context items to call _translate_error with.
2968
:returns: The error raised by _translate_error.
2970
# Raise the ErrorFromSmartServer before passing it as an argument,
2971
# because _translate_error may need to re-raise it with a bare 'raise'
2973
server_error = errors.ErrorFromSmartServer(error_tuple)
2974
translated_error = self.translateErrorFromSmartServer(
2975
server_error, **context)
2976
return translated_error
2978
def translateErrorFromSmartServer(self, error_object, **context):
2979
"""Like translateTuple, but takes an already constructed
2980
ErrorFromSmartServer rather than a tuple.
2984
except errors.ErrorFromSmartServer, server_error:
2985
translated_error = self.assertRaises(
2986
errors.BzrError, remote._translate_error, server_error,
2988
return translated_error
2991
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2992
"""Unit tests for bzrlib.remote._translate_error.
2994
Given an ErrorFromSmartServer (which has an error tuple from a smart
2995
server) and some context, _translate_error raises more specific errors from
2998
This test case covers the cases where _translate_error succeeds in
2999
translating an ErrorFromSmartServer to something better. See
3000
TestErrorTranslationRobustness for other cases.
3003
def test_NoSuchRevision(self):
3004
branch = self.make_branch('')
3006
translated_error = self.translateTuple(
3007
('NoSuchRevision', revid), branch=branch)
3008
expected_error = errors.NoSuchRevision(branch, revid)
3009
self.assertEqual(expected_error, translated_error)
3011
def test_nosuchrevision(self):
3012
repository = self.make_repository('')
3014
translated_error = self.translateTuple(
3015
('nosuchrevision', revid), repository=repository)
3016
expected_error = errors.NoSuchRevision(repository, revid)
3017
self.assertEqual(expected_error, translated_error)
3019
def test_nobranch(self):
3020
bzrdir = self.make_bzrdir('')
3021
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3022
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3023
self.assertEqual(expected_error, translated_error)
3025
def test_nobranch_one_arg(self):
3026
bzrdir = self.make_bzrdir('')
3027
translated_error = self.translateTuple(
3028
('nobranch', 'extra detail'), bzrdir=bzrdir)
3029
expected_error = errors.NotBranchError(
3030
path=bzrdir.root_transport.base,
3031
detail='extra detail')
3032
self.assertEqual(expected_error, translated_error)
3034
def test_norepository(self):
3035
bzrdir = self.make_bzrdir('')
3036
translated_error = self.translateTuple(('norepository',),
3038
expected_error = errors.NoRepositoryPresent(bzrdir)
3039
self.assertEqual(expected_error, translated_error)
3041
def test_LockContention(self):
3042
translated_error = self.translateTuple(('LockContention',))
3043
expected_error = errors.LockContention('(remote lock)')
3044
self.assertEqual(expected_error, translated_error)
3046
def test_UnlockableTransport(self):
3047
bzrdir = self.make_bzrdir('')
3048
translated_error = self.translateTuple(
3049
('UnlockableTransport',), bzrdir=bzrdir)
3050
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3051
self.assertEqual(expected_error, translated_error)
3053
def test_LockFailed(self):
3054
lock = 'str() of a server lock'
3055
why = 'str() of why'
3056
translated_error = self.translateTuple(('LockFailed', lock, why))
3057
expected_error = errors.LockFailed(lock, why)
3058
self.assertEqual(expected_error, translated_error)
3060
def test_TokenMismatch(self):
3061
token = 'a lock token'
3062
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3063
expected_error = errors.TokenMismatch(token, '(remote token)')
3064
self.assertEqual(expected_error, translated_error)
3066
def test_Diverged(self):
3067
branch = self.make_branch('a')
3068
other_branch = self.make_branch('b')
3069
translated_error = self.translateTuple(
3070
('Diverged',), branch=branch, other_branch=other_branch)
3071
expected_error = errors.DivergedBranches(branch, other_branch)
3072
self.assertEqual(expected_error, translated_error)
3074
def test_NotStacked(self):
3075
branch = self.make_branch('')
3076
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3077
expected_error = errors.NotStacked(branch)
3078
self.assertEqual(expected_error, translated_error)
3080
def test_ReadError_no_args(self):
3082
translated_error = self.translateTuple(('ReadError',), path=path)
3083
expected_error = errors.ReadError(path)
3084
self.assertEqual(expected_error, translated_error)
3086
def test_ReadError(self):
3088
translated_error = self.translateTuple(('ReadError', path))
3089
expected_error = errors.ReadError(path)
3090
self.assertEqual(expected_error, translated_error)
3092
def test_IncompatibleRepositories(self):
3093
translated_error = self.translateTuple(('IncompatibleRepositories',
3094
"repo1", "repo2", "details here"))
3095
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3097
self.assertEqual(expected_error, translated_error)
3099
def test_PermissionDenied_no_args(self):
3101
translated_error = self.translateTuple(('PermissionDenied',),
3103
expected_error = errors.PermissionDenied(path)
3104
self.assertEqual(expected_error, translated_error)
3106
def test_PermissionDenied_one_arg(self):
3108
translated_error = self.translateTuple(('PermissionDenied', path))
3109
expected_error = errors.PermissionDenied(path)
3110
self.assertEqual(expected_error, translated_error)
3112
def test_PermissionDenied_one_arg_and_context(self):
3113
"""Given a choice between a path from the local context and a path on
3114
the wire, _translate_error prefers the path from the local context.
3116
local_path = 'local path'
3117
remote_path = 'remote path'
3118
translated_error = self.translateTuple(
3119
('PermissionDenied', remote_path), path=local_path)
3120
expected_error = errors.PermissionDenied(local_path)
3121
self.assertEqual(expected_error, translated_error)
3123
def test_PermissionDenied_two_args(self):
3125
extra = 'a string with extra info'
3126
translated_error = self.translateTuple(
3127
('PermissionDenied', path, extra))
3128
expected_error = errors.PermissionDenied(path, extra)
3129
self.assertEqual(expected_error, translated_error)
3131
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3133
def test_NoSuchFile_context_path(self):
3134
local_path = "local path"
3135
translated_error = self.translateTuple(('ReadError', "remote path"),
3137
expected_error = errors.ReadError(local_path)
3138
self.assertEqual(expected_error, translated_error)
3140
def test_NoSuchFile_without_context(self):
3141
remote_path = "remote path"
3142
translated_error = self.translateTuple(('ReadError', remote_path))
3143
expected_error = errors.ReadError(remote_path)
3144
self.assertEqual(expected_error, translated_error)
3146
def test_ReadOnlyError(self):
3147
translated_error = self.translateTuple(('ReadOnlyError',))
3148
expected_error = errors.TransportNotPossible("readonly transport")
3149
self.assertEqual(expected_error, translated_error)
3151
def test_MemoryError(self):
3152
translated_error = self.translateTuple(('MemoryError',))
3153
self.assertStartsWith(str(translated_error),
3154
"remote server out of memory")
3156
def test_generic_IndexError_no_classname(self):
3157
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3158
translated_error = self.translateErrorFromSmartServer(err)
3159
expected_error = errors.UnknownErrorFromSmartServer(err)
3160
self.assertEqual(expected_error, translated_error)
3162
# GZ 2011-03-02: TODO test generic non-ascii error string
3164
def test_generic_KeyError(self):
3165
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3166
translated_error = self.translateErrorFromSmartServer(err)
3167
expected_error = errors.UnknownErrorFromSmartServer(err)
3168
self.assertEqual(expected_error, translated_error)
3171
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3172
"""Unit tests for bzrlib.remote._translate_error's robustness.
3174
TestErrorTranslationSuccess is for cases where _translate_error can
3175
translate successfully. This class about how _translate_err behaves when
3176
it fails to translate: it re-raises the original error.
3179
def test_unrecognised_server_error(self):
3180
"""If the error code from the server is not recognised, the original
3181
ErrorFromSmartServer is propagated unmodified.
3183
error_tuple = ('An unknown error tuple',)
3184
server_error = errors.ErrorFromSmartServer(error_tuple)
3185
translated_error = self.translateErrorFromSmartServer(server_error)
3186
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3187
self.assertEqual(expected_error, translated_error)
3189
def test_context_missing_a_key(self):
3190
"""In case of a bug in the client, or perhaps an unexpected response
3191
from a server, _translate_error returns the original error tuple from
3192
the server and mutters a warning.
3194
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3195
# in the context dict. So let's give it an empty context dict instead
3196
# to exercise its error recovery.
3198
error_tuple = ('NoSuchRevision', 'revid')
3199
server_error = errors.ErrorFromSmartServer(error_tuple)
3200
translated_error = self.translateErrorFromSmartServer(server_error)
3201
self.assertEqual(server_error, translated_error)
3202
# In addition to re-raising ErrorFromSmartServer, some debug info has
3203
# been muttered to the log file for developer to look at.
3204
self.assertContainsRe(
3206
"Missing key 'branch' in context")
3208
def test_path_missing(self):
3209
"""Some translations (PermissionDenied, ReadError) can determine the
3210
'path' variable from either the wire or the local context. If neither
3211
has it, then an error is raised.
3213
error_tuple = ('ReadError',)
3214
server_error = errors.ErrorFromSmartServer(error_tuple)
3215
translated_error = self.translateErrorFromSmartServer(server_error)
3216
self.assertEqual(server_error, translated_error)
3217
# In addition to re-raising ErrorFromSmartServer, some debug info has
3218
# been muttered to the log file for developer to look at.
3219
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3222
class TestStacking(tests.TestCaseWithTransport):
3223
"""Tests for operations on stacked remote repositories.
3225
The underlying format type must support stacking.
3228
def test_access_stacked_remote(self):
3229
# based on <http://launchpad.net/bugs/261315>
3230
# make a branch stacked on another repository containing an empty
3231
# revision, then open it over hpss - we should be able to see that
3233
base_transport = self.get_transport()
3234
base_builder = self.make_branch_builder('base', format='1.9')
3235
base_builder.start_series()
3236
base_revid = base_builder.build_snapshot('rev-id', None,
3237
[('add', ('', None, 'directory', None))],
3239
base_builder.finish_series()
3240
stacked_branch = self.make_branch('stacked', format='1.9')
3241
stacked_branch.set_stacked_on_url('../base')
3242
# start a server looking at this
3243
smart_server = test_server.SmartTCPServer_for_testing()
3244
self.start_server(smart_server)
3245
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3246
# can get its branch and repository
3247
remote_branch = remote_bzrdir.open_branch()
3248
remote_repo = remote_branch.repository
3249
remote_repo.lock_read()
3251
# it should have an appropriate fallback repository, which should also
3252
# be a RemoteRepository
3253
self.assertLength(1, remote_repo._fallback_repositories)
3254
self.assertIsInstance(remote_repo._fallback_repositories[0],
3256
# and it has the revision committed to the underlying repository;
3257
# these have varying implementations so we try several of them
3258
self.assertTrue(remote_repo.has_revisions([base_revid]))
3259
self.assertTrue(remote_repo.has_revision(base_revid))
3260
self.assertEqual(remote_repo.get_revision(base_revid).message,
3263
remote_repo.unlock()
3265
def prepare_stacked_remote_branch(self):
3266
"""Get stacked_upon and stacked branches with content in each."""
3267
self.setup_smart_server_with_call_log()
3268
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3269
tree1.commit('rev1', rev_id='rev1')
3270
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3271
).open_workingtree()
3272
local_tree = tree2.branch.create_checkout('local')
3273
local_tree.commit('local changes make me feel good.')
3274
branch2 = Branch.open(self.get_url('tree2'))
3276
self.addCleanup(branch2.unlock)
3277
return tree1.branch, branch2
3279
def test_stacked_get_parent_map(self):
3280
# the public implementation of get_parent_map obeys stacking
3281
_, branch = self.prepare_stacked_remote_branch()
3282
repo = branch.repository
3283
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3285
def test_unstacked_get_parent_map(self):
3286
# _unstacked_provider.get_parent_map ignores stacking
3287
_, branch = self.prepare_stacked_remote_branch()
3288
provider = branch.repository._unstacked_provider
3289
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3291
def fetch_stream_to_rev_order(self, stream):
3293
for kind, substream in stream:
3294
if not kind == 'revisions':
3297
for content in substream:
3298
result.append(content.key[-1])
3301
def get_ordered_revs(self, format, order, branch_factory=None):
3302
"""Get a list of the revisions in a stream to format format.
3304
:param format: The format of the target.
3305
:param order: the order that target should have requested.
3306
:param branch_factory: A callable to create a trunk and stacked branch
3307
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3308
:result: The revision ids in the stream, in the order seen,
3309
the topological order of revisions in the source.
3311
unordered_format = bzrdir.format_registry.get(format)()
3312
target_repository_format = unordered_format.repository_format
3314
self.assertEqual(order, target_repository_format._fetch_order)
3315
if branch_factory is None:
3316
branch_factory = self.prepare_stacked_remote_branch
3317
_, stacked = branch_factory()
3318
source = stacked.repository._get_source(target_repository_format)
3319
tip = stacked.last_revision()
3320
stacked.repository._ensure_real()
3321
graph = stacked.repository.get_graph()
3322
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3323
if r != NULL_REVISION]
3325
search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
3326
self.reset_smart_call_log()
3327
stream = source.get_stream(search)
3328
# We trust that if a revision is in the stream the rest of the new
3329
# content for it is too, as per our main fetch tests; here we are
3330
# checking that the revisions are actually included at all, and their
3332
return self.fetch_stream_to_rev_order(stream), revs
3334
def test_stacked_get_stream_unordered(self):
3335
# Repository._get_source.get_stream() from a stacked repository with
3336
# unordered yields the full data from both stacked and stacked upon
3338
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3339
self.assertEqual(set(expected_revs), set(rev_ord))
3340
# Getting unordered results should have made a streaming data request
3341
# from the server, then one from the backing branch.
3342
self.assertLength(2, self.hpss_calls)
3344
def test_stacked_on_stacked_get_stream_unordered(self):
3345
# Repository._get_source.get_stream() from a stacked repository which
3346
# is itself stacked yields the full data from all three sources.
3347
def make_stacked_stacked():
3348
_, stacked = self.prepare_stacked_remote_branch()
3349
tree = stacked.bzrdir.sprout('tree3', stacked=True
3350
).open_workingtree()
3351
local_tree = tree.branch.create_checkout('local-tree3')
3352
local_tree.commit('more local changes are better')
3353
branch = Branch.open(self.get_url('tree3'))
3355
self.addCleanup(branch.unlock)
3357
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3358
branch_factory=make_stacked_stacked)
3359
self.assertEqual(set(expected_revs), set(rev_ord))
3360
# Getting unordered results should have made a streaming data request
3361
# from the server, and one from each backing repo
3362
self.assertLength(3, self.hpss_calls)
3364
def test_stacked_get_stream_topological(self):
3365
# Repository._get_source.get_stream() from a stacked repository with
3366
# topological sorting yields the full data from both stacked and
3367
# stacked upon sources in topological order.
3368
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3369
self.assertEqual(expected_revs, rev_ord)
3370
# Getting topological sort requires VFS calls still - one of which is
3371
# pushing up from the bound branch.
3372
self.assertLength(14, self.hpss_calls)
3374
def test_stacked_get_stream_groupcompress(self):
3375
# Repository._get_source.get_stream() from a stacked repository with
3376
# groupcompress sorting yields the full data from both stacked and
3377
# stacked upon sources in groupcompress order.
3378
raise tests.TestSkipped('No groupcompress ordered format available')
3379
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3380
self.assertEqual(expected_revs, reversed(rev_ord))
3381
# Getting unordered results should have made a streaming data request
3382
# from the backing branch, and one from the stacked on branch.
3383
self.assertLength(2, self.hpss_calls)
3385
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3386
# When pulling some fixed amount of content that is more than the
3387
# source has (because some is coming from a fallback branch, no error
3388
# should be received. This was reported as bug 360791.
3389
# Need three branches: a trunk, a stacked branch, and a preexisting
3390
# branch pulling content from stacked and trunk.
3391
self.setup_smart_server_with_call_log()
3392
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3393
r1 = trunk.commit('start')
3394
stacked_branch = trunk.branch.create_clone_on_transport(
3395
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3396
local = self.make_branch('local', format='1.9-rich-root')
3397
local.repository.fetch(stacked_branch.repository,
3398
stacked_branch.last_revision())
3401
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3404
super(TestRemoteBranchEffort, self).setUp()
3405
# Create a smart server that publishes whatever the backing VFS server
3407
self.smart_server = test_server.SmartTCPServer_for_testing()
3408
self.start_server(self.smart_server, self.get_server())
3409
# Log all HPSS calls into self.hpss_calls.
3410
_SmartClient.hooks.install_named_hook(
3411
'call', self.capture_hpss_call, None)
3412
self.hpss_calls = []
3414
def capture_hpss_call(self, params):
3415
self.hpss_calls.append(params.method)
3417
def test_copy_content_into_avoids_revision_history(self):
3418
local = self.make_branch('local')
3419
builder = self.make_branch_builder('remote')
3420
builder.build_commit(message="Commit.")
3421
remote_branch_url = self.smart_server.get_url() + 'remote'
3422
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3423
local.repository.fetch(remote_branch.repository)
3424
self.hpss_calls = []
3425
remote_branch.copy_content_into(local)
3426
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3428
def test_fetch_everything_needs_just_one_call(self):
3429
local = self.make_branch('local')
3430
builder = self.make_branch_builder('remote')
3431
builder.build_commit(message="Commit.")
3432
remote_branch_url = self.smart_server.get_url() + 'remote'
3433
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3434
self.hpss_calls = []
3435
local.repository.fetch(
3436
remote_branch.repository,
3437
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3438
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3440
def override_verb(self, verb_name, verb):
3441
request_handlers = request.request_handlers
3442
orig_verb = request_handlers.get(verb_name)
3443
request_handlers.register(verb_name, verb, override_existing=True)
3444
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3445
override_existing=True)
3447
def test_fetch_everything_backwards_compat(self):
3448
"""Can fetch with EverythingResult even with pre 2.4 servers.
3450
Pre-2.4 do not support 'everything' searches with the
3451
Repository.get_stream_1.19 verb.
3454
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3455
"""A version of the Repository.get_stream_1.19 verb patched to
3456
reject 'everything' searches the way 2.3 and earlier do.
3458
def recreate_search(self, repository, search_bytes,
3459
discard_excess=False):
3460
verb_log.append(search_bytes.split('\n', 1)[0])
3461
if search_bytes == 'everything':
3463
request.FailedSmartServerResponse(('BadSearch',)))
3464
return super(OldGetStreamVerb,
3465
self).recreate_search(repository, search_bytes,
3466
discard_excess=discard_excess)
3467
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3468
local = self.make_branch('local')
3469
builder = self.make_branch_builder('remote')
3470
builder.build_commit(message="Commit.")
3471
remote_branch_url = self.smart_server.get_url() + 'remote'
3472
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3473
self.hpss_calls = []
3474
local.repository.fetch(
3475
remote_branch.repository,
3476
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3477
# make sure the overridden verb was used
3478
self.assertLength(1, verb_log)
3479
# more than one HPSS call is needed, but because it's a VFS callback
3480
# its hard to predict exactly how many.
3481
self.assertTrue(len(self.hpss_calls) > 1)
3484
class TestUpdateBoundBranchWithModifiedBoundLocation(
3485
tests.TestCaseWithTransport):
3486
"""Ensure correct handling of bound_location modifications.
3488
This is tested against a smart server as http://pad.lv/786980 was about a
3489
ReadOnlyError (write attempt during a read-only transaction) which can only
3490
happen in this context.
3494
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
3495
self.transport_server = test_server.SmartTCPServer_for_testing
3497
def make_master_and_checkout(self, master_name, checkout_name):
3498
# Create the master branch and its associated checkout
3499
self.master = self.make_branch_and_tree(master_name)
3500
self.checkout = self.master.branch.create_checkout(checkout_name)
3501
# Modify the master branch so there is something to update
3502
self.master.commit('add stuff')
3503
self.last_revid = self.master.commit('even more stuff')
3504
self.bound_location = self.checkout.branch.get_bound_location()
3506
def assertUpdateSucceeds(self, new_location):
3507
self.checkout.branch.set_bound_location(new_location)
3508
self.checkout.update()
3509
self.assertEquals(self.last_revid, self.checkout.last_revision())
3511
def test_without_final_slash(self):
3512
self.make_master_and_checkout('master', 'checkout')
3513
# For unclear reasons some users have a bound_location without a final
3514
# '/', simulate that by forcing such a value
3515
self.assertEndsWith(self.bound_location, '/')
3516
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
3518
def test_plus_sign(self):
3519
self.make_master_and_checkout('+master', 'checkout')
3520
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
3522
def test_tilda(self):
3523
# Embed ~ in the middle of the path just to avoid any $HOME
3525
self.make_master_and_checkout('mas~ter', 'checkout')
3526
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
961
"""If the server doesn't recognise this request, fallback to VFS."""
963
"Generic bzr smart protocol error: "
964
"bad request 'Repository.stream_revisions_chunked'")
966
(('error', error_msg), '')]
967
repo, client = self.setup_fake_client_and_repository(
969
self.mock_called = False
970
repo._real_repository = MockRealRepository(self)
971
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
972
repo.get_data_stream_for_search(search)
973
self.assertTrue(self.mock_called)
974
self.failIf(client.expecting_body,
975
"The protocol has been left in an unclean state that will cause "
976
"TooManyConcurrentRequests errors.")
979
class MockRealRepository(object):
980
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
982
def __init__(self, test):
985
def get_data_stream_for_search(self, search):
986
self.test.assertEqual(set(['revid']), search.get_keys())
987
self.test.mock_called = True