17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
21
20
from cStringIO import StringIO
23
22
from bzrlib import (
33
from bzrlib.branch import BranchReferenceFormat
28
from bzrlib.branch import Branch, BranchReferenceFormat
34
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
30
from bzrlib.config import BranchConfig, TreeConfig
36
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import (
32
from bzrlib.errors import NoSuchRevision
41
33
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.pack import ContainerPushParser
34
from bzrlib.revision import NULL_REVISION
43
35
from bzrlib.smart import client, vfs
44
36
from bzrlib.symbol_versioning import (
48
from bzrlib.revision import ensure_null, NULL_REVISION
49
from bzrlib.trace import mutter, note, warning
40
from bzrlib.trace import note
51
42
# Note: RemoteBzrDirFormat is in bzrdir.py
156
135
def open_repository(self):
157
136
path = self._path_for_remote_call(self._client)
158
verb = 'BzrDir.find_repositoryV2'
161
response = self._client.call(verb, path)
162
except errors.UnknownSmartMethod:
163
verb = 'BzrDir.find_repository'
164
response = self._client.call(verb, path)
165
except errors.ErrorFromSmartServer, err:
166
if err.error_verb == 'norepository':
167
raise errors.NoRepositoryPresent(self)
169
if response[0] != 'ok':
170
raise errors.UnexpectedSmartServerResponse(response)
171
if verb == 'BzrDir.find_repository':
172
# servers that don't support the V2 method don't support external
174
response = response + ('no', )
175
if not (len(response) == 5):
176
raise SmartProtocolError('incorrect response length %s' % (response,))
137
response = self._client.call('BzrDir.find_repository', path)
138
assert response[0] in ('ok', 'norepository'), \
139
'unexpected response code %s' % (response,)
140
if response[0] == 'norepository':
141
raise errors.NoRepositoryPresent(self)
142
assert len(response) == 4, 'incorrect response length %s' % (response,)
177
143
if response[1] == '':
178
144
format = RemoteRepositoryFormat()
179
145
format.rich_root_data = (response[2] == 'yes')
180
146
format.supports_tree_reference = (response[3] == 'yes')
181
# No wire format to check this yet.
182
format.supports_external_lookups = (response[4] == 'yes')
183
147
return RemoteRepository(self, format)
185
149
raise errors.NoRepositoryPresent(self)
345
298
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
346
299
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
348
def find_text_key_references(self):
349
"""Find the text key references within the repository.
351
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
352
revision_ids. Each altered file-ids has the exact revision_ids that
353
altered it listed explicitly.
354
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
355
to whether they were referred to by the inventory of the
356
revision_id that they contain. The inventory texts from all present
357
revision ids are assessed to generate this report.
360
return self._real_repository.find_text_key_references()
362
def _generate_text_key_index(self):
363
"""Generate a new text key index for the repository.
365
This is an expensive function that will take considerable time to run.
367
:return: A dict mapping (file_id, revision_id) tuples to a list of
368
parents, also (file_id, revision_id) tuples.
371
return self._real_repository._generate_text_key_index()
373
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
374
301
def get_revision_graph(self, revision_id=None):
375
302
"""See Repository.get_revision_graph()."""
376
return self._get_revision_graph(revision_id)
378
def _get_revision_graph(self, revision_id):
379
"""Private method for using with old (< 1.2) servers to fallback."""
380
303
if revision_id is None:
382
elif revision.is_null(revision_id):
305
elif revision_id == NULL_REVISION:
385
308
path = self.bzrdir._path_for_remote_call(self._client)
387
response = self._client.call_expecting_body(
388
'Repository.get_revision_graph', path, revision_id)
389
except errors.ErrorFromSmartServer, err:
390
if err.error_verb == 'nosuchrevision':
391
raise NoSuchRevision(self, revision_id)
393
response_tuple, response_handler = response
394
if response_tuple[0] != 'ok':
395
raise errors.UnexpectedSmartServerResponse(response_tuple)
396
coded = response_handler.read_body_bytes()
398
# no revisions in this repository!
400
lines = coded.split('\n')
403
d = tuple(line.split())
404
revision_graph[d[0]] = d[1:]
406
return revision_graph
309
assert type(revision_id) is str
310
response = self._client.call_expecting_body(
311
'Repository.get_revision_graph', path, revision_id)
312
if response[0][0] not in ['ok', 'nosuchrevision']:
313
raise errors.UnexpectedSmartServerResponse(response[0])
314
if response[0][0] == 'ok':
315
coded = response[1].read_body_bytes()
317
# no revisions in this repository!
319
lines = coded.split('\n')
322
d = tuple(line.split())
323
revision_graph[d[0]] = d[1:]
325
return revision_graph
327
response_body = response[1].read_body_bytes()
328
assert response_body == ''
329
raise NoSuchRevision(self, revision_id)
408
331
def has_revision(self, revision_id):
409
332
"""See Repository.has_revision()."""
410
if revision_id == NULL_REVISION:
333
if revision_id is None:
411
334
# The null revision is always present.
413
336
path = self.bzrdir._path_for_remote_call(self._client)
414
response = self._client.call(
415
'Repository.has_revision', path, revision_id)
416
if response[0] not in ('yes', 'no'):
417
raise errors.UnexpectedSmartServerResponse(response)
337
response = self._client.call('Repository.has_revision', path, revision_id)
338
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
418
339
return response[0] == 'yes'
420
def has_revisions(self, revision_ids):
421
"""See Repository.has_revisions()."""
423
for revision_id in revision_ids:
424
if self.has_revision(revision_id):
425
result.add(revision_id)
428
341
def has_same_location(self, other):
429
342
return (self.__class__ == other.__class__ and
430
343
self.bzrdir.transport.base == other.bzrdir.transport.base)
432
345
def get_graph(self, other_repository=None):
433
346
"""Return the graph for this repository format"""
434
parents_provider = self
435
if (other_repository is not None and
436
other_repository.bzrdir.transport.base !=
437
self.bzrdir.transport.base):
438
parents_provider = graph._StackedParentsProvider(
439
[parents_provider, other_repository._make_parents_provider()])
440
return graph.Graph(parents_provider)
347
return self._real_repository.get_graph(other_repository)
442
349
def gather_stats(self, revid=None, committers=None):
443
350
"""See Repository.gather_stats()."""
444
351
path = self.bzrdir._path_for_remote_call(self._client)
445
# revid can be None to indicate no revisions, not just NULL_REVISION
446
if revid is None or revision.is_null(revid):
352
if revid in (None, NULL_REVISION):
449
355
fmt_revid = revid
521
412
path = self.bzrdir._path_for_remote_call(self._client)
522
413
if token is None:
525
response = self._client.call('Repository.lock_write', path, token)
526
except errors.ErrorFromSmartServer, err:
527
if err.error_verb == 'LockContention':
528
raise errors.LockContention('(remote lock)')
529
elif err.error_verb == 'UnlockableTransport':
530
raise errors.UnlockableTransport(self.bzrdir.root_transport)
531
elif err.error_verb == 'LockFailed':
532
raise errors.LockFailed(err.error_args[0], err.error_args[1])
415
response = self._client.call('Repository.lock_write', path, token)
535
416
if response[0] == 'ok':
536
417
ok, token = response
419
elif response[0] == 'LockContention':
420
raise errors.LockContention('(remote lock)')
421
elif response[0] == 'UnlockableTransport':
422
raise errors.UnlockableTransport(self.bzrdir.root_transport)
423
elif response[0] == 'LockFailed':
424
raise errors.LockFailed(response[1], response[2])
539
426
raise errors.UnexpectedSmartServerResponse(response)
541
428
def lock_write(self, token=None):
542
429
if not self._lock_mode:
543
430
self._lock_token = self._remote_lock_write(token)
544
# if self._lock_token is None, then this is something like packs or
545
# svn where we don't get to lock the repo, or a weave style repository
546
# where we cannot lock it over the wire and attempts to do so will
431
assert self._lock_token, 'Remote server did not return a token!'
548
432
if self._real_repository is not None:
549
433
self._real_repository.lock_write(token=self._lock_token)
550
434
if token is not None:
602
478
def _unlock(self, token):
603
479
path = self.bzrdir._path_for_remote_call(self._client)
605
# with no token the remote repository is not persistently locked.
608
response = self._client.call('Repository.unlock', path, token)
609
except errors.ErrorFromSmartServer, err:
610
if err.error_verb == 'TokenMismatch':
611
raise errors.TokenMismatch(token, '(remote token)')
480
response = self._client.call('Repository.unlock', path, token)
613
481
if response == ('ok',):
483
elif response[0] == 'TokenMismatch':
484
raise errors.TokenMismatch(token, '(remote token)')
616
486
raise errors.UnexpectedSmartServerResponse(response)
618
488
def unlock(self):
489
if self._lock_count == 1 and self._lock_mode == 'w':
490
# don't unlock if inside a write group.
491
if self.is_in_write_group():
492
raise errors.BzrError(
493
'Must end write groups before releasing write locks.')
619
494
self._lock_count -= 1
620
if self._lock_count > 0:
622
self._parents_map = None
623
if 'hpss' in debug.debug_flags:
624
self._requested_parents = None
625
old_mode = self._lock_mode
626
self._lock_mode = None
628
# The real repository is responsible at present for raising an
629
# exception if it's in an unfinished write group. However, it
630
# normally will *not* actually remove the lock from disk - that's
631
# done by the server on receiving the Repository.unlock call.
632
# This is just to let the _real_repository stay up to date.
495
if not self._lock_count:
496
mode = self._lock_mode
497
self._lock_mode = None
633
498
if self._real_repository is not None:
634
499
self._real_repository.unlock()
636
# The rpc-level lock should be released even if there was a
637
# problem releasing the vfs-based lock.
639
501
# Only write-locked repositories need to make a remote method
640
502
# call to perfom the unlock.
641
old_token = self._lock_token
642
self._lock_token = None
643
if not self._leave_lock:
644
self._unlock(old_token)
504
assert self._lock_token, 'Locked, but no token!'
505
token = self._lock_token
506
self._lock_token = None
507
if not self._leave_lock:
646
510
def break_lock(self):
647
511
# should hand off to the network
737
605
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
739
607
def make_working_trees(self):
740
"""See Repository.make_working_trees"""
742
return self._real_repository.make_working_trees()
744
def revision_ids_to_search_result(self, result_set):
745
"""Convert a set of revision ids to a graph SearchResult."""
746
result_parents = set()
747
for parents in self.get_graph().get_parent_map(
748
result_set).itervalues():
749
result_parents.update(parents)
750
included_keys = result_set.intersection(result_parents)
751
start_keys = result_set.difference(included_keys)
752
exclude_keys = result_parents.difference(result_set)
753
result = graph.SearchResult(start_keys, exclude_keys,
754
len(result_set), result_set)
758
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
759
"""Return the revision ids that other has that this does not.
761
These are returned in topological order.
763
revision_id: only return revision ids included by revision_id.
765
return repository.InterRepository.get(
766
other, self).search_missing_revision_ids(revision_id, find_ghosts)
608
"""RemoteRepositories never create working trees by default."""
768
611
def fetch(self, source, revision_id=None, pb=None):
769
612
if self.has_same_location(source):
770
613
# check that last_revision is in 'from' and then return a
772
615
if (revision_id is not None and
773
not revision.is_null(revision_id)):
616
not _mod_revision.is_null(revision_id)):
774
617
self.get_revision(revision_id)
776
619
self._ensure_real()
800
643
self._ensure_real()
801
644
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
803
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
805
return self._real_repository._get_versioned_file_checker(
806
revisions, revision_versions_cache)
808
646
def iter_files_bytes(self, desired_files):
809
647
"""See Repository.iter_file_bytes.
811
649
self._ensure_real()
812
650
return self._real_repository.iter_files_bytes(desired_files)
814
def get_parent_map(self, keys):
815
"""See bzrlib.Graph.get_parent_map()."""
816
# Hack to build up the caching logic.
817
ancestry = self._parents_map
819
# Repository is not locked, so there's no cache.
820
missing_revisions = set(keys)
823
missing_revisions = set(key for key in keys if key not in ancestry)
824
if missing_revisions:
825
parent_map = self._get_parent_map(missing_revisions)
826
if 'hpss' in debug.debug_flags:
827
mutter('retransmitted revisions: %d of %d',
828
len(set(ancestry).intersection(parent_map)),
830
ancestry.update(parent_map)
831
present_keys = [k for k in keys if k in ancestry]
832
if 'hpss' in debug.debug_flags:
833
if self._requested_parents is not None and len(ancestry) != 0:
834
self._requested_parents.update(present_keys)
835
mutter('Current RemoteRepository graph hit rate: %d%%',
836
100.0 * len(self._requested_parents) / len(ancestry))
837
return dict((k, ancestry[k]) for k in present_keys)
839
def _get_parent_map(self, keys):
840
"""Helper for get_parent_map that performs the RPC."""
841
medium = self._client._medium
842
if not medium._remote_is_at_least_1_2:
843
# We already found out that the server can't understand
844
# Repository.get_parent_map requests, so just fetch the whole
846
# XXX: Note that this will issue a deprecation warning. This is ok
847
# :- its because we're working with a deprecated server anyway, and
848
# the user will almost certainly have seen a warning about the
849
# server version already.
850
rg = self.get_revision_graph()
851
# There is an api discrepency between get_parent_map and
852
# get_revision_graph. Specifically, a "key:()" pair in
853
# get_revision_graph just means a node has no parents. For
854
# "get_parent_map" it means the node is a ghost. So fix up the
855
# graph to correct this.
856
# https://bugs.launchpad.net/bzr/+bug/214894
857
# There is one other "bug" which is that ghosts in
858
# get_revision_graph() are not returned at all. But we won't worry
859
# about that for now.
860
for node_id, parent_ids in rg.iteritems():
862
rg[node_id] = (NULL_REVISION,)
863
rg[NULL_REVISION] = ()
868
raise ValueError('get_parent_map(None) is not valid')
869
if NULL_REVISION in keys:
870
keys.discard(NULL_REVISION)
871
found_parents = {NULL_REVISION:()}
876
# TODO(Needs analysis): We could assume that the keys being requested
877
# from get_parent_map are in a breadth first search, so typically they
878
# will all be depth N from some common parent, and we don't have to
879
# have the server iterate from the root parent, but rather from the
880
# keys we're searching; and just tell the server the keyspace we
881
# already have; but this may be more traffic again.
883
# Transform self._parents_map into a search request recipe.
884
# TODO: Manage this incrementally to avoid covering the same path
885
# repeatedly. (The server will have to on each request, but the less
886
# work done the better).
887
parents_map = self._parents_map
888
if parents_map is None:
889
# Repository is not locked, so there's no cache.
891
start_set = set(parents_map)
892
result_parents = set()
893
for parents in parents_map.itervalues():
894
result_parents.update(parents)
895
stop_keys = result_parents.difference(start_set)
896
included_keys = start_set.intersection(result_parents)
897
start_set.difference_update(included_keys)
898
recipe = (start_set, stop_keys, len(parents_map))
899
body = self._serialise_search_recipe(recipe)
900
path = self.bzrdir._path_for_remote_call(self._client)
902
if type(key) is not str:
904
"key %r not a plain string" % (key,))
905
verb = 'Repository.get_parent_map'
906
args = (path,) + tuple(keys)
908
response = self._client.call_with_body_bytes_expecting_body(
909
verb, args, self._serialise_search_recipe(recipe))
910
except errors.UnknownSmartMethod:
911
# Server does not support this method, so get the whole graph.
912
# Worse, we have to force a disconnection, because the server now
913
# doesn't realise it has a body on the wire to consume, so the
914
# only way to recover is to abandon the connection.
916
'Server is too old for fast get_parent_map, reconnecting. '
917
'(Upgrade the server to Bazaar 1.2 to avoid this)')
919
# To avoid having to disconnect repeatedly, we keep track of the
920
# fact the server doesn't understand remote methods added in 1.2.
921
medium._remote_is_at_least_1_2 = False
922
return self.get_revision_graph(None)
923
response_tuple, response_handler = response
924
if response_tuple[0] not in ['ok']:
925
response_handler.cancel_read_body()
926
raise errors.UnexpectedSmartServerResponse(response_tuple)
927
if response_tuple[0] == 'ok':
928
coded = bz2.decompress(response_handler.read_body_bytes())
932
lines = coded.split('\n')
935
d = tuple(line.split())
937
revision_graph[d[0]] = d[1:]
939
# No parents - so give the Graph result (NULL_REVISION,).
940
revision_graph[d[0]] = (NULL_REVISION,)
941
return revision_graph
944
653
def get_signature_text(self, revision_id):
945
654
self._ensure_real()
946
655
return self._real_repository.get_signature_text(revision_id)
949
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
950
658
def get_revision_graph_with_ghosts(self, revision_ids=None):
951
659
self._ensure_real()
952
660
return self._real_repository.get_revision_graph_with_ghosts(
1069
777
return self._real_repository.store_revision_signature(
1070
778
gpg_strategy, plaintext, revision_id)
1072
def add_signature_text(self, revision_id, signature):
1074
return self._real_repository.add_signature_text(revision_id, signature)
1076
780
def has_signature_for_revision_id(self, revision_id):
1077
781
self._ensure_real()
1078
782
return self._real_repository.has_signature_for_revision_id(revision_id)
1080
def get_data_stream_for_search(self, search):
1081
medium = self._client._medium
1082
if not medium._remote_is_at_least_1_2:
1084
return self._real_repository.get_data_stream_for_search(search)
1085
REQUEST_NAME = 'Repository.stream_revisions_chunked'
1086
path = self.bzrdir._path_for_remote_call(self._client)
1087
body = self._serialise_search_recipe(search.get_recipe())
1089
result = self._client.call_with_body_bytes_expecting_body(
1090
REQUEST_NAME, (path,), body)
1091
response, protocol = result
1092
except errors.UnknownSmartMethod:
1093
# Server does not support this method, so fall back to VFS.
1094
# Worse, we have to force a disconnection, because the server now
1095
# doesn't realise it has a body on the wire to consume, so the
1096
# only way to recover is to abandon the connection.
1098
'Server is too old for streaming pull, reconnecting. '
1099
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1101
# To avoid having to disconnect repeatedly, we keep track of the
1102
# fact the server doesn't understand this remote method.
1103
medium._remote_is_at_least_1_2 = False
1105
return self._real_repository.get_data_stream_for_search(search)
1107
if response == ('ok',):
1108
return self._deserialise_stream(protocol)
1109
if response == ('NoSuchRevision', ):
1110
# We cannot easily identify the revision that is missing in this
1111
# situation without doing much more network IO. For now, bail.
1112
raise NoSuchRevision(self, "unknown")
1114
raise errors.UnexpectedSmartServerResponse(response)
1116
def _deserialise_stream(self, protocol):
1117
stream = protocol.read_streamed_body()
1118
container_parser = ContainerPushParser()
1119
for bytes in stream:
1120
container_parser.accept_bytes(bytes)
1121
records = container_parser.read_pending_records()
1122
for record_names, record_bytes in records:
1123
if len(record_names) != 1:
1124
# These records should have only one name, and that name
1125
# should be a one-element tuple.
1126
raise errors.SmartProtocolError(
1127
'Repository data stream had invalid record name %r'
1129
name_tuple = record_names[0]
1130
yield name_tuple, record_bytes
1132
def insert_data_stream(self, stream):
1134
self._real_repository.insert_data_stream(stream)
1136
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1138
return self._real_repository.item_keys_introduced_by(revision_ids,
1139
_files_pb=_files_pb)
1141
def revision_graph_can_have_wrong_parents(self):
1142
# The answer depends on the remote repo format.
1144
return self._real_repository.revision_graph_can_have_wrong_parents()
1146
def _find_inconsistent_revision_parents(self):
1148
return self._real_repository._find_inconsistent_revision_parents()
1150
def _check_for_inconsistent_revision_parents(self):
1152
return self._real_repository._check_for_inconsistent_revision_parents()
1154
def _make_parents_provider(self):
1157
def _serialise_search_recipe(self, recipe):
1158
"""Serialise a graph search recipe.
1160
:param recipe: A search recipe (start, stop, count).
1161
:return: Serialised bytes.
1163
start_keys = ' '.join(recipe[0])
1164
stop_keys = ' '.join(recipe[1])
1165
count = str(recipe[2])
1166
return '\n'.join((start_keys, stop_keys, count))
1169
785
class RemoteBranchLockableFiles(LockableFiles):
1170
786
"""A 'LockableFiles' implementation that talks to a smart server.
1328
953
repo_token = self.repository.lock_write()
1329
954
self.repository.unlock()
1330
955
path = self.bzrdir._path_for_remote_call(self._client)
1332
response = self._client.call(
1333
'Branch.lock_write', path, branch_token, repo_token or '')
1334
except errors.ErrorFromSmartServer, err:
1335
if err.error_verb == 'LockContention':
1336
raise errors.LockContention('(remote lock)')
1337
elif err.error_verb == 'TokenMismatch':
1338
raise errors.TokenMismatch(token, '(remote token)')
1339
elif err.error_verb == 'UnlockableTransport':
1340
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1341
elif err.error_verb == 'ReadOnlyError':
1342
raise errors.ReadOnlyError(self)
1343
elif err.error_verb == 'LockFailed':
1344
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1346
if response[0] != 'ok':
956
response = self._client.call('Branch.lock_write', path, branch_token,
958
if response[0] == 'ok':
959
ok, branch_token, repo_token = response
960
return branch_token, repo_token
961
elif response[0] == 'LockContention':
962
raise errors.LockContention('(remote lock)')
963
elif response[0] == 'TokenMismatch':
964
raise errors.TokenMismatch(token, '(remote token)')
965
elif response[0] == 'UnlockableTransport':
966
raise errors.UnlockableTransport(self.bzrdir.root_transport)
967
elif response[0] == 'ReadOnlyError':
968
raise errors.ReadOnlyError(self)
969
elif response[0] == 'LockFailed':
970
raise errors.LockFailed(response[1], response[2])
1347
972
raise errors.UnexpectedSmartServerResponse(response)
1348
ok, branch_token, repo_token = response
1349
return branch_token, repo_token
1351
974
def lock_write(self, token=None):
1352
975
if not self._lock_mode:
1353
976
remote_tokens = self._remote_lock_write(token)
1354
977
self._lock_token, self._repo_lock_token = remote_tokens
1355
if not self._lock_token:
1356
raise SmartProtocolError('Remote server did not return a token!')
978
assert self._lock_token, 'Remote server did not return a token!'
1357
979
# TODO: We really, really, really don't want to call _ensure_real
1358
980
# here, but it's the easiest way to ensure coherency between the
1359
981
# state of the RemoteBranch and RemoteRepository objects and the
1385
1007
if token != self._lock_token:
1386
1008
raise errors.TokenMismatch(token, self._lock_token)
1387
1009
self._lock_count += 1
1388
return self._lock_token or None
1010
return self._lock_token
1390
1012
def _unlock(self, branch_token, repo_token):
1391
1013
path = self.bzrdir._path_for_remote_call(self._client)
1393
response = self._client.call('Branch.unlock', path, branch_token,
1395
except errors.ErrorFromSmartServer, err:
1396
if err.error_verb == 'TokenMismatch':
1397
raise errors.TokenMismatch(
1398
str((branch_token, repo_token)), '(remote tokens)')
1014
response = self._client.call('Branch.unlock', path, branch_token,
1400
1016
if response == ('ok',):
1402
raise errors.UnexpectedSmartServerResponse(response)
1018
elif response[0] == 'TokenMismatch':
1019
raise errors.TokenMismatch(
1020
str((branch_token, repo_token)), '(remote tokens)')
1022
raise errors.UnexpectedSmartServerResponse(response)
1404
1024
def unlock(self):
1405
1025
self._lock_count -= 1
1529
1142
def is_locked(self):
1530
1143
return self._lock_count >= 1
1533
1145
def set_last_revision_info(self, revno, revision_id):
1534
revision_id = ensure_null(revision_id)
1535
path = self.bzrdir._path_for_remote_call(self._client)
1537
response = self._client.call('Branch.set_last_revision_info',
1538
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1539
except errors.UnknownSmartMethod:
1541
self._clear_cached_state()
1542
return self._real_branch.set_last_revision_info(revno, revision_id)
1543
except errors.ErrorFromSmartServer, err:
1544
if err.error_verb == 'NoSuchRevision':
1545
raise NoSuchRevision(self, err.error_args[0])
1547
if response == ('ok',):
1548
self._clear_cached_state()
1550
raise errors.UnexpectedSmartServerResponse(response)
1147
self._clear_cached_state()
1148
return self._real_branch.set_last_revision_info(revno, revision_id)
1552
1150
def generate_revision_history(self, revision_id, last_rev=None,
1553
1151
other_branch=None):