136
106
b = BzrDir.open_from_transport(self.transport).open_branch()
137
107
self.assertStartsWith(str(b), 'RemoteBranch(')
139
def test_remote_bzrdir_repr(self):
140
b = BzrDir.open_from_transport(self.transport)
141
self.assertStartsWith(str(b), 'RemoteBzrDir(')
143
def test_remote_branch_format_supports_stacking(self):
145
self.make_branch('unstackable', format='pack-0.92')
146
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
147
self.assertFalse(b._format.supports_stacking())
148
self.make_branch('stackable', format='1.9')
149
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
150
self.assertTrue(b._format.supports_stacking())
152
def test_remote_repo_format_supports_external_references(self):
154
bd = self.make_bzrdir('unstackable', format='pack-0.92')
155
r = bd.create_repository()
156
self.assertFalse(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
158
self.assertFalse(r._format.supports_external_lookups)
159
bd = self.make_bzrdir('stackable', format='1.9')
160
r = bd.create_repository()
161
self.assertTrue(r._format.supports_external_lookups)
162
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
163
self.assertTrue(r._format.supports_external_lookups)
165
def test_remote_branch_set_append_revisions_only(self):
166
# Make a format 1.9 branch, which supports append_revisions_only
167
branch = self.make_branch('branch', format='1.9')
168
config = branch.get_config()
169
branch.set_append_revisions_only(True)
171
'True', config.get_user_option('append_revisions_only'))
172
branch.set_append_revisions_only(False)
174
'False', config.get_user_option('append_revisions_only'))
176
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
177
branch = self.make_branch('branch', format='knit')
178
config = branch.get_config()
180
errors.UpgradeRequired, branch.set_append_revisions_only, True)
110
class FakeRemoteTransport(object):
111
"""This class provides the minimum support for use in place of a RemoteTransport.
113
It doesn't actually transmit requests, but rather expects them to be
114
handled by a FakeClient which holds canned responses. It does not allow
115
any vfs access, therefore is not suitable for testing any operation that
116
will fallback to vfs access. Backing the test by an instance of this
117
class guarantees that it's - done using non-vfs operations.
120
_default_url = 'fakeremotetransport://host/path/'
122
def __init__(self, url=None):
124
url = self._default_url
128
return "%r(%r)" % (self.__class__.__name__,
131
def clone(self, relpath):
132
return FakeRemoteTransport(urlutils.join(self.base, relpath))
134
def get(self, relpath):
135
# only get is specifically stubbed out, because it's usually the first
136
# thing we do. anything else will fail with an AttributeError.
137
raise AssertionError("%r doesn't support file access to %r"
183
142
class FakeProtocol(object):
419
347
# Calling _remember_remote_is_before again with a lower value works.
420
348
client_medium._remember_remote_is_before((1, 5))
421
349
self.assertTrue(client_medium._is_remote_before((1, 5)))
422
# If you call _remember_remote_is_before with a higher value it logs a
423
# warning, and continues to remember the lower value.
424
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
425
client_medium._remember_remote_is_before((1, 9))
426
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
427
self.assertTrue(client_medium._is_remote_before((1, 5)))
430
class TestBzrDirCloningMetaDir(TestRemote):
432
def test_backwards_compat(self):
433
self.setup_smart_server_with_call_log()
434
a_dir = self.make_bzrdir('.')
435
self.reset_smart_call_log()
436
verb = 'BzrDir.cloning_metadir'
437
self.disable_verb(verb)
438
format = a_dir.cloning_metadir()
439
call_count = len([call for call in self.hpss_calls if
440
call.call.method == verb])
441
self.assertEqual(1, call_count)
443
def test_branch_reference(self):
444
transport = self.get_transport('quack')
445
referenced = self.make_branch('referenced')
446
expected = referenced.bzrdir.cloning_metadir()
447
client = FakeClient(transport.base)
448
client.add_expected_call(
449
'BzrDir.cloning_metadir', ('quack/', 'False'),
450
'error', ('BranchReference',)),
451
client.add_expected_call(
452
'BzrDir.open_branchV3', ('quack/',),
453
'success', ('ref', self.get_url('referenced'))),
454
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
456
result = a_bzrdir.cloning_metadir()
457
# We should have got a control dir matching the referenced branch.
458
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
459
self.assertEqual(expected._repository_format, result._repository_format)
460
self.assertEqual(expected._branch_format, result._branch_format)
461
self.assertFinished(client)
463
def test_current_server(self):
464
transport = self.get_transport('.')
465
transport = transport.clone('quack')
466
self.make_bzrdir('quack')
467
client = FakeClient(transport.base)
468
reference_bzrdir_format = bzrdir.format_registry.get('default')()
469
control_name = reference_bzrdir_format.network_name()
470
client.add_expected_call(
471
'BzrDir.cloning_metadir', ('quack/', 'False'),
472
'success', (control_name, '', ('branch', ''))),
473
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
475
result = a_bzrdir.cloning_metadir()
476
# We should have got a reference control dir with default branch and
477
# repository formats.
478
# This pokes a little, just to be sure.
479
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
480
self.assertEqual(None, result._repository_format)
481
self.assertEqual(None, result._branch_format)
482
self.assertFinished(client)
485
class TestBzrDirOpen(TestRemote):
487
def make_fake_client_and_transport(self, path='quack'):
488
transport = MemoryTransport()
489
transport.mkdir(path)
490
transport = transport.clone(path)
491
client = FakeClient(transport.base)
492
return client, transport
494
def test_absent(self):
495
client, transport = self.make_fake_client_and_transport()
496
client.add_expected_call(
497
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
498
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
499
RemoteBzrDirFormat(), _client=client, _force_probe=True)
500
self.assertFinished(client)
502
def test_present_without_workingtree(self):
503
client, transport = self.make_fake_client_and_transport()
504
client.add_expected_call(
505
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
506
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
507
_client=client, _force_probe=True)
508
self.assertIsInstance(bd, RemoteBzrDir)
509
self.assertFalse(bd.has_workingtree())
510
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
511
self.assertFinished(client)
513
def test_present_with_workingtree(self):
514
client, transport = self.make_fake_client_and_transport()
515
client.add_expected_call(
516
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
517
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
518
_client=client, _force_probe=True)
519
self.assertIsInstance(bd, RemoteBzrDir)
520
self.assertTrue(bd.has_workingtree())
521
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
522
self.assertFinished(client)
524
def test_backwards_compat(self):
525
client, transport = self.make_fake_client_and_transport()
526
client.add_expected_call(
527
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
528
client.add_expected_call(
529
'BzrDir.open', ('quack/',), 'success', ('yes',))
530
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
531
_client=client, _force_probe=True)
532
self.assertIsInstance(bd, RemoteBzrDir)
533
self.assertFinished(client)
535
def test_backwards_compat_hpss_v2(self):
536
client, transport = self.make_fake_client_and_transport()
537
# Monkey-patch fake client to simulate real-world behaviour with v2
538
# server: upon first RPC call detect the protocol version, and because
539
# the version is 2 also do _remember_remote_is_before((1, 6)) before
540
# continuing with the RPC.
541
orig_check_call = client._check_call
542
def check_call(method, args):
543
client._medium._protocol_version = 2
544
client._medium._remember_remote_is_before((1, 6))
545
client._check_call = orig_check_call
546
client._check_call(method, args)
547
client._check_call = check_call
548
client.add_expected_call(
549
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
550
client.add_expected_call(
551
'BzrDir.open', ('quack/',), 'success', ('yes',))
552
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
553
_client=client, _force_probe=True)
554
self.assertIsInstance(bd, RemoteBzrDir)
555
self.assertFinished(client)
558
class TestBzrDirOpenBranch(TestRemote):
560
def test_backwards_compat(self):
561
self.setup_smart_server_with_call_log()
562
self.make_branch('.')
563
a_dir = BzrDir.open(self.get_url('.'))
564
self.reset_smart_call_log()
565
verb = 'BzrDir.open_branchV3'
566
self.disable_verb(verb)
567
format = a_dir.open_branch()
568
call_count = len([call for call in self.hpss_calls if
569
call.call.method == verb])
570
self.assertEqual(1, call_count)
350
# You cannot call _remember_remote_is_before with a larger value.
352
AssertionError, client_medium._remember_remote_is_before, (1, 9))
355
class TestBzrDirOpenBranch(tests.TestCase):
572
357
def test_branch_present(self):
573
reference_format = self.get_repo_format()
574
network_name = reference_format.network_name()
575
branch_network_name = self.get_branch_format().network_name()
576
358
transport = MemoryTransport()
577
359
transport.mkdir('quack')
578
360
transport = transport.clone('quack')
579
361
client = FakeClient(transport.base)
580
362
client.add_expected_call(
581
'BzrDir.open_branchV3', ('quack/',),
582
'success', ('branch', branch_network_name))
363
'BzrDir.open_branch', ('quack/',),
364
'success', ('ok', ''))
583
365
client.add_expected_call(
584
'BzrDir.find_repositoryV3', ('quack/',),
585
'success', ('ok', '', 'no', 'no', 'no', network_name))
366
'BzrDir.find_repositoryV2', ('quack/',),
367
'success', ('ok', '', 'no', 'no', 'no'))
586
368
client.add_expected_call(
587
369
'Branch.get_stacked_on_url', ('quack/',),
588
370
'error', ('NotStacked',))
589
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
371
bzrdir = RemoteBzrDir(transport, _client=client)
591
372
result = bzrdir.open_branch()
592
373
self.assertIsInstance(result, RemoteBranch)
593
374
self.assertEqual(bzrdir, result.bzrdir)
594
self.assertFinished(client)
375
client.finished_test()
596
377
def test_branch_missing(self):
597
378
transport = MemoryTransport()
689
460
self.assertRaises(errors.NotBranchError,
690
RemoteBzrProber.probe_transport, OldServerTransport())
693
class TestBzrDirCreateBranch(TestRemote):
695
def test_backwards_compat(self):
696
self.setup_smart_server_with_call_log()
697
repo = self.make_repository('.')
698
self.reset_smart_call_log()
699
self.disable_verb('BzrDir.create_branch')
700
branch = repo.bzrdir.create_branch()
701
create_branch_call_count = len([call for call in self.hpss_calls if
702
call.call.method == 'BzrDir.create_branch'])
703
self.assertEqual(1, create_branch_call_count)
705
def test_current_server(self):
706
transport = self.get_transport('.')
707
transport = transport.clone('quack')
708
self.make_repository('quack')
709
client = FakeClient(transport.base)
710
reference_bzrdir_format = bzrdir.format_registry.get('default')()
711
reference_format = reference_bzrdir_format.get_branch_format()
712
network_name = reference_format.network_name()
713
reference_repo_fmt = reference_bzrdir_format.repository_format
714
reference_repo_name = reference_repo_fmt.network_name()
715
client.add_expected_call(
716
'BzrDir.create_branch', ('quack/', network_name),
717
'success', ('ok', network_name, '', 'no', 'no', 'yes',
718
reference_repo_name))
719
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
721
branch = a_bzrdir.create_branch()
722
# We should have got a remote branch
723
self.assertIsInstance(branch, remote.RemoteBranch)
724
# its format should have the settings from the response
725
format = branch._format
726
self.assertEqual(network_name, format.network_name())
728
def test_already_open_repo_and_reused_medium(self):
729
"""Bug 726584: create_branch(..., repository=repo) should work
730
regardless of what the smart medium's base URL is.
732
self.transport_server = test_server.SmartTCPServer_for_testing
733
transport = self.get_transport('.')
734
repo = self.make_repository('quack')
735
# Client's medium rooted a transport root (not at the bzrdir)
736
client = FakeClient(transport.base)
737
transport = transport.clone('quack')
738
reference_bzrdir_format = bzrdir.format_registry.get('default')()
739
reference_format = reference_bzrdir_format.get_branch_format()
740
network_name = reference_format.network_name()
741
reference_repo_fmt = reference_bzrdir_format.repository_format
742
reference_repo_name = reference_repo_fmt.network_name()
743
client.add_expected_call(
744
'BzrDir.create_branch', ('extra/quack/', network_name),
745
'success', ('ok', network_name, '', 'no', 'no', 'yes',
746
reference_repo_name))
747
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
749
branch = a_bzrdir.create_branch(repository=repo)
750
# We should have got a remote branch
751
self.assertIsInstance(branch, remote.RemoteBranch)
752
# its format should have the settings from the response
753
format = branch._format
754
self.assertEqual(network_name, format.network_name())
757
class TestBzrDirCreateRepository(TestRemote):
759
def test_backwards_compat(self):
760
self.setup_smart_server_with_call_log()
761
bzrdir = self.make_bzrdir('.')
762
self.reset_smart_call_log()
763
self.disable_verb('BzrDir.create_repository')
764
repo = bzrdir.create_repository()
765
create_repo_call_count = len([call for call in self.hpss_calls if
766
call.call.method == 'BzrDir.create_repository'])
767
self.assertEqual(1, create_repo_call_count)
769
def test_current_server(self):
770
transport = self.get_transport('.')
771
transport = transport.clone('quack')
772
self.make_bzrdir('quack')
773
client = FakeClient(transport.base)
774
reference_bzrdir_format = bzrdir.format_registry.get('default')()
775
reference_format = reference_bzrdir_format.repository_format
776
network_name = reference_format.network_name()
777
client.add_expected_call(
778
'BzrDir.create_repository', ('quack/',
779
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
781
'success', ('ok', 'yes', 'yes', 'yes', network_name))
782
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
784
repo = a_bzrdir.create_repository()
785
# We should have got a remote repository
786
self.assertIsInstance(repo, remote.RemoteRepository)
787
# its format should have the settings from the response
788
format = repo._format
789
self.assertTrue(format.rich_root_data)
790
self.assertTrue(format.supports_tree_reference)
791
self.assertTrue(format.supports_external_lookups)
792
self.assertEqual(network_name, format.network_name())
795
class TestBzrDirOpenRepository(TestRemote):
797
def test_backwards_compat_1_2_3(self):
798
# fallback all the way to the first version.
799
reference_format = self.get_repo_format()
800
network_name = reference_format.network_name()
801
server_url = 'bzr://example.com/'
802
self.permit_url(server_url)
803
client = FakeClient(server_url)
804
client.add_unknown_method_response('BzrDir.find_repositoryV3')
805
client.add_unknown_method_response('BzrDir.find_repositoryV2')
461
RemoteBzrDirFormat.probe_transport, OldServerTransport())
464
class TestBzrDirOpenRepository(tests.TestCase):
466
def test_backwards_compat_1_2(self):
467
transport = MemoryTransport()
468
transport.mkdir('quack')
469
transport = transport.clone('quack')
470
client = FakeClient(transport.base)
471
client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
806
472
client.add_success_response('ok', '', 'no', 'no')
807
# A real repository instance will be created to determine the network
809
client.add_success_response_with_body(
810
"Bazaar-NG meta directory, format 1\n", 'ok')
811
client.add_success_response_with_body(
812
reference_format.get_format_string(), 'ok')
813
# PackRepository wants to do a stat
814
client.add_success_response('stat', '0', '65535')
815
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
817
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
819
repo = bzrdir.open_repository()
821
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
822
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
823
('call', 'BzrDir.find_repository', ('quack/',)),
824
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
825
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
826
('call', 'stat', ('/quack/.bzr/repository',)),
829
self.assertEqual(network_name, repo._format.network_name())
831
def test_backwards_compat_2(self):
832
# fallback to find_repositoryV2
833
reference_format = self.get_repo_format()
834
network_name = reference_format.network_name()
835
server_url = 'bzr://example.com/'
836
self.permit_url(server_url)
837
client = FakeClient(server_url)
838
client.add_unknown_method_response('BzrDir.find_repositoryV3')
839
client.add_success_response('ok', '', 'no', 'no', 'no')
840
# A real repository instance will be created to determine the network
842
client.add_success_response_with_body(
843
"Bazaar-NG meta directory, format 1\n", 'ok')
844
client.add_success_response_with_body(
845
reference_format.get_format_string(), 'ok')
846
# PackRepository wants to do a stat
847
client.add_success_response('stat', '0', '65535')
848
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
850
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
852
repo = bzrdir.open_repository()
854
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
855
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
856
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
857
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
858
('call', 'stat', ('/quack/.bzr/repository',)),
861
self.assertEqual(network_name, repo._format.network_name())
863
def test_current_server(self):
864
reference_format = self.get_repo_format()
865
network_name = reference_format.network_name()
866
transport = MemoryTransport()
867
transport.mkdir('quack')
868
transport = transport.clone('quack')
869
client = FakeClient(transport.base)
870
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
871
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
873
repo = bzrdir.open_repository()
875
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
877
self.assertEqual(network_name, repo._format.network_name())
880
class TestBzrDirFormatInitializeEx(TestRemote):
882
def test_success(self):
883
"""Simple test for typical successful call."""
884
fmt = RemoteBzrDirFormat()
885
default_format_name = BzrDirFormat.get_default_format().network_name()
886
transport = self.get_transport()
887
client = FakeClient(transport.base)
888
client.add_expected_call(
889
'BzrDirFormat.initialize_ex_1.16',
890
(default_format_name, 'path', 'False', 'False', 'False', '',
891
'', '', '', 'False'),
893
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
894
'bzrdir fmt', 'False', '', '', 'repo lock token'))
895
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
896
# it's currently hard to test that without supplying a real remote
897
# transport connected to a real server.
898
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
899
transport, False, False, False, None, None, None, None, False)
900
self.assertFinished(client)
902
def test_error(self):
903
"""Error responses are translated, e.g. 'PermissionDenied' raises the
904
corresponding error from the client.
906
fmt = RemoteBzrDirFormat()
907
default_format_name = BzrDirFormat.get_default_format().network_name()
908
transport = self.get_transport()
909
client = FakeClient(transport.base)
910
client.add_expected_call(
911
'BzrDirFormat.initialize_ex_1.16',
912
(default_format_name, 'path', 'False', 'False', 'False', '',
913
'', '', '', 'False'),
915
('PermissionDenied', 'path', 'extra info'))
916
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
917
# it's currently hard to test that without supplying a real remote
918
# transport connected to a real server.
919
err = self.assertRaises(errors.PermissionDenied,
920
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
921
False, False, False, None, None, None, None, False)
922
self.assertEqual('path', err.path)
923
self.assertEqual(': extra info', err.extra)
924
self.assertFinished(client)
926
def test_error_from_real_server(self):
927
"""Integration test for error translation."""
928
transport = self.make_smart_server('foo')
929
transport = transport.clone('no-such-path')
930
fmt = RemoteBzrDirFormat()
931
err = self.assertRaises(errors.NoSuchFile,
932
fmt.initialize_on_transport_ex, transport, create_prefix=False)
473
bzrdir = RemoteBzrDir(transport, _client=client)
474
repo = bzrdir.open_repository()
476
[('call', 'BzrDir.find_repositoryV2', ('quack/',)),
477
('call', 'BzrDir.find_repository', ('quack/',))],
935
481
class OldSmartClient(object):
960
506
return OldSmartClient()
963
class RemoteBzrDirTestCase(TestRemote):
965
def make_remote_bzrdir(self, transport, client):
966
"""Make a RemotebzrDir using 'client' as the _client."""
967
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
971
class RemoteBranchTestCase(RemoteBzrDirTestCase):
973
def lock_remote_branch(self, branch):
974
"""Trick a RemoteBranch into thinking it is locked."""
975
branch._lock_mode = 'w'
976
branch._lock_count = 2
977
branch._lock_token = 'branch token'
978
branch._repo_lock_token = 'repo token'
979
branch.repository._lock_mode = 'w'
980
branch.repository._lock_count = 2
981
branch.repository._lock_token = 'repo token'
509
class RemoteBranchTestCase(tests.TestCase):
983
511
def make_remote_branch(self, transport, client):
984
512
"""Make a RemoteBranch using 'client' as its _SmartClient.
986
514
A RemoteBzrDir and RemoteRepository will also be created to fill out
987
515
the RemoteBranch, albeit with stub values for some of their attributes.
989
517
# we do not want bzrdir to make any remote calls, so use False as its
990
518
# _client. If it tries to make a remote call, this will fail
992
bzrdir = self.make_remote_bzrdir(transport, False)
520
bzrdir = RemoteBzrDir(transport, _client=False)
993
521
repo = RemoteRepository(bzrdir, None, _client=client)
994
branch_format = self.get_branch_format()
995
format = RemoteBranchFormat(network_name=branch_format.network_name())
996
return RemoteBranch(bzrdir, repo, _client=client, format=format)
999
class TestBranchGetParent(RemoteBranchTestCase):
1001
def test_no_parent(self):
1002
# in an empty branch we decode the response properly
1003
transport = MemoryTransport()
1004
client = FakeClient(transport.base)
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('quack/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.get_parent', ('quack/',),
1011
transport.mkdir('quack')
1012
transport = transport.clone('quack')
1013
branch = self.make_remote_branch(transport, client)
1014
result = branch.get_parent()
1015
self.assertFinished(client)
1016
self.assertEqual(None, result)
1018
def test_parent_relative(self):
1019
transport = MemoryTransport()
1020
client = FakeClient(transport.base)
1021
client.add_expected_call(
1022
'Branch.get_stacked_on_url', ('kwaak/',),
1023
'error', ('NotStacked',))
1024
client.add_expected_call(
1025
'Branch.get_parent', ('kwaak/',),
1026
'success', ('../foo/',))
1027
transport.mkdir('kwaak')
1028
transport = transport.clone('kwaak')
1029
branch = self.make_remote_branch(transport, client)
1030
result = branch.get_parent()
1031
self.assertEqual(transport.clone('../foo').base, result)
1033
def test_parent_absolute(self):
1034
transport = MemoryTransport()
1035
client = FakeClient(transport.base)
1036
client.add_expected_call(
1037
'Branch.get_stacked_on_url', ('kwaak/',),
1038
'error', ('NotStacked',))
1039
client.add_expected_call(
1040
'Branch.get_parent', ('kwaak/',),
1041
'success', ('http://foo/',))
1042
transport.mkdir('kwaak')
1043
transport = transport.clone('kwaak')
1044
branch = self.make_remote_branch(transport, client)
1045
result = branch.get_parent()
1046
self.assertEqual('http://foo/', result)
1047
self.assertFinished(client)
1050
class TestBranchSetParentLocation(RemoteBranchTestCase):
1052
def test_no_parent(self):
1053
# We call the verb when setting parent to None
1054
transport = MemoryTransport()
1055
client = FakeClient(transport.base)
1056
client.add_expected_call(
1057
'Branch.get_stacked_on_url', ('quack/',),
1058
'error', ('NotStacked',))
1059
client.add_expected_call(
1060
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1062
transport.mkdir('quack')
1063
transport = transport.clone('quack')
1064
branch = self.make_remote_branch(transport, client)
1065
branch._lock_token = 'b'
1066
branch._repo_lock_token = 'r'
1067
branch._set_parent_location(None)
1068
self.assertFinished(client)
1070
def test_parent(self):
1071
transport = MemoryTransport()
1072
client = FakeClient(transport.base)
1073
client.add_expected_call(
1074
'Branch.get_stacked_on_url', ('kwaak/',),
1075
'error', ('NotStacked',))
1076
client.add_expected_call(
1077
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1079
transport.mkdir('kwaak')
1080
transport = transport.clone('kwaak')
1081
branch = self.make_remote_branch(transport, client)
1082
branch._lock_token = 'b'
1083
branch._repo_lock_token = 'r'
1084
branch._set_parent_location('foo')
1085
self.assertFinished(client)
1087
def test_backwards_compat(self):
1088
self.setup_smart_server_with_call_log()
1089
branch = self.make_branch('.')
1090
self.reset_smart_call_log()
1091
verb = 'Branch.set_parent_location'
1092
self.disable_verb(verb)
1093
branch.set_parent('http://foo/')
1094
self.assertLength(12, self.hpss_calls)
1097
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1099
def test_backwards_compat(self):
1100
self.setup_smart_server_with_call_log()
1101
branch = self.make_branch('.')
1102
self.reset_smart_call_log()
1103
verb = 'Branch.get_tags_bytes'
1104
self.disable_verb(verb)
1105
branch.tags.get_tag_dict()
1106
call_count = len([call for call in self.hpss_calls if
1107
call.call.method == verb])
1108
self.assertEqual(1, call_count)
1110
def test_trivial(self):
1111
transport = MemoryTransport()
1112
client = FakeClient(transport.base)
1113
client.add_expected_call(
1114
'Branch.get_stacked_on_url', ('quack/',),
1115
'error', ('NotStacked',))
1116
client.add_expected_call(
1117
'Branch.get_tags_bytes', ('quack/',),
1119
transport.mkdir('quack')
1120
transport = transport.clone('quack')
1121
branch = self.make_remote_branch(transport, client)
1122
result = branch.tags.get_tag_dict()
1123
self.assertFinished(client)
1124
self.assertEqual({}, result)
1127
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1129
def test_trivial(self):
1130
transport = MemoryTransport()
1131
client = FakeClient(transport.base)
1132
client.add_expected_call(
1133
'Branch.get_stacked_on_url', ('quack/',),
1134
'error', ('NotStacked',))
1135
client.add_expected_call(
1136
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1138
transport.mkdir('quack')
1139
transport = transport.clone('quack')
1140
branch = self.make_remote_branch(transport, client)
1141
self.lock_remote_branch(branch)
1142
branch._set_tags_bytes('tags bytes')
1143
self.assertFinished(client)
1144
self.assertEqual('tags bytes', client._calls[-1][-1])
1146
def test_backwards_compatible(self):
1147
transport = MemoryTransport()
1148
client = FakeClient(transport.base)
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('quack/',),
1151
'error', ('NotStacked',))
1152
client.add_expected_call(
1153
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1154
'unknown', ('Branch.set_tags_bytes',))
1155
transport.mkdir('quack')
1156
transport = transport.clone('quack')
1157
branch = self.make_remote_branch(transport, client)
1158
self.lock_remote_branch(branch)
1159
class StubRealBranch(object):
1162
def _set_tags_bytes(self, bytes):
1163
self.calls.append(('set_tags_bytes', bytes))
1164
real_branch = StubRealBranch()
1165
branch._real_branch = real_branch
1166
branch._set_tags_bytes('tags bytes')
1167
# Call a second time, to exercise the 'remote version already inferred'
1169
branch._set_tags_bytes('tags bytes')
1170
self.assertFinished(client)
1172
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1175
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1177
def test_uses_last_revision_info_and_tags_by_default(self):
1178
transport = MemoryTransport()
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('quack/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.last_revision_info', ('quack/',),
1185
'success', ('ok', '1', 'rev-tip'))
1186
client.add_expected_call(
1187
'Branch.get_config_file', ('quack/',),
1188
'success', ('ok',), '')
1189
transport.mkdir('quack')
1190
transport = transport.clone('quack')
1191
branch = self.make_remote_branch(transport, client)
1192
result = branch.heads_to_fetch()
1193
self.assertFinished(client)
1194
self.assertEqual((set(['rev-tip']), set()), result)
1196
def test_uses_last_revision_info_and_tags_when_set(self):
1197
transport = MemoryTransport()
1198
client = FakeClient(transport.base)
1199
client.add_expected_call(
1200
'Branch.get_stacked_on_url', ('quack/',),
1201
'error', ('NotStacked',))
1202
client.add_expected_call(
1203
'Branch.last_revision_info', ('quack/',),
1204
'success', ('ok', '1', 'rev-tip'))
1205
client.add_expected_call(
1206
'Branch.get_config_file', ('quack/',),
1207
'success', ('ok',), 'branch.fetch_tags = True')
1208
# XXX: this will break if the default format's serialization of tags
1209
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1210
client.add_expected_call(
1211
'Branch.get_tags_bytes', ('quack/',),
1212
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1213
transport.mkdir('quack')
1214
transport = transport.clone('quack')
1215
branch = self.make_remote_branch(transport, client)
1216
result = branch.heads_to_fetch()
1217
self.assertFinished(client)
1219
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1221
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1222
transport = MemoryTransport()
1223
client = FakeClient(transport.base)
1224
client.add_expected_call(
1225
'Branch.get_stacked_on_url', ('quack/',),
1226
'error', ('NotStacked',))
1227
client.add_expected_call(
1228
'Branch.heads_to_fetch', ('quack/',),
1229
'success', (['tip'], ['tagged-1', 'tagged-2']))
1230
transport.mkdir('quack')
1231
transport = transport.clone('quack')
1232
branch = self.make_remote_branch(transport, client)
1233
branch._format._use_default_local_heads_to_fetch = lambda: False
1234
result = branch.heads_to_fetch()
1235
self.assertFinished(client)
1236
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1238
def make_branch_with_tags(self):
1239
self.setup_smart_server_with_call_log()
1240
# Make a branch with a single revision.
1241
builder = self.make_branch_builder('foo')
1242
builder.start_series()
1243
builder.build_snapshot('tip', None, [
1244
('add', ('', 'root-id', 'directory', ''))])
1245
builder.finish_series()
1246
branch = builder.get_branch()
1247
# Add two tags to that branch
1248
branch.tags.set_tag('tag-1', 'rev-1')
1249
branch.tags.set_tag('tag-2', 'rev-2')
1252
def test_backwards_compatible(self):
1253
branch = self.make_branch_with_tags()
1254
c = branch.get_config()
1255
c.set_user_option('branch.fetch_tags', 'True')
1256
self.addCleanup(branch.lock_read().unlock)
1257
# Disable the heads_to_fetch verb
1258
verb = 'Branch.heads_to_fetch'
1259
self.disable_verb(verb)
1260
self.reset_smart_call_log()
1261
result = branch.heads_to_fetch()
1262
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1264
['Branch.last_revision_info', 'Branch.get_config_file',
1265
'Branch.get_tags_bytes'],
1266
[call.call.method for call in self.hpss_calls])
1268
def test_backwards_compatible_no_tags(self):
1269
branch = self.make_branch_with_tags()
1270
c = branch.get_config()
1271
c.set_user_option('branch.fetch_tags', 'False')
1272
self.addCleanup(branch.lock_read().unlock)
1273
# Disable the heads_to_fetch verb
1274
verb = 'Branch.heads_to_fetch'
1275
self.disable_verb(verb)
1276
self.reset_smart_call_log()
1277
result = branch.heads_to_fetch()
1278
self.assertEqual((set(['tip']), set()), result)
1280
['Branch.last_revision_info', 'Branch.get_config_file'],
1281
[call.call.method for call in self.hpss_calls])
522
return RemoteBranch(bzrdir, repo, _client=client)
1284
525
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1736
931
self.assertEqual('rejection message', err.msg)
1739
class TestBranchGetSetConfig(RemoteBranchTestCase):
934
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
935
"""Getting the branch configuration should use an abstract method not vfs.
1741
938
def test_get_branch_conf(self):
1742
# in an empty branch we decode the response properly
1743
client = FakeClient()
1744
client.add_expected_call(
1745
'Branch.get_stacked_on_url', ('memory:///',),
1746
'error', ('NotStacked',),)
1747
client.add_success_response_with_body('# config file body', 'ok')
1748
transport = MemoryTransport()
1749
branch = self.make_remote_branch(transport, client)
1750
config = branch.get_config()
1751
config.has_explicit_nickname()
1753
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1754
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1757
def test_get_multi_line_branch_conf(self):
1758
# Make sure that multiple-line branch.conf files are supported
1760
# https://bugs.launchpad.net/bzr/+bug/354075
1761
client = FakeClient()
1762
client.add_expected_call(
1763
'Branch.get_stacked_on_url', ('memory:///',),
1764
'error', ('NotStacked',),)
1765
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1766
transport = MemoryTransport()
1767
branch = self.make_remote_branch(transport, client)
1768
config = branch.get_config()
1769
self.assertEqual(u'2', config.get_user_option('b'))
1771
def test_set_option(self):
1772
client = FakeClient()
1773
client.add_expected_call(
1774
'Branch.get_stacked_on_url', ('memory:///',),
1775
'error', ('NotStacked',),)
1776
client.add_expected_call(
1777
'Branch.lock_write', ('memory:///', '', ''),
1778
'success', ('ok', 'branch token', 'repo token'))
1779
client.add_expected_call(
1780
'Branch.set_config_option', ('memory:///', 'branch token',
1781
'repo token', 'foo', 'bar', ''),
1783
client.add_expected_call(
1784
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1786
transport = MemoryTransport()
1787
branch = self.make_remote_branch(transport, client)
1789
config = branch._get_config()
1790
config.set_option('foo', 'bar')
1792
self.assertFinished(client)
1794
def test_set_option_with_dict(self):
1795
client = FakeClient()
1796
client.add_expected_call(
1797
'Branch.get_stacked_on_url', ('memory:///',),
1798
'error', ('NotStacked',),)
1799
client.add_expected_call(
1800
'Branch.lock_write', ('memory:///', '', ''),
1801
'success', ('ok', 'branch token', 'repo token'))
1802
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1803
client.add_expected_call(
1804
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1805
'repo token', encoded_dict_value, 'foo', ''),
1807
client.add_expected_call(
1808
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1810
transport = MemoryTransport()
1811
branch = self.make_remote_branch(transport, client)
1813
config = branch._get_config()
1815
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1818
self.assertFinished(client)
1820
def test_backwards_compat_set_option(self):
1821
self.setup_smart_server_with_call_log()
1822
branch = self.make_branch('.')
1823
verb = 'Branch.set_config_option'
1824
self.disable_verb(verb)
1826
self.addCleanup(branch.unlock)
1827
self.reset_smart_call_log()
1828
branch._get_config().set_option('value', 'name')
1829
self.assertLength(10, self.hpss_calls)
1830
self.assertEqual('value', branch._get_config().get_option('name'))
1832
def test_backwards_compat_set_option_with_dict(self):
1833
self.setup_smart_server_with_call_log()
1834
branch = self.make_branch('.')
1835
verb = 'Branch.set_config_option_dict'
1836
self.disable_verb(verb)
1838
self.addCleanup(branch.unlock)
1839
self.reset_smart_call_log()
1840
config = branch._get_config()
1841
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1842
config.set_option(value_dict, 'name')
1843
self.assertLength(10, self.hpss_calls)
1844
self.assertEqual(value_dict, branch._get_config().get_option('name'))
939
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
940
## # We should see that branch.get_config() does a single rpc to get the
941
## # remote configuration file, abstracting away where that is stored on
942
## # the server. However at the moment it always falls back to using the
943
## # vfs, and this would need some changes in config.py.
945
## # in an empty branch we decode the response properly
946
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
947
## # we need to make a real branch because the remote_branch.control_files
948
## # will trigger _ensure_real.
949
## branch = self.make_branch('quack')
950
## transport = branch.bzrdir.root_transport
951
## # we do not want bzrdir to make any remote calls
952
## bzrdir = RemoteBzrDir(transport, _client=False)
953
## branch = RemoteBranch(bzrdir, None, _client=client)
954
## config = branch.get_config()
956
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1847
960
class TestBranchLockWrite(RemoteBranchTestCase):
2187
1208
errors.UnexpectedSmartServerResponse,
2188
1209
repo.get_parent_map, ['a-revision-id'])
2190
def test_get_parent_map_negative_caches_missing_keys(self):
2191
self.setup_smart_server_with_call_log()
2192
repo = self.make_repository('foo')
2193
self.assertIsInstance(repo, RemoteRepository)
2195
self.addCleanup(repo.unlock)
2196
self.reset_smart_call_log()
2197
graph = repo.get_graph()
2198
self.assertEqual({},
2199
graph.get_parent_map(['some-missing', 'other-missing']))
2200
self.assertLength(1, self.hpss_calls)
2201
# No call if we repeat this
2202
self.reset_smart_call_log()
2203
graph = repo.get_graph()
2204
self.assertEqual({},
2205
graph.get_parent_map(['some-missing', 'other-missing']))
2206
self.assertLength(0, self.hpss_calls)
2207
# Asking for more unknown keys makes a request.
2208
self.reset_smart_call_log()
2209
graph = repo.get_graph()
2210
self.assertEqual({},
2211
graph.get_parent_map(['some-missing', 'other-missing',
2213
self.assertLength(1, self.hpss_calls)
2215
def disableExtraResults(self):
2216
self.overrideAttr(SmartServerRepositoryGetParentMap,
2217
'no_extra_results', True)
2219
def test_null_cached_missing_and_stop_key(self):
2220
self.setup_smart_server_with_call_log()
2221
# Make a branch with a single revision.
2222
builder = self.make_branch_builder('foo')
2223
builder.start_series()
2224
builder.build_snapshot('first', None, [
2225
('add', ('', 'root-id', 'directory', ''))])
2226
builder.finish_series()
2227
branch = builder.get_branch()
2228
repo = branch.repository
2229
self.assertIsInstance(repo, RemoteRepository)
2230
# Stop the server from sending extra results.
2231
self.disableExtraResults()
2233
self.addCleanup(repo.unlock)
2234
self.reset_smart_call_log()
2235
graph = repo.get_graph()
2236
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2237
# 'first' it will be a candidate for the stop_keys of subsequent
2238
# requests, and because 'null:' was queried but not returned it will be
2239
# cached as missing.
2240
self.assertEqual({'first': ('null:',)},
2241
graph.get_parent_map(['first', 'null:']))
2242
# Now query for another key. This request will pass along a recipe of
2243
# start and stop keys describing the already cached results, and this
2244
# recipe's revision count must be correct (or else it will trigger an
2245
# error from the server).
2246
self.assertEqual({}, graph.get_parent_map(['another-key']))
2247
# This assertion guards against disableExtraResults silently failing to
2248
# work, thus invalidating the test.
2249
self.assertLength(2, self.hpss_calls)
2251
def test_get_parent_map_gets_ghosts_from_result(self):
2252
# asking for a revision should negatively cache close ghosts in its
2254
self.setup_smart_server_with_call_log()
2255
tree = self.make_branch_and_memory_tree('foo')
2258
builder = treebuilder.TreeBuilder()
2259
builder.start_tree(tree)
2261
builder.finish_tree()
2262
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2263
rev_id = tree.commit('')
2267
self.addCleanup(tree.unlock)
2268
repo = tree.branch.repository
2269
self.assertIsInstance(repo, RemoteRepository)
2271
repo.get_parent_map([rev_id])
2272
self.reset_smart_call_log()
2273
# Now asking for rev_id's ghost parent should not make calls
2274
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2275
self.assertLength(0, self.hpss_calls)
2278
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2280
def test_allows_new_revisions(self):
2281
"""get_parent_map's results can be updated by commit."""
2282
smart_server = test_server.SmartTCPServer_for_testing()
2283
self.start_server(smart_server)
2284
self.make_branch('branch')
2285
branch = Branch.open(smart_server.get_url() + '/branch')
2286
tree = branch.create_checkout('tree', lightweight=True)
2288
self.addCleanup(tree.unlock)
2289
graph = tree.branch.repository.get_graph()
2290
# This provides an opportunity for the missing rev-id to be cached.
2291
self.assertEqual({}, graph.get_parent_map(['rev1']))
2292
tree.commit('message', rev_id='rev1')
2293
graph = tree.branch.repository.get_graph()
2294
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2297
1212
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2299
1214
def test_null_revision(self):
2300
1215
# a null revision has the predictable result {}, we should have no wire
2301
1216
# traffic when calling it with this argument
2302
1217
transport_path = 'empty'
2303
1218
repo, client = self.setup_fake_client_and_repository(transport_path)
2304
1219
client.add_success_response('notused')
2305
# actual RemoteRepository.get_revision_graph is gone, but there's an
2306
# equivalent private method for testing
2307
result = repo._get_revision_graph(NULL_REVISION)
1220
result = self.applyDeprecated(one_four, repo.get_revision_graph,
2308
1222
self.assertEqual([], client._calls)
2309
1223
self.assertEqual({}, result)
2365
1277
repo, client = self.setup_fake_client_and_repository(transport_path)
2366
1278
client.add_error_response('AnUnexpectedError')
2367
1279
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2368
repo._get_revision_graph, revid)
1280
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
2369
1281
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2372
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2375
repo, client = self.setup_fake_client_and_repository('quack')
2376
client.add_expected_call(
2377
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2378
'success', ('ok', 'rev-five'))
2379
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2380
self.assertEqual((True, 'rev-five'), result)
2381
self.assertFinished(client)
2383
def test_history_incomplete(self):
2384
repo, client = self.setup_fake_client_and_repository('quack')
2385
client.add_expected_call(
2386
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2387
'success', ('history-incomplete', 10, 'rev-ten'))
2388
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2389
self.assertEqual((False, (10, 'rev-ten')), result)
2390
self.assertFinished(client)
2392
def test_history_incomplete_with_fallback(self):
2393
"""A 'history-incomplete' response causes the fallback repository to be
2394
queried too, if one is set.
2396
# Make a repo with a fallback repo, both using a FakeClient.
2397
format = remote.response_tuple_to_repo_format(
2398
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2399
repo, client = self.setup_fake_client_and_repository('quack')
2400
repo._format = format
2401
fallback_repo, ignored = self.setup_fake_client_and_repository(
2403
fallback_repo._client = client
2404
fallback_repo._format = format
2405
repo.add_fallback_repository(fallback_repo)
2406
# First the client should ask the primary repo
2407
client.add_expected_call(
2408
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2409
'success', ('history-incomplete', 2, 'rev-two'))
2410
# Then it should ask the fallback, using revno/revid from the
2411
# history-incomplete response as the known revno/revid.
2412
client.add_expected_call(
2413
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2414
'success', ('ok', 'rev-one'))
2415
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2416
self.assertEqual((True, 'rev-one'), result)
2417
self.assertFinished(client)
2419
def test_nosuchrevision(self):
2420
# 'nosuchrevision' is returned when the known-revid is not found in the
2421
# remote repo. The client translates that response to NoSuchRevision.
2422
repo, client = self.setup_fake_client_and_repository('quack')
2423
client.add_expected_call(
2424
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2425
'error', ('nosuchrevision', 'rev-foo'))
2427
errors.NoSuchRevision,
2428
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2429
self.assertFinished(client)
2431
def test_branch_fallback_locking(self):
2432
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2433
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2434
will be invoked, which will fail if the repo is unlocked.
2436
self.setup_smart_server_with_call_log()
2437
tree = self.make_branch_and_memory_tree('.')
2440
rev1 = tree.commit('First')
2441
rev2 = tree.commit('Second')
2443
branch = tree.branch
2444
self.assertFalse(branch.is_locked())
2445
self.reset_smart_call_log()
2446
verb = 'Repository.get_rev_id_for_revno'
2447
self.disable_verb(verb)
2448
self.assertEqual(rev1, branch.get_rev_id(1))
2449
self.assertLength(1, [call for call in self.hpss_calls if
2450
call.call.method == verb])
2453
1284
class TestRepositoryIsShared(TestRemoteRepository):
2455
1286
def test_is_shared(self):
2570
1375
self.assertEqual([], client._calls)
2573
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2574
"""Base class for Repository.insert_stream and .insert_stream_1.19
2578
def checkInsertEmptyStream(self, repo, client):
2579
"""Insert an empty stream, checking the result.
2581
This checks that there are no resume_tokens or missing_keys, and that
2582
the client is finished.
2584
sink = repo._get_sink()
2585
fmt = repository.format_registry.get_default()
2586
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2587
self.assertEqual([], resume_tokens)
2588
self.assertEqual(set(), missing_keys)
2589
self.assertFinished(client)
2592
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2593
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2596
This test case is very similar to TestRepositoryInsertStream_1_19.
2600
TestRemoteRepository.setUp(self)
2601
self.disable_verb('Repository.insert_stream_1.19')
2603
def test_unlocked_repo(self):
2604
transport_path = 'quack'
2605
repo, client = self.setup_fake_client_and_repository(transport_path)
2606
client.add_expected_call(
2607
'Repository.insert_stream_1.19', ('quack/', ''),
2608
'unknown', ('Repository.insert_stream_1.19',))
2609
client.add_expected_call(
2610
'Repository.insert_stream', ('quack/', ''),
2612
client.add_expected_call(
2613
'Repository.insert_stream', ('quack/', ''),
2615
self.checkInsertEmptyStream(repo, client)
2617
def test_locked_repo_with_no_lock_token(self):
2618
transport_path = 'quack'
2619
repo, client = self.setup_fake_client_and_repository(transport_path)
2620
client.add_expected_call(
2621
'Repository.lock_write', ('quack/', ''),
2622
'success', ('ok', ''))
2623
client.add_expected_call(
2624
'Repository.insert_stream_1.19', ('quack/', ''),
2625
'unknown', ('Repository.insert_stream_1.19',))
2626
client.add_expected_call(
2627
'Repository.insert_stream', ('quack/', ''),
2629
client.add_expected_call(
2630
'Repository.insert_stream', ('quack/', ''),
2633
self.checkInsertEmptyStream(repo, client)
2635
def test_locked_repo_with_lock_token(self):
2636
transport_path = 'quack'
2637
repo, client = self.setup_fake_client_and_repository(transport_path)
2638
client.add_expected_call(
2639
'Repository.lock_write', ('quack/', ''),
2640
'success', ('ok', 'a token'))
2641
client.add_expected_call(
2642
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2643
'unknown', ('Repository.insert_stream_1.19',))
2644
client.add_expected_call(
2645
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2647
client.add_expected_call(
2648
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2651
self.checkInsertEmptyStream(repo, client)
2653
def test_stream_with_inventory_deltas(self):
2654
"""'inventory-deltas' substreams cannot be sent to the
2655
Repository.insert_stream verb, because not all servers that implement
2656
that verb will accept them. So when one is encountered the RemoteSink
2657
immediately stops using that verb and falls back to VFS insert_stream.
2659
transport_path = 'quack'
2660
repo, client = self.setup_fake_client_and_repository(transport_path)
2661
client.add_expected_call(
2662
'Repository.insert_stream_1.19', ('quack/', ''),
2663
'unknown', ('Repository.insert_stream_1.19',))
2664
client.add_expected_call(
2665
'Repository.insert_stream', ('quack/', ''),
2667
client.add_expected_call(
2668
'Repository.insert_stream', ('quack/', ''),
2670
# Create a fake real repository for insert_stream to fall back on, so
2671
# that we can directly see the records the RemoteSink passes to the
2676
def insert_stream(self, stream, src_format, resume_tokens):
2677
for substream_kind, substream in stream:
2678
self.records.append(
2679
(substream_kind, [record.key for record in substream]))
2680
return ['fake tokens'], ['fake missing keys']
2681
fake_real_sink = FakeRealSink()
2682
class FakeRealRepository:
2683
def _get_sink(self):
2684
return fake_real_sink
2685
def is_in_write_group(self):
2687
def refresh_data(self):
2689
repo._real_repository = FakeRealRepository()
2690
sink = repo._get_sink()
2691
fmt = repository.format_registry.get_default()
2692
stream = self.make_stream_with_inv_deltas(fmt)
2693
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2694
# Every record from the first inventory delta should have been sent to
2696
expected_records = [
2697
('inventory-deltas', [('rev2',), ('rev3',)]),
2698
('texts', [('some-rev', 'some-file')])]
2699
self.assertEqual(expected_records, fake_real_sink.records)
2700
# The return values from the real sink's insert_stream are propagated
2701
# back to the original caller.
2702
self.assertEqual(['fake tokens'], resume_tokens)
2703
self.assertEqual(['fake missing keys'], missing_keys)
2704
self.assertFinished(client)
2706
def make_stream_with_inv_deltas(self, fmt):
2707
"""Make a simple stream with an inventory delta followed by more
2708
records and more substreams to test that all records and substreams
2709
from that point on are used.
2711
This sends, in order:
2712
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2714
* texts substream: (some-rev, some-file)
2716
# Define a stream using generators so that it isn't rewindable.
2717
inv = inventory.Inventory(revision_id='rev1')
2718
inv.root.revision = 'rev1'
2719
def stream_with_inv_delta():
2720
yield ('inventories', inventories_substream())
2721
yield ('inventory-deltas', inventory_delta_substream())
2723
versionedfile.FulltextContentFactory(
2724
('some-rev', 'some-file'), (), None, 'content')])
2725
def inventories_substream():
2726
# An empty inventory fulltext. This will be streamed normally.
2727
text = fmt._serializer.write_inventory_to_string(inv)
2728
yield versionedfile.FulltextContentFactory(
2729
('rev1',), (), None, text)
2730
def inventory_delta_substream():
2731
# An inventory delta. This can't be streamed via this verb, so it
2732
# will trigger a fallback to VFS insert_stream.
2733
entry = inv.make_entry(
2734
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2735
entry.revision = 'ghost'
2736
delta = [(None, 'newdir', 'newdir-id', entry)]
2737
serializer = inventory_delta.InventoryDeltaSerializer(
2738
versioned_root=True, tree_references=False)
2739
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2740
yield versionedfile.ChunkedContentFactory(
2741
('rev2',), (('rev1',)), None, lines)
2743
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2744
yield versionedfile.ChunkedContentFactory(
2745
('rev3',), (('rev1',)), None, lines)
2746
return stream_with_inv_delta()
2749
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2751
def test_unlocked_repo(self):
2752
transport_path = 'quack'
2753
repo, client = self.setup_fake_client_and_repository(transport_path)
2754
client.add_expected_call(
2755
'Repository.insert_stream_1.19', ('quack/', ''),
2757
client.add_expected_call(
2758
'Repository.insert_stream_1.19', ('quack/', ''),
2760
self.checkInsertEmptyStream(repo, client)
2762
def test_locked_repo_with_no_lock_token(self):
2763
transport_path = 'quack'
2764
repo, client = self.setup_fake_client_and_repository(transport_path)
2765
client.add_expected_call(
2766
'Repository.lock_write', ('quack/', ''),
2767
'success', ('ok', ''))
2768
client.add_expected_call(
2769
'Repository.insert_stream_1.19', ('quack/', ''),
2771
client.add_expected_call(
2772
'Repository.insert_stream_1.19', ('quack/', ''),
2775
self.checkInsertEmptyStream(repo, client)
2777
def test_locked_repo_with_lock_token(self):
2778
transport_path = 'quack'
2779
repo, client = self.setup_fake_client_and_repository(transport_path)
2780
client.add_expected_call(
2781
'Repository.lock_write', ('quack/', ''),
2782
'success', ('ok', 'a token'))
2783
client.add_expected_call(
2784
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2786
client.add_expected_call(
2787
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2790
self.checkInsertEmptyStream(repo, client)
2793
1378
class TestRepositoryTarball(TestRemoteRepository):
2795
1380
# This is a canned tarball reponse we can validate against
2844
1429
src_repo.copy_content_into(dest_repo)
2847
class _StubRealPackRepository(object):
2849
def __init__(self, calls):
2851
self._pack_collection = _StubPackCollection(calls)
2853
def is_in_write_group(self):
2856
def refresh_data(self):
2857
self.calls.append(('pack collection reload_pack_names',))
2860
class _StubPackCollection(object):
2862
def __init__(self, calls):
2866
self.calls.append(('pack collection autopack',))
2869
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2870
"""Tests for RemoteRepository.autopack implementation."""
2873
"""When the server returns 'ok' and there's no _real_repository, then
2874
nothing else happens: the autopack method is done.
2876
transport_path = 'quack'
2877
repo, client = self.setup_fake_client_and_repository(transport_path)
2878
client.add_expected_call(
2879
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2881
self.assertFinished(client)
2883
def test_ok_with_real_repo(self):
2884
"""When the server returns 'ok' and there is a _real_repository, then
2885
the _real_repository's reload_pack_name's method will be called.
2887
transport_path = 'quack'
2888
repo, client = self.setup_fake_client_and_repository(transport_path)
2889
client.add_expected_call(
2890
'PackRepository.autopack', ('quack/',),
2892
repo._real_repository = _StubRealPackRepository(client._calls)
2895
[('call', 'PackRepository.autopack', ('quack/',)),
2896
('pack collection reload_pack_names',)],
2899
def test_backwards_compatibility(self):
2900
"""If the server does not recognise the PackRepository.autopack verb,
2901
fallback to the real_repository's implementation.
2903
transport_path = 'quack'
2904
repo, client = self.setup_fake_client_and_repository(transport_path)
2905
client.add_unknown_method_response('PackRepository.autopack')
2906
def stub_ensure_real():
2907
client._calls.append(('_ensure_real',))
2908
repo._real_repository = _StubRealPackRepository(client._calls)
2909
repo._ensure_real = stub_ensure_real
2912
[('call', 'PackRepository.autopack', ('quack/',)),
2914
('pack collection autopack',)],
2917
def test_oom_error_reporting(self):
2918
"""An out-of-memory condition on the server is reported clearly"""
2919
transport_path = 'quack'
2920
repo, client = self.setup_fake_client_and_repository(transport_path)
2921
client.add_expected_call(
2922
'PackRepository.autopack', ('quack/',),
2923
'error', ('MemoryError',))
2924
err = self.assertRaises(errors.BzrError, repo.autopack)
2925
self.assertContainsRe(str(err), "^remote server out of mem")
2928
1432
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2929
1433
"""Base class for unit tests for bzrlib.remote._translate_error."""
3042
1530
expected_error = errors.DivergedBranches(branch, other_branch)
3043
1531
self.assertEqual(expected_error, translated_error)
3045
def test_NotStacked(self):
3046
branch = self.make_branch('')
3047
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3048
expected_error = errors.NotStacked(branch)
3049
self.assertEqual(expected_error, translated_error)
3051
def test_ReadError_no_args(self):
3053
translated_error = self.translateTuple(('ReadError',), path=path)
3054
expected_error = errors.ReadError(path)
3055
self.assertEqual(expected_error, translated_error)
3057
def test_ReadError(self):
3059
translated_error = self.translateTuple(('ReadError', path))
3060
expected_error = errors.ReadError(path)
3061
self.assertEqual(expected_error, translated_error)
3063
def test_IncompatibleRepositories(self):
3064
translated_error = self.translateTuple(('IncompatibleRepositories',
3065
"repo1", "repo2", "details here"))
3066
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3068
self.assertEqual(expected_error, translated_error)
3070
def test_PermissionDenied_no_args(self):
3072
translated_error = self.translateTuple(('PermissionDenied',),
3074
expected_error = errors.PermissionDenied(path)
3075
self.assertEqual(expected_error, translated_error)
3077
def test_PermissionDenied_one_arg(self):
3079
translated_error = self.translateTuple(('PermissionDenied', path))
3080
expected_error = errors.PermissionDenied(path)
3081
self.assertEqual(expected_error, translated_error)
3083
def test_PermissionDenied_one_arg_and_context(self):
3084
"""Given a choice between a path from the local context and a path on
3085
the wire, _translate_error prefers the path from the local context.
3087
local_path = 'local path'
3088
remote_path = 'remote path'
3089
translated_error = self.translateTuple(
3090
('PermissionDenied', remote_path), path=local_path)
3091
expected_error = errors.PermissionDenied(local_path)
3092
self.assertEqual(expected_error, translated_error)
3094
def test_PermissionDenied_two_args(self):
3096
extra = 'a string with extra info'
3097
translated_error = self.translateTuple(
3098
('PermissionDenied', path, extra))
3099
expected_error = errors.PermissionDenied(path, extra)
3100
self.assertEqual(expected_error, translated_error)
3102
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3104
def test_NoSuchFile_context_path(self):
3105
local_path = "local path"
3106
translated_error = self.translateTuple(('ReadError', "remote path"),
3108
expected_error = errors.ReadError(local_path)
3109
self.assertEqual(expected_error, translated_error)
3111
def test_NoSuchFile_without_context(self):
3112
remote_path = "remote path"
3113
translated_error = self.translateTuple(('ReadError', remote_path))
3114
expected_error = errors.ReadError(remote_path)
3115
self.assertEqual(expected_error, translated_error)
3117
def test_ReadOnlyError(self):
3118
translated_error = self.translateTuple(('ReadOnlyError',))
3119
expected_error = errors.TransportNotPossible("readonly transport")
3120
self.assertEqual(expected_error, translated_error)
3122
def test_MemoryError(self):
3123
translated_error = self.translateTuple(('MemoryError',))
3124
self.assertStartsWith(str(translated_error),
3125
"remote server out of memory")
3127
def test_generic_IndexError_no_classname(self):
3128
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3129
translated_error = self.translateErrorFromSmartServer(err)
3130
expected_error = errors.UnknownErrorFromSmartServer(err)
3131
self.assertEqual(expected_error, translated_error)
3133
# GZ 2011-03-02: TODO test generic non-ascii error string
3135
def test_generic_KeyError(self):
3136
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3137
translated_error = self.translateErrorFromSmartServer(err)
3138
expected_error = errors.UnknownErrorFromSmartServer(err)
3139
self.assertEqual(expected_error, translated_error)
3142
1534
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3143
1535
"""Unit tests for bzrlib.remote._translate_error's robustness.
3145
1537
TestErrorTranslationSuccess is for cases where _translate_error can
3146
1538
translate successfully. This class about how _translate_err behaves when
3147
1539
it fails to translate: it re-raises the original error.
3234
1614
remote_repo.unlock()
3236
def prepare_stacked_remote_branch(self):
3237
"""Get stacked_upon and stacked branches with content in each."""
3238
self.setup_smart_server_with_call_log()
3239
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3240
tree1.commit('rev1', rev_id='rev1')
3241
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3242
).open_workingtree()
3243
local_tree = tree2.branch.create_checkout('local')
3244
local_tree.commit('local changes make me feel good.')
3245
branch2 = Branch.open(self.get_url('tree2'))
3247
self.addCleanup(branch2.unlock)
3248
return tree1.branch, branch2
3250
def test_stacked_get_parent_map(self):
3251
# the public implementation of get_parent_map obeys stacking
3252
_, branch = self.prepare_stacked_remote_branch()
3253
repo = branch.repository
3254
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3256
def test_unstacked_get_parent_map(self):
3257
# _unstacked_provider.get_parent_map ignores stacking
3258
_, branch = self.prepare_stacked_remote_branch()
3259
provider = branch.repository._unstacked_provider
3260
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3262
def fetch_stream_to_rev_order(self, stream):
3264
for kind, substream in stream:
3265
if not kind == 'revisions':
3268
for content in substream:
3269
result.append(content.key[-1])
3272
def get_ordered_revs(self, format, order, branch_factory=None):
3273
"""Get a list of the revisions in a stream to format format.
3275
:param format: The format of the target.
3276
:param order: the order that target should have requested.
3277
:param branch_factory: A callable to create a trunk and stacked branch
3278
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3279
:result: The revision ids in the stream, in the order seen,
3280
the topological order of revisions in the source.
3282
unordered_format = bzrdir.format_registry.get(format)()
3283
target_repository_format = unordered_format.repository_format
3285
self.assertEqual(order, target_repository_format._fetch_order)
3286
if branch_factory is None:
3287
branch_factory = self.prepare_stacked_remote_branch
3288
_, stacked = branch_factory()
3289
source = stacked.repository._get_source(target_repository_format)
3290
tip = stacked.last_revision()
3291
stacked.repository._ensure_real()
3292
graph = stacked.repository.get_graph()
3293
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3294
if r != NULL_REVISION]
3296
search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
3297
self.reset_smart_call_log()
3298
stream = source.get_stream(search)
3299
# We trust that if a revision is in the stream the rest of the new
3300
# content for it is too, as per our main fetch tests; here we are
3301
# checking that the revisions are actually included at all, and their
3303
return self.fetch_stream_to_rev_order(stream), revs
3305
def test_stacked_get_stream_unordered(self):
3306
# Repository._get_source.get_stream() from a stacked repository with
3307
# unordered yields the full data from both stacked and stacked upon
3309
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3310
self.assertEqual(set(expected_revs), set(rev_ord))
3311
# Getting unordered results should have made a streaming data request
3312
# from the server, then one from the backing branch.
3313
self.assertLength(2, self.hpss_calls)
3315
def test_stacked_on_stacked_get_stream_unordered(self):
3316
# Repository._get_source.get_stream() from a stacked repository which
3317
# is itself stacked yields the full data from all three sources.
3318
def make_stacked_stacked():
3319
_, stacked = self.prepare_stacked_remote_branch()
3320
tree = stacked.bzrdir.sprout('tree3', stacked=True
3321
).open_workingtree()
3322
local_tree = tree.branch.create_checkout('local-tree3')
3323
local_tree.commit('more local changes are better')
3324
branch = Branch.open(self.get_url('tree3'))
3326
self.addCleanup(branch.unlock)
3328
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3329
branch_factory=make_stacked_stacked)
3330
self.assertEqual(set(expected_revs), set(rev_ord))
3331
# Getting unordered results should have made a streaming data request
3332
# from the server, and one from each backing repo
3333
self.assertLength(3, self.hpss_calls)
3335
def test_stacked_get_stream_topological(self):
3336
# Repository._get_source.get_stream() from a stacked repository with
3337
# topological sorting yields the full data from both stacked and
3338
# stacked upon sources in topological order.
3339
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3340
self.assertEqual(expected_revs, rev_ord)
3341
# Getting topological sort requires VFS calls still - one of which is
3342
# pushing up from the bound branch.
3343
self.assertLength(14, self.hpss_calls)
3345
def test_stacked_get_stream_groupcompress(self):
3346
# Repository._get_source.get_stream() from a stacked repository with
3347
# groupcompress sorting yields the full data from both stacked and
3348
# stacked upon sources in groupcompress order.
3349
raise tests.TestSkipped('No groupcompress ordered format available')
3350
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3351
self.assertEqual(expected_revs, reversed(rev_ord))
3352
# Getting unordered results should have made a streaming data request
3353
# from the backing branch, and one from the stacked on branch.
3354
self.assertLength(2, self.hpss_calls)
3356
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3357
# When pulling some fixed amount of content that is more than the
3358
# source has (because some is coming from a fallback branch, no error
3359
# should be received. This was reported as bug 360791.
3360
# Need three branches: a trunk, a stacked branch, and a preexisting
3361
# branch pulling content from stacked and trunk.
3362
self.setup_smart_server_with_call_log()
3363
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3364
r1 = trunk.commit('start')
3365
stacked_branch = trunk.branch.create_clone_on_transport(
3366
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3367
local = self.make_branch('local', format='1.9-rich-root')
3368
local.repository.fetch(stacked_branch.repository,
3369
stacked_branch.last_revision())
3372
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3375
super(TestRemoteBranchEffort, self).setUp()
3376
# Create a smart server that publishes whatever the backing VFS server
3378
self.smart_server = test_server.SmartTCPServer_for_testing()
3379
self.start_server(self.smart_server, self.get_server())
3380
# Log all HPSS calls into self.hpss_calls.
3381
_SmartClient.hooks.install_named_hook(
3382
'call', self.capture_hpss_call, None)
3383
self.hpss_calls = []
3385
def capture_hpss_call(self, params):
3386
self.hpss_calls.append(params.method)
3388
def test_copy_content_into_avoids_revision_history(self):
3389
local = self.make_branch('local')
3390
builder = self.make_branch_builder('remote')
3391
builder.build_commit(message="Commit.")
3392
remote_branch_url = self.smart_server.get_url() + 'remote'
3393
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3394
local.repository.fetch(remote_branch.repository)
3395
self.hpss_calls = []
3396
remote_branch.copy_content_into(local)
3397
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3399
def test_fetch_everything_needs_just_one_call(self):
3400
local = self.make_branch('local')
3401
builder = self.make_branch_builder('remote')
3402
builder.build_commit(message="Commit.")
3403
remote_branch_url = self.smart_server.get_url() + 'remote'
3404
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3405
self.hpss_calls = []
3406
local.repository.fetch(
3407
remote_branch.repository,
3408
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3409
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3411
def override_verb(self, verb_name, verb):
3412
request_handlers = request.request_handlers
3413
orig_verb = request_handlers.get(verb_name)
3414
request_handlers.register(verb_name, verb, override_existing=True)
3415
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3416
override_existing=True)
3418
def test_fetch_everything_backwards_compat(self):
3419
"""Can fetch with EverythingResult even with pre 2.4 servers.
3421
Pre-2.4 do not support 'everything' searches with the
3422
Repository.get_stream_1.19 verb.
3425
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3426
"""A version of the Repository.get_stream_1.19 verb patched to
3427
reject 'everything' searches the way 2.3 and earlier do.
3429
def recreate_search(self, repository, search_bytes,
3430
discard_excess=False):
3431
verb_log.append(search_bytes.split('\n', 1)[0])
3432
if search_bytes == 'everything':
3434
request.FailedSmartServerResponse(('BadSearch',)))
3435
return super(OldGetStreamVerb,
3436
self).recreate_search(repository, search_bytes,
3437
discard_excess=discard_excess)
3438
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3439
local = self.make_branch('local')
3440
builder = self.make_branch_builder('remote')
3441
builder.build_commit(message="Commit.")
3442
remote_branch_url = self.smart_server.get_url() + 'remote'
3443
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3444
self.hpss_calls = []
3445
local.repository.fetch(
3446
remote_branch.repository,
3447
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3448
# make sure the overridden verb was used
3449
self.assertLength(1, verb_log)
3450
# more than one HPSS call is needed, but because it's a VFS callback
3451
# its hard to predict exactly how many.
3452
self.assertTrue(len(self.hpss_calls) > 1)
3455
class TestUpdateBoundBranchWithModifiedBoundLocation(
3456
tests.TestCaseWithTransport):
3457
"""Ensure correct handling of bound_location modifications.
3459
This is tested against a smart server as http://pad.lv/786980 was about a
3460
ReadOnlyError (write attempt during a read-only transaction) which can only
3461
happen in this context.
3465
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
3466
self.transport_server = test_server.SmartTCPServer_for_testing
3468
def make_master_and_checkout(self, master_name, checkout_name):
3469
# Create the master branch and its associated checkout
3470
self.master = self.make_branch_and_tree(master_name)
3471
self.checkout = self.master.branch.create_checkout(checkout_name)
3472
# Modify the master branch so there is something to update
3473
self.master.commit('add stuff')
3474
self.last_revid = self.master.commit('even more stuff')
3475
self.bound_location = self.checkout.branch.get_bound_location()
3477
def assertUpdateSucceeds(self, new_location):
3478
self.checkout.branch.set_bound_location(new_location)
3479
self.checkout.update()
3480
self.assertEquals(self.last_revid, self.checkout.last_revision())
3482
def test_without_final_slash(self):
3483
self.make_master_and_checkout('master', 'checkout')
3484
# For unclear reasons some users have a bound_location without a final
3485
# '/', simulate that by forcing such a value
3486
self.assertEndsWith(self.bound_location, '/')
3487
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
3489
def test_plus_sign(self):
3490
self.make_master_and_checkout('+master', 'checkout')
3491
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
3493
def test_tilda(self):
3494
# Embed ~ in the middle of the path just to avoid any $HOME
3496
self.make_master_and_checkout('mas~ter', 'checkout')
3497
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))