1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
21
from cStringIO import StringIO
33
from bzrlib.branch import BranchReferenceFormat
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
from bzrlib.config import BranchConfig, TreeConfig
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.pack import ContainerPushParser
43
from bzrlib.smart import client, vfs
44
from bzrlib.symbol_versioning import (
48
from bzrlib.revision import ensure_null, NULL_REVISION
49
from bzrlib.trace import mutter, note, warning
52
# Note: RemoteBzrDirFormat is in bzrdir.py
54
class RemoteBzrDir(BzrDir):
55
"""Control directory on a remote server, accessed via bzr:// or similar."""
57
def __init__(self, transport, _client=None):
58
"""Construct a RemoteBzrDir.
60
:param _client: Private parameter for testing. Disables probing and the
63
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
64
# this object holds a delegated bzrdir that uses file-level operations
65
# to talk to the other side
66
self._real_bzrdir = None
69
medium = transport.get_smart_medium()
70
self._client = client._SmartClient(medium)
72
self._client = _client
75
path = self._path_for_remote_call(self._client)
76
response = self._client.call('BzrDir.open', path)
77
if response not in [('yes',), ('no',)]:
78
raise errors.UnexpectedSmartServerResponse(response)
79
if response == ('no',):
80
raise errors.NotBranchError(path=transport.base)
82
def _ensure_real(self):
83
"""Ensure that there is a _real_bzrdir set.
85
Used before calls to self._real_bzrdir.
87
if not self._real_bzrdir:
88
self._real_bzrdir = BzrDir.open_from_transport(
89
self.root_transport, _server_formats=False)
91
def create_repository(self, shared=False):
93
self._real_bzrdir.create_repository(shared=shared)
94
return self.open_repository()
96
def destroy_repository(self):
97
"""See BzrDir.destroy_repository"""
99
self._real_bzrdir.destroy_repository()
101
def create_branch(self):
103
real_branch = self._real_bzrdir.create_branch()
104
return RemoteBranch(self, self.find_repository(), real_branch)
106
def destroy_branch(self):
107
"""See BzrDir.destroy_branch"""
109
self._real_bzrdir.destroy_branch()
111
def create_workingtree(self, revision_id=None, from_branch=None):
112
raise errors.NotLocalUrl(self.transport.base)
114
def find_branch_format(self):
115
"""Find the branch 'format' for this bzrdir.
117
This might be a synthetic object for e.g. RemoteBranch and SVN.
119
b = self.open_branch()
122
def get_branch_reference(self):
123
"""See BzrDir.get_branch_reference()."""
124
path = self._path_for_remote_call(self._client)
126
response = self._client.call('BzrDir.open_branch', path)
127
except errors.ErrorFromSmartServer, err:
128
if err.error_tuple == ('nobranch',):
129
raise errors.NotBranchError(path=self.root_transport.base)
131
if response[0] == 'ok':
132
if response[1] == '':
133
# branch at this location.
136
# a branch reference, use the existing BranchReference logic.
139
raise errors.UnexpectedSmartServerResponse(response)
141
def _get_tree_branch(self):
142
"""See BzrDir._get_tree_branch()."""
143
return None, self.open_branch()
145
def open_branch(self, _unsupported=False):
147
raise NotImplementedError('unsupported flag support not implemented yet.')
148
reference_url = self.get_branch_reference()
149
if reference_url is None:
150
# branch at this location.
151
return RemoteBranch(self, self.find_repository())
153
# a branch reference, use the existing BranchReference logic.
154
format = BranchReferenceFormat()
155
return format.open(self, _found=True, location=reference_url)
157
def open_repository(self):
158
path = self._path_for_remote_call(self._client)
159
verb = 'BzrDir.find_repositoryV2'
162
response = self._client.call(verb, path)
163
except errors.UnknownSmartMethod:
164
verb = 'BzrDir.find_repository'
165
response = self._client.call(verb, path)
166
except errors.ErrorFromSmartServer, err:
167
if err.error_verb == 'norepository':
168
raise errors.NoRepositoryPresent(self)
170
if response[0] != 'ok':
171
raise errors.UnexpectedSmartServerResponse(response)
172
if verb == 'BzrDir.find_repository':
173
# servers that don't support the V2 method don't support external
175
response = response + ('no', )
176
if not (len(response) == 5):
177
raise SmartProtocolError('incorrect response length %s' % (response,))
178
if response[1] == '':
179
format = RemoteRepositoryFormat()
180
format.rich_root_data = (response[2] == 'yes')
181
format.supports_tree_reference = (response[3] == 'yes')
182
# No wire format to check this yet.
183
format.supports_external_lookups = (response[4] == 'yes')
184
return RemoteRepository(self, format)
186
raise errors.NoRepositoryPresent(self)
188
def open_workingtree(self, recommend_upgrade=True):
190
if self._real_bzrdir.has_workingtree():
191
raise errors.NotLocalUrl(self.root_transport)
193
raise errors.NoWorkingTree(self.root_transport.base)
195
def _path_for_remote_call(self, client):
196
"""Return the path to be used for this bzrdir in a remote call."""
197
return client.remote_path_from_transport(self.root_transport)
199
def get_branch_transport(self, branch_format):
201
return self._real_bzrdir.get_branch_transport(branch_format)
203
def get_repository_transport(self, repository_format):
205
return self._real_bzrdir.get_repository_transport(repository_format)
207
def get_workingtree_transport(self, workingtree_format):
209
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
211
def can_convert_format(self):
212
"""Upgrading of remote bzrdirs is not supported yet."""
215
def needs_format_conversion(self, format=None):
216
"""Upgrading of remote bzrdirs is not supported yet."""
219
def clone(self, url, revision_id=None, force_new_repo=False):
221
return self._real_bzrdir.clone(url, revision_id=revision_id,
222
force_new_repo=force_new_repo)
225
class RemoteRepositoryFormat(repository.RepositoryFormat):
226
"""Format for repositories accessed over a _SmartClient.
228
Instances of this repository are represented by RemoteRepository
231
The RemoteRepositoryFormat is parameterized during construction
232
to reflect the capabilities of the real, remote format. Specifically
233
the attributes rich_root_data and supports_tree_reference are set
234
on a per instance basis, and are not set (and should not be) at
238
_matchingbzrdir = RemoteBzrDirFormat
240
def initialize(self, a_bzrdir, shared=False):
241
if not isinstance(a_bzrdir, RemoteBzrDir):
242
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
243
return a_bzrdir.create_repository(shared=shared)
245
def open(self, a_bzrdir):
246
if not isinstance(a_bzrdir, RemoteBzrDir):
247
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
248
return a_bzrdir.open_repository()
250
def get_format_description(self):
251
return 'bzr remote repository'
253
def __eq__(self, other):
254
return self.__class__ == other.__class__
256
def check_conversion_target(self, target_format):
257
if self.rich_root_data and not target_format.rich_root_data:
258
raise errors.BadConversionTarget(
259
'Does not support rich root data.', target_format)
260
if (self.supports_tree_reference and
261
not getattr(target_format, 'supports_tree_reference', False)):
262
raise errors.BadConversionTarget(
263
'Does not support nested trees', target_format)
266
class RemoteRepository(object):
267
"""Repository accessed over rpc.
269
For the moment most operations are performed using local transport-backed
273
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
274
"""Create a RemoteRepository instance.
276
:param remote_bzrdir: The bzrdir hosting this repository.
277
:param format: The RemoteFormat object to use.
278
:param real_repository: If not None, a local implementation of the
279
repository logic for the repository, usually accessing the data
281
:param _client: Private testing parameter - override the smart client
282
to be used by the repository.
285
self._real_repository = real_repository
287
self._real_repository = None
288
self.bzrdir = remote_bzrdir
290
self._client = remote_bzrdir._client
292
self._client = _client
293
self._format = format
294
self._lock_mode = None
295
self._lock_token = None
297
self._leave_lock = False
298
# A cache of looked up revision parent data; reset at unlock time.
299
self._parents_map = None
300
if 'hpss' in debug.debug_flags:
301
self._requested_parents = None
303
# These depend on the actual remote format, so force them off for
304
# maximum compatibility. XXX: In future these should depend on the
305
# remote repository instance, but this is irrelevant until we perform
306
# reconcile via an RPC call.
307
self._reconcile_does_inventory_gc = False
308
self._reconcile_fixes_text_parents = False
309
self._reconcile_backsup_inventory = False
310
self.base = self.bzrdir.transport.base
311
# Additional places to query for data.
312
self._fallback_repositories = []
315
return "%s(%s)" % (self.__class__.__name__, self.base)
319
def abort_write_group(self):
320
"""Complete a write group on the decorated repository.
322
Smart methods peform operations in a single step so this api
323
is not really applicable except as a compatibility thunk
324
for older plugins that don't use e.g. the CommitBuilder
328
return self._real_repository.abort_write_group()
330
def commit_write_group(self):
331
"""Complete a write group on the decorated repository.
333
Smart methods peform operations in a single step so this api
334
is not really applicable except as a compatibility thunk
335
for older plugins that don't use e.g. the CommitBuilder
339
return self._real_repository.commit_write_group()
341
def _ensure_real(self):
342
"""Ensure that there is a _real_repository set.
344
Used before calls to self._real_repository.
346
if not self._real_repository:
347
self.bzrdir._ensure_real()
348
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
349
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
351
def find_text_key_references(self):
352
"""Find the text key references within the repository.
354
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
355
revision_ids. Each altered file-ids has the exact revision_ids that
356
altered it listed explicitly.
357
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
358
to whether they were referred to by the inventory of the
359
revision_id that they contain. The inventory texts from all present
360
revision ids are assessed to generate this report.
363
return self._real_repository.find_text_key_references()
365
def _generate_text_key_index(self):
366
"""Generate a new text key index for the repository.
368
This is an expensive function that will take considerable time to run.
370
:return: A dict mapping (file_id, revision_id) tuples to a list of
371
parents, also (file_id, revision_id) tuples.
374
return self._real_repository._generate_text_key_index()
376
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
377
def get_revision_graph(self, revision_id=None):
378
"""See Repository.get_revision_graph()."""
379
return self._get_revision_graph(revision_id)
381
def _get_revision_graph(self, revision_id):
382
"""Private method for using with old (< 1.2) servers to fallback."""
383
if revision_id is None:
385
elif revision.is_null(revision_id):
388
path = self.bzrdir._path_for_remote_call(self._client)
390
response = self._client.call_expecting_body(
391
'Repository.get_revision_graph', path, revision_id)
392
except errors.ErrorFromSmartServer, err:
393
if err.error_verb == 'nosuchrevision':
394
raise NoSuchRevision(self, revision_id)
396
response_tuple, response_handler = response
397
if response_tuple[0] != 'ok':
398
raise errors.UnexpectedSmartServerResponse(response_tuple)
399
coded = response_handler.read_body_bytes()
401
# no revisions in this repository!
403
lines = coded.split('\n')
406
d = tuple(line.split())
407
revision_graph[d[0]] = d[1:]
409
return revision_graph
411
def has_revision(self, revision_id):
412
"""See Repository.has_revision()."""
413
if revision_id == NULL_REVISION:
414
# The null revision is always present.
416
path = self.bzrdir._path_for_remote_call(self._client)
417
response = self._client.call(
418
'Repository.has_revision', path, revision_id)
419
if response[0] not in ('yes', 'no'):
420
raise errors.UnexpectedSmartServerResponse(response)
421
return response[0] == 'yes'
423
def has_revisions(self, revision_ids):
424
"""See Repository.has_revisions()."""
426
for revision_id in revision_ids:
427
if self.has_revision(revision_id):
428
result.add(revision_id)
431
def has_same_location(self, other):
432
return (self.__class__ == other.__class__ and
433
self.bzrdir.transport.base == other.bzrdir.transport.base)
435
def get_graph(self, other_repository=None):
436
"""Return the graph for this repository format"""
437
parents_provider = self
438
if (other_repository is not None and
439
other_repository.bzrdir.transport.base !=
440
self.bzrdir.transport.base):
441
parents_provider = graph._StackedParentsProvider(
442
[parents_provider, other_repository._make_parents_provider()])
443
return graph.Graph(parents_provider)
445
def gather_stats(self, revid=None, committers=None):
446
"""See Repository.gather_stats()."""
447
path = self.bzrdir._path_for_remote_call(self._client)
448
# revid can be None to indicate no revisions, not just NULL_REVISION
449
if revid is None or revision.is_null(revid):
453
if committers is None or not committers:
454
fmt_committers = 'no'
456
fmt_committers = 'yes'
457
response_tuple, response_handler = self._client.call_expecting_body(
458
'Repository.gather_stats', path, fmt_revid, fmt_committers)
459
if response_tuple[0] != 'ok':
460
raise errors.UnexpectedSmartServerResponse(response_tuple)
462
body = response_handler.read_body_bytes()
464
for line in body.split('\n'):
467
key, val_text = line.split(':')
468
if key in ('revisions', 'size', 'committers'):
469
result[key] = int(val_text)
470
elif key in ('firstrev', 'latestrev'):
471
values = val_text.split(' ')[1:]
472
result[key] = (float(values[0]), long(values[1]))
476
def find_branches(self, using=False):
477
"""See Repository.find_branches()."""
478
# should be an API call to the server.
480
return self._real_repository.find_branches(using=using)
482
def get_physical_lock_status(self):
483
"""See Repository.get_physical_lock_status()."""
484
# should be an API call to the server.
486
return self._real_repository.get_physical_lock_status()
488
def is_in_write_group(self):
489
"""Return True if there is an open write group.
491
write groups are only applicable locally for the smart server..
493
if self._real_repository:
494
return self._real_repository.is_in_write_group()
497
return self._lock_count >= 1
500
"""See Repository.is_shared()."""
501
path = self.bzrdir._path_for_remote_call(self._client)
502
response = self._client.call('Repository.is_shared', path)
503
if response[0] not in ('yes', 'no'):
504
raise SmartProtocolError('unexpected response code %s' % (response,))
505
return response[0] == 'yes'
507
def is_write_locked(self):
508
return self._lock_mode == 'w'
511
# wrong eventually - want a local lock cache context
512
if not self._lock_mode:
513
self._lock_mode = 'r'
515
self._parents_map = {}
516
if 'hpss' in debug.debug_flags:
517
self._requested_parents = set()
518
if self._real_repository is not None:
519
self._real_repository.lock_read()
521
self._lock_count += 1
523
def _remote_lock_write(self, token):
524
path = self.bzrdir._path_for_remote_call(self._client)
528
response = self._client.call('Repository.lock_write', path, token)
529
except errors.ErrorFromSmartServer, err:
530
if err.error_verb == 'LockContention':
531
raise errors.LockContention('(remote lock)')
532
elif err.error_verb == 'UnlockableTransport':
533
raise errors.UnlockableTransport(self.bzrdir.root_transport)
534
elif err.error_verb == 'LockFailed':
535
raise errors.LockFailed(err.error_args[0], err.error_args[1])
538
if response[0] == 'ok':
542
raise errors.UnexpectedSmartServerResponse(response)
544
def lock_write(self, token=None):
545
if not self._lock_mode:
546
self._lock_token = self._remote_lock_write(token)
547
# if self._lock_token is None, then this is something like packs or
548
# svn where we don't get to lock the repo, or a weave style repository
549
# where we cannot lock it over the wire and attempts to do so will
551
if self._real_repository is not None:
552
self._real_repository.lock_write(token=self._lock_token)
553
if token is not None:
554
self._leave_lock = True
556
self._leave_lock = False
557
self._lock_mode = 'w'
559
self._parents_map = {}
560
if 'hpss' in debug.debug_flags:
561
self._requested_parents = set()
562
elif self._lock_mode == 'r':
563
raise errors.ReadOnlyError(self)
565
self._lock_count += 1
566
return self._lock_token or None
568
def leave_lock_in_place(self):
569
if not self._lock_token:
570
raise NotImplementedError(self.leave_lock_in_place)
571
self._leave_lock = True
573
def dont_leave_lock_in_place(self):
574
if not self._lock_token:
575
raise NotImplementedError(self.dont_leave_lock_in_place)
576
self._leave_lock = False
578
def _set_real_repository(self, repository):
579
"""Set the _real_repository for this repository.
581
:param repository: The repository to fallback to for non-hpss
582
implemented operations.
584
if isinstance(repository, RemoteRepository):
585
raise AssertionError()
586
self._real_repository = repository
587
if self._lock_mode == 'w':
588
# if we are already locked, the real repository must be able to
589
# acquire the lock with our token.
590
self._real_repository.lock_write(self._lock_token)
591
elif self._lock_mode == 'r':
592
self._real_repository.lock_read()
594
def start_write_group(self):
595
"""Start a write group on the decorated repository.
597
Smart methods peform operations in a single step so this api
598
is not really applicable except as a compatibility thunk
599
for older plugins that don't use e.g. the CommitBuilder
603
return self._real_repository.start_write_group()
605
def _unlock(self, token):
606
path = self.bzrdir._path_for_remote_call(self._client)
608
# with no token the remote repository is not persistently locked.
611
response = self._client.call('Repository.unlock', path, token)
612
except errors.ErrorFromSmartServer, err:
613
if err.error_verb == 'TokenMismatch':
614
raise errors.TokenMismatch(token, '(remote token)')
616
if response == ('ok',):
619
raise errors.UnexpectedSmartServerResponse(response)
622
self._lock_count -= 1
623
if self._lock_count > 0:
625
self._parents_map = None
626
if 'hpss' in debug.debug_flags:
627
self._requested_parents = None
628
old_mode = self._lock_mode
629
self._lock_mode = None
631
# The real repository is responsible at present for raising an
632
# exception if it's in an unfinished write group. However, it
633
# normally will *not* actually remove the lock from disk - that's
634
# done by the server on receiving the Repository.unlock call.
635
# This is just to let the _real_repository stay up to date.
636
if self._real_repository is not None:
637
self._real_repository.unlock()
639
# The rpc-level lock should be released even if there was a
640
# problem releasing the vfs-based lock.
642
# Only write-locked repositories need to make a remote method
643
# call to perfom the unlock.
644
old_token = self._lock_token
645
self._lock_token = None
646
if not self._leave_lock:
647
self._unlock(old_token)
649
def break_lock(self):
650
# should hand off to the network
652
return self._real_repository.break_lock()
654
def _get_tarball(self, compression):
655
"""Return a TemporaryFile containing a repository tarball.
657
Returns None if the server does not support sending tarballs.
660
path = self.bzrdir._path_for_remote_call(self._client)
662
response, protocol = self._client.call_expecting_body(
663
'Repository.tarball', path, compression)
664
except errors.UnknownSmartMethod:
665
protocol.cancel_read_body()
667
if response[0] == 'ok':
668
# Extract the tarball and return it
669
t = tempfile.NamedTemporaryFile()
670
# TODO: rpc layer should read directly into it...
671
t.write(protocol.read_body_bytes())
674
raise errors.UnexpectedSmartServerResponse(response)
676
def sprout(self, to_bzrdir, revision_id=None):
677
# TODO: Option to control what format is created?
679
dest_repo = self._real_repository._format.initialize(to_bzrdir,
681
dest_repo.fetch(self, revision_id=revision_id)
684
### These methods are just thin shims to the VFS object for now.
686
def revision_tree(self, revision_id):
688
return self._real_repository.revision_tree(revision_id)
690
def get_serializer_format(self):
692
return self._real_repository.get_serializer_format()
694
def get_commit_builder(self, branch, parents, config, timestamp=None,
695
timezone=None, committer=None, revprops=None,
697
# FIXME: It ought to be possible to call this without immediately
698
# triggering _ensure_real. For now it's the easiest thing to do.
700
builder = self._real_repository.get_commit_builder(branch, parents,
701
config, timestamp=timestamp, timezone=timezone,
702
committer=committer, revprops=revprops, revision_id=revision_id)
705
def add_fallback_repository(self, repository):
706
"""Add a repository to use for looking up data not held locally.
708
:param repository: A repository.
710
if not self._format.supports_external_lookups:
711
raise errors.UnstackableRepositoryFormat(self._format, self.base)
712
# We need to accumulate additional repositories here, to pass them in
714
self._fallback_repositories.append(repository)
716
def add_inventory(self, revid, inv, parents):
718
return self._real_repository.add_inventory(revid, inv, parents)
720
def add_revision(self, rev_id, rev, inv=None, config=None):
722
return self._real_repository.add_revision(
723
rev_id, rev, inv=inv, config=config)
726
def get_inventory(self, revision_id):
728
return self._real_repository.get_inventory(revision_id)
730
def iter_inventories(self, revision_ids):
732
return self._real_repository.iter_inventories(revision_ids)
735
def get_revision(self, revision_id):
737
return self._real_repository.get_revision(revision_id)
740
def weave_store(self):
742
return self._real_repository.weave_store
744
def get_transaction(self):
746
return self._real_repository.get_transaction()
749
def clone(self, a_bzrdir, revision_id=None):
751
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
753
def make_working_trees(self):
754
"""See Repository.make_working_trees"""
756
return self._real_repository.make_working_trees()
758
def revision_ids_to_search_result(self, result_set):
759
"""Convert a set of revision ids to a graph SearchResult."""
760
result_parents = set()
761
for parents in self.get_graph().get_parent_map(
762
result_set).itervalues():
763
result_parents.update(parents)
764
included_keys = result_set.intersection(result_parents)
765
start_keys = result_set.difference(included_keys)
766
exclude_keys = result_parents.difference(result_set)
767
result = graph.SearchResult(start_keys, exclude_keys,
768
len(result_set), result_set)
772
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
773
"""Return the revision ids that other has that this does not.
775
These are returned in topological order.
777
revision_id: only return revision ids included by revision_id.
779
return repository.InterRepository.get(
780
other, self).search_missing_revision_ids(revision_id, find_ghosts)
782
def fetch(self, source, revision_id=None, pb=None):
783
if self.has_same_location(source):
784
# check that last_revision is in 'from' and then return a
786
if (revision_id is not None and
787
not revision.is_null(revision_id)):
788
self.get_revision(revision_id)
791
return self._real_repository.fetch(
792
source, revision_id=revision_id, pb=pb)
794
def create_bundle(self, target, base, fileobj, format=None):
796
self._real_repository.create_bundle(target, base, fileobj, format)
799
def control_weaves(self):
801
return self._real_repository.control_weaves
804
def get_ancestry(self, revision_id, topo_sorted=True):
806
return self._real_repository.get_ancestry(revision_id, topo_sorted)
809
def get_inventory_weave(self):
811
return self._real_repository.get_inventory_weave()
813
def fileids_altered_by_revision_ids(self, revision_ids):
815
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
817
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
819
return self._real_repository._get_versioned_file_checker(
820
revisions, revision_versions_cache)
822
def iter_files_bytes(self, desired_files):
823
"""See Repository.iter_file_bytes.
826
return self._real_repository.iter_files_bytes(desired_files)
828
def get_parent_map(self, keys):
829
"""See bzrlib.Graph.get_parent_map()."""
830
# Hack to build up the caching logic.
831
ancestry = self._parents_map
833
# Repository is not locked, so there's no cache.
834
missing_revisions = set(keys)
837
missing_revisions = set(key for key in keys if key not in ancestry)
838
if missing_revisions:
839
parent_map = self._get_parent_map(missing_revisions)
840
if 'hpss' in debug.debug_flags:
841
mutter('retransmitted revisions: %d of %d',
842
len(set(ancestry).intersection(parent_map)),
844
ancestry.update(parent_map)
845
present_keys = [k for k in keys if k in ancestry]
846
if 'hpss' in debug.debug_flags:
847
if self._requested_parents is not None and len(ancestry) != 0:
848
self._requested_parents.update(present_keys)
849
mutter('Current RemoteRepository graph hit rate: %d%%',
850
100.0 * len(self._requested_parents) / len(ancestry))
851
return dict((k, ancestry[k]) for k in present_keys)
853
def _get_parent_map(self, keys):
854
"""Helper for get_parent_map that performs the RPC."""
855
medium = self._client._medium
856
if not medium._remote_is_at_least_1_2:
857
# We already found out that the server can't understand
858
# Repository.get_parent_map requests, so just fetch the whole
860
# XXX: Note that this will issue a deprecation warning. This is ok
861
# :- its because we're working with a deprecated server anyway, and
862
# the user will almost certainly have seen a warning about the
863
# server version already.
864
rg = self.get_revision_graph()
865
# There is an api discrepency between get_parent_map and
866
# get_revision_graph. Specifically, a "key:()" pair in
867
# get_revision_graph just means a node has no parents. For
868
# "get_parent_map" it means the node is a ghost. So fix up the
869
# graph to correct this.
870
# https://bugs.launchpad.net/bzr/+bug/214894
871
# There is one other "bug" which is that ghosts in
872
# get_revision_graph() are not returned at all. But we won't worry
873
# about that for now.
874
for node_id, parent_ids in rg.iteritems():
876
rg[node_id] = (NULL_REVISION,)
877
rg[NULL_REVISION] = ()
882
raise ValueError('get_parent_map(None) is not valid')
883
if NULL_REVISION in keys:
884
keys.discard(NULL_REVISION)
885
found_parents = {NULL_REVISION:()}
890
# TODO(Needs analysis): We could assume that the keys being requested
891
# from get_parent_map are in a breadth first search, so typically they
892
# will all be depth N from some common parent, and we don't have to
893
# have the server iterate from the root parent, but rather from the
894
# keys we're searching; and just tell the server the keyspace we
895
# already have; but this may be more traffic again.
897
# Transform self._parents_map into a search request recipe.
898
# TODO: Manage this incrementally to avoid covering the same path
899
# repeatedly. (The server will have to on each request, but the less
900
# work done the better).
901
parents_map = self._parents_map
902
if parents_map is None:
903
# Repository is not locked, so there's no cache.
905
start_set = set(parents_map)
906
result_parents = set()
907
for parents in parents_map.itervalues():
908
result_parents.update(parents)
909
stop_keys = result_parents.difference(start_set)
910
included_keys = start_set.intersection(result_parents)
911
start_set.difference_update(included_keys)
912
recipe = (start_set, stop_keys, len(parents_map))
913
body = self._serialise_search_recipe(recipe)
914
path = self.bzrdir._path_for_remote_call(self._client)
916
if type(key) is not str:
918
"key %r not a plain string" % (key,))
919
verb = 'Repository.get_parent_map'
920
args = (path,) + tuple(keys)
922
response = self._client.call_with_body_bytes_expecting_body(
923
verb, args, self._serialise_search_recipe(recipe))
924
except errors.UnknownSmartMethod:
925
# Server does not support this method, so get the whole graph.
926
# Worse, we have to force a disconnection, because the server now
927
# doesn't realise it has a body on the wire to consume, so the
928
# only way to recover is to abandon the connection.
930
'Server is too old for fast get_parent_map, reconnecting. '
931
'(Upgrade the server to Bazaar 1.2 to avoid this)')
933
# To avoid having to disconnect repeatedly, we keep track of the
934
# fact the server doesn't understand remote methods added in 1.2.
935
medium._remote_is_at_least_1_2 = False
936
return self.get_revision_graph(None)
937
response_tuple, response_handler = response
938
if response_tuple[0] not in ['ok']:
939
response_handler.cancel_read_body()
940
raise errors.UnexpectedSmartServerResponse(response_tuple)
941
if response_tuple[0] == 'ok':
942
coded = bz2.decompress(response_handler.read_body_bytes())
946
lines = coded.split('\n')
949
d = tuple(line.split())
951
revision_graph[d[0]] = d[1:]
953
# No parents - so give the Graph result (NULL_REVISION,).
954
revision_graph[d[0]] = (NULL_REVISION,)
955
return revision_graph
958
def get_signature_text(self, revision_id):
960
return self._real_repository.get_signature_text(revision_id)
963
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
964
def get_revision_graph_with_ghosts(self, revision_ids=None):
966
return self._real_repository.get_revision_graph_with_ghosts(
967
revision_ids=revision_ids)
970
def get_inventory_xml(self, revision_id):
972
return self._real_repository.get_inventory_xml(revision_id)
974
def deserialise_inventory(self, revision_id, xml):
976
return self._real_repository.deserialise_inventory(revision_id, xml)
978
def reconcile(self, other=None, thorough=False):
980
return self._real_repository.reconcile(other=other, thorough=thorough)
982
def all_revision_ids(self):
984
return self._real_repository.all_revision_ids()
987
def get_deltas_for_revisions(self, revisions):
989
return self._real_repository.get_deltas_for_revisions(revisions)
992
def get_revision_delta(self, revision_id):
994
return self._real_repository.get_revision_delta(revision_id)
997
def revision_trees(self, revision_ids):
999
return self._real_repository.revision_trees(revision_ids)
1002
def get_revision_reconcile(self, revision_id):
1004
return self._real_repository.get_revision_reconcile(revision_id)
1007
def check(self, revision_ids=None):
1009
return self._real_repository.check(revision_ids=revision_ids)
1011
def copy_content_into(self, destination, revision_id=None):
1013
return self._real_repository.copy_content_into(
1014
destination, revision_id=revision_id)
1016
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1017
# get a tarball of the remote repository, and copy from that into the
1019
from bzrlib import osutils
1022
# TODO: Maybe a progress bar while streaming the tarball?
1023
note("Copying repository content as tarball...")
1024
tar_file = self._get_tarball('bz2')
1025
if tar_file is None:
1027
destination = to_bzrdir.create_repository()
1029
tar = tarfile.open('repository', fileobj=tar_file,
1031
tmpdir = tempfile.mkdtemp()
1033
_extract_tar(tar, tmpdir)
1034
tmp_bzrdir = BzrDir.open(tmpdir)
1035
tmp_repo = tmp_bzrdir.open_repository()
1036
tmp_repo.copy_content_into(destination, revision_id)
1038
osutils.rmtree(tmpdir)
1042
# TODO: Suggestion from john: using external tar is much faster than
1043
# python's tarfile library, but it may not work on windows.
1047
"""Compress the data within the repository.
1049
This is not currently implemented within the smart server.
1052
return self._real_repository.pack()
1054
def set_make_working_trees(self, new_value):
1056
self._real_repository.set_make_working_trees(new_value)
1059
def sign_revision(self, revision_id, gpg_strategy):
1061
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1064
def get_revisions(self, revision_ids):
1066
return self._real_repository.get_revisions(revision_ids)
1068
def supports_rich_root(self):
1070
return self._real_repository.supports_rich_root()
1072
def iter_reverse_revision_history(self, revision_id):
1074
return self._real_repository.iter_reverse_revision_history(revision_id)
1077
def _serializer(self):
1079
return self._real_repository._serializer
1081
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1083
return self._real_repository.store_revision_signature(
1084
gpg_strategy, plaintext, revision_id)
1086
def add_signature_text(self, revision_id, signature):
1088
return self._real_repository.add_signature_text(revision_id, signature)
1090
def has_signature_for_revision_id(self, revision_id):
1092
return self._real_repository.has_signature_for_revision_id(revision_id)
1094
def get_data_stream_for_search(self, search):
1095
medium = self._client._medium
1096
if not medium._remote_is_at_least_1_2:
1098
return self._real_repository.get_data_stream_for_search(search)
1099
REQUEST_NAME = 'Repository.stream_revisions_chunked'
1100
path = self.bzrdir._path_for_remote_call(self._client)
1101
body = self._serialise_search_recipe(search.get_recipe())
1103
result = self._client.call_with_body_bytes_expecting_body(
1104
REQUEST_NAME, (path,), body)
1105
response, protocol = result
1106
except errors.UnknownSmartMethod:
1107
# Server does not support this method, so fall back to VFS.
1108
# Worse, we have to force a disconnection, because the server now
1109
# doesn't realise it has a body on the wire to consume, so the
1110
# only way to recover is to abandon the connection.
1112
'Server is too old for streaming pull, reconnecting. '
1113
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1115
# To avoid having to disconnect repeatedly, we keep track of the
1116
# fact the server doesn't understand this remote method.
1117
medium._remote_is_at_least_1_2 = False
1119
return self._real_repository.get_data_stream_for_search(search)
1121
if response == ('ok',):
1122
return self._deserialise_stream(protocol)
1123
if response == ('NoSuchRevision', ):
1124
# We cannot easily identify the revision that is missing in this
1125
# situation without doing much more network IO. For now, bail.
1126
raise NoSuchRevision(self, "unknown")
1128
raise errors.UnexpectedSmartServerResponse(response)
1130
def _deserialise_stream(self, protocol):
1131
stream = protocol.read_streamed_body()
1132
container_parser = ContainerPushParser()
1133
for bytes in stream:
1134
container_parser.accept_bytes(bytes)
1135
records = container_parser.read_pending_records()
1136
for record_names, record_bytes in records:
1137
if len(record_names) != 1:
1138
# These records should have only one name, and that name
1139
# should be a one-element tuple.
1140
raise errors.SmartProtocolError(
1141
'Repository data stream had invalid record name %r'
1143
name_tuple = record_names[0]
1144
yield name_tuple, record_bytes
1146
def insert_data_stream(self, stream):
1148
self._real_repository.insert_data_stream(stream)
1150
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1152
return self._real_repository.item_keys_introduced_by(revision_ids,
1153
_files_pb=_files_pb)
1155
def revision_graph_can_have_wrong_parents(self):
1156
# The answer depends on the remote repo format.
1158
return self._real_repository.revision_graph_can_have_wrong_parents()
1160
def _find_inconsistent_revision_parents(self):
1162
return self._real_repository._find_inconsistent_revision_parents()
1164
def _check_for_inconsistent_revision_parents(self):
1166
return self._real_repository._check_for_inconsistent_revision_parents()
1168
def _make_parents_provider(self):
1171
def _serialise_search_recipe(self, recipe):
1172
"""Serialise a graph search recipe.
1174
:param recipe: A search recipe (start, stop, count).
1175
:return: Serialised bytes.
1177
start_keys = ' '.join(recipe[0])
1178
stop_keys = ' '.join(recipe[1])
1179
count = str(recipe[2])
1180
return '\n'.join((start_keys, stop_keys, count))
1183
class RemoteBranchLockableFiles(LockableFiles):
1184
"""A 'LockableFiles' implementation that talks to a smart server.
1186
This is not a public interface class.
1189
def __init__(self, bzrdir, _client):
1190
self.bzrdir = bzrdir
1191
self._client = _client
1192
self._need_find_modes = True
1193
LockableFiles.__init__(
1194
self, bzrdir.get_branch_transport(None),
1195
'lock', lockdir.LockDir)
1197
def _find_modes(self):
1198
# RemoteBranches don't let the client set the mode of control files.
1199
self._dir_mode = None
1200
self._file_mode = None
1203
class RemoteBranchFormat(branch.BranchFormat):
1205
def __eq__(self, other):
1206
return (isinstance(other, RemoteBranchFormat) and
1207
self.__dict__ == other.__dict__)
1209
def get_format_description(self):
1210
return 'Remote BZR Branch'
1212
def get_format_string(self):
1213
return 'Remote BZR Branch'
1215
def open(self, a_bzrdir):
1216
return a_bzrdir.open_branch()
1218
def initialize(self, a_bzrdir):
1219
return a_bzrdir.create_branch()
1221
def supports_tags(self):
1222
# Remote branches might support tags, but we won't know until we
1223
# access the real remote branch.
1227
class RemoteBranch(branch.Branch):
1228
"""Branch stored on a server accessed by HPSS RPC.
1230
At the moment most operations are mapped down to simple file operations.
1233
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1235
"""Create a RemoteBranch instance.
1237
:param real_branch: An optional local implementation of the branch
1238
format, usually accessing the data via the VFS.
1239
:param _client: Private parameter for testing.
1241
# We intentionally don't call the parent class's __init__, because it
1242
# will try to assign to self.tags, which is a property in this subclass.
1243
# And the parent's __init__ doesn't do much anyway.
1244
self._revision_id_to_revno_cache = None
1245
self._revision_history_cache = None
1246
self.bzrdir = remote_bzrdir
1247
if _client is not None:
1248
self._client = _client
1250
self._client = remote_bzrdir._client
1251
self.repository = remote_repository
1252
if real_branch is not None:
1253
self._real_branch = real_branch
1254
# Give the remote repository the matching real repo.
1255
real_repo = self._real_branch.repository
1256
if isinstance(real_repo, RemoteRepository):
1257
real_repo._ensure_real()
1258
real_repo = real_repo._real_repository
1259
self.repository._set_real_repository(real_repo)
1260
# Give the branch the remote repository to let fast-pathing happen.
1261
self._real_branch.repository = self.repository
1263
self._real_branch = None
1264
# Fill out expected attributes of branch for bzrlib api users.
1265
self._format = RemoteBranchFormat()
1266
self.base = self.bzrdir.root_transport.base
1267
self._control_files = None
1268
self._lock_mode = None
1269
self._lock_token = None
1270
self._repo_lock_token = None
1271
self._lock_count = 0
1272
self._leave_lock = False
1274
def _get_real_transport(self):
1275
# if we try vfs access, return the real branch's vfs transport
1277
return self._real_branch._transport
1279
_transport = property(_get_real_transport)
1282
return "%s(%s)" % (self.__class__.__name__, self.base)
1286
def _ensure_real(self):
1287
"""Ensure that there is a _real_branch set.
1289
Used before calls to self._real_branch.
1291
if self._real_branch is None:
1292
if not vfs.vfs_enabled():
1293
raise AssertionError('smart server vfs must be enabled '
1294
'to use vfs implementation')
1295
self.bzrdir._ensure_real()
1296
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1297
# Give the remote repository the matching real repo.
1298
real_repo = self._real_branch.repository
1299
if isinstance(real_repo, RemoteRepository):
1300
real_repo._ensure_real()
1301
real_repo = real_repo._real_repository
1302
self.repository._set_real_repository(real_repo)
1303
# Give the branch the remote repository to let fast-pathing happen.
1304
self._real_branch.repository = self.repository
1305
# XXX: deal with _lock_mode == 'w'
1306
if self._lock_mode == 'r':
1307
self._real_branch.lock_read()
1310
def control_files(self):
1311
# Defer actually creating RemoteBranchLockableFiles until its needed,
1312
# because it triggers an _ensure_real that we otherwise might not need.
1313
if self._control_files is None:
1314
self._control_files = RemoteBranchLockableFiles(
1315
self.bzrdir, self._client)
1316
return self._control_files
1318
def _get_checkout_format(self):
1320
return self._real_branch._get_checkout_format()
1322
def get_physical_lock_status(self):
1323
"""See Branch.get_physical_lock_status()."""
1324
# should be an API call to the server, as branches must be lockable.
1326
return self._real_branch.get_physical_lock_status()
1328
def get_stacked_on(self):
1329
"""Get the URL this branch is stacked against.
1331
:raises NotStacked: If the branch is not stacked.
1332
:raises UnstackableBranchFormat: If the branch does not support
1334
:raises UnstackableRepositoryFormat: If the repository does not support
1338
return self._real_branch.get_stacked_on()
1340
def lock_read(self):
1341
if not self._lock_mode:
1342
self._lock_mode = 'r'
1343
self._lock_count = 1
1344
if self._real_branch is not None:
1345
self._real_branch.lock_read()
1347
self._lock_count += 1
1349
def _remote_lock_write(self, token):
1351
branch_token = repo_token = ''
1353
branch_token = token
1354
repo_token = self.repository.lock_write()
1355
self.repository.unlock()
1356
path = self.bzrdir._path_for_remote_call(self._client)
1358
response = self._client.call(
1359
'Branch.lock_write', path, branch_token, repo_token or '')
1360
except errors.ErrorFromSmartServer, err:
1361
if err.error_verb == 'LockContention':
1362
raise errors.LockContention('(remote lock)')
1363
elif err.error_verb == 'TokenMismatch':
1364
raise errors.TokenMismatch(token, '(remote token)')
1365
elif err.error_verb == 'UnlockableTransport':
1366
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1367
elif err.error_verb == 'ReadOnlyError':
1368
raise errors.ReadOnlyError(self)
1369
elif err.error_verb == 'LockFailed':
1370
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1372
if response[0] != 'ok':
1373
raise errors.UnexpectedSmartServerResponse(response)
1374
ok, branch_token, repo_token = response
1375
return branch_token, repo_token
1377
def lock_write(self, token=None):
1378
if not self._lock_mode:
1379
remote_tokens = self._remote_lock_write(token)
1380
self._lock_token, self._repo_lock_token = remote_tokens
1381
if not self._lock_token:
1382
raise SmartProtocolError('Remote server did not return a token!')
1383
# TODO: We really, really, really don't want to call _ensure_real
1384
# here, but it's the easiest way to ensure coherency between the
1385
# state of the RemoteBranch and RemoteRepository objects and the
1386
# physical locks. If we don't materialise the real objects here,
1387
# then getting everything in the right state later is complex, so
1388
# for now we just do it the lazy way.
1389
# -- Andrew Bennetts, 2007-02-22.
1391
if self._real_branch is not None:
1392
self._real_branch.repository.lock_write(
1393
token=self._repo_lock_token)
1395
self._real_branch.lock_write(token=self._lock_token)
1397
self._real_branch.repository.unlock()
1398
if token is not None:
1399
self._leave_lock = True
1401
# XXX: this case seems to be unreachable; token cannot be None.
1402
self._leave_lock = False
1403
self._lock_mode = 'w'
1404
self._lock_count = 1
1405
elif self._lock_mode == 'r':
1406
raise errors.ReadOnlyTransaction
1408
if token is not None:
1409
# A token was given to lock_write, and we're relocking, so check
1410
# that the given token actually matches the one we already have.
1411
if token != self._lock_token:
1412
raise errors.TokenMismatch(token, self._lock_token)
1413
self._lock_count += 1
1414
return self._lock_token or None
1416
def _unlock(self, branch_token, repo_token):
1417
path = self.bzrdir._path_for_remote_call(self._client)
1419
response = self._client.call('Branch.unlock', path, branch_token,
1421
except errors.ErrorFromSmartServer, err:
1422
if err.error_verb == 'TokenMismatch':
1423
raise errors.TokenMismatch(
1424
str((branch_token, repo_token)), '(remote tokens)')
1426
if response == ('ok',):
1428
raise errors.UnexpectedSmartServerResponse(response)
1431
self._lock_count -= 1
1432
if not self._lock_count:
1433
self._clear_cached_state()
1434
mode = self._lock_mode
1435
self._lock_mode = None
1436
if self._real_branch is not None:
1437
if (not self._leave_lock and mode == 'w' and
1438
self._repo_lock_token):
1439
# If this RemoteBranch will remove the physical lock for the
1440
# repository, make sure the _real_branch doesn't do it
1441
# first. (Because the _real_branch's repository is set to
1442
# be the RemoteRepository.)
1443
self._real_branch.repository.leave_lock_in_place()
1444
self._real_branch.unlock()
1446
# Only write-locked branched need to make a remote method call
1447
# to perfom the unlock.
1449
if not self._lock_token:
1450
raise AssertionError('Locked, but no token!')
1451
branch_token = self._lock_token
1452
repo_token = self._repo_lock_token
1453
self._lock_token = None
1454
self._repo_lock_token = None
1455
if not self._leave_lock:
1456
self._unlock(branch_token, repo_token)
1458
def break_lock(self):
1460
return self._real_branch.break_lock()
1462
def leave_lock_in_place(self):
1463
if not self._lock_token:
1464
raise NotImplementedError(self.leave_lock_in_place)
1465
self._leave_lock = True
1467
def dont_leave_lock_in_place(self):
1468
if not self._lock_token:
1469
raise NotImplementedError(self.dont_leave_lock_in_place)
1470
self._leave_lock = False
1472
def last_revision_info(self):
1473
"""See Branch.last_revision_info()."""
1474
path = self.bzrdir._path_for_remote_call(self._client)
1475
response = self._client.call('Branch.last_revision_info', path)
1476
if response[0] != 'ok':
1477
raise SmartProtocolError('unexpected response code %s' % (response,))
1478
revno = int(response[1])
1479
last_revision = response[2]
1480
return (revno, last_revision)
1482
def _gen_revision_history(self):
1483
"""See Branch._gen_revision_history()."""
1484
path = self.bzrdir._path_for_remote_call(self._client)
1485
response_tuple, response_handler = self._client.call_expecting_body(
1486
'Branch.revision_history', path)
1487
if response_tuple[0] != 'ok':
1488
raise UnexpectedSmartServerResponse(response_tuple)
1489
result = response_handler.read_body_bytes().split('\x00')
1495
def set_revision_history(self, rev_history):
1496
# Send just the tip revision of the history; the server will generate
1497
# the full history from that. If the revision doesn't exist in this
1498
# branch, NoSuchRevision will be raised.
1499
path = self.bzrdir._path_for_remote_call(self._client)
1500
if rev_history == []:
1503
rev_id = rev_history[-1]
1504
self._clear_cached_state()
1506
response = self._client.call('Branch.set_last_revision',
1507
path, self._lock_token, self._repo_lock_token, rev_id)
1508
except errors.ErrorFromSmartServer, err:
1509
if err.error_verb == 'NoSuchRevision':
1510
raise NoSuchRevision(self, rev_id)
1512
if response != ('ok',):
1513
raise errors.UnexpectedSmartServerResponse(response)
1514
self._cache_revision_history(rev_history)
1516
def get_parent(self):
1518
return self._real_branch.get_parent()
1520
def set_parent(self, url):
1522
return self._real_branch.set_parent(url)
1524
def set_stacked_on(self, stacked_location):
1525
"""Set the URL this branch is stacked against.
1527
:raises UnstackableBranchFormat: If the branch does not support
1529
:raises UnstackableRepositoryFormat: If the repository does not support
1533
return self._real_branch.set_stacked_on(stacked_location)
1535
def sprout(self, to_bzrdir, revision_id=None):
1536
# Like Branch.sprout, except that it sprouts a branch in the default
1537
# format, because RemoteBranches can't be created at arbitrary URLs.
1538
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1539
# to_bzrdir.create_branch...
1541
result = self._real_branch._format.initialize(to_bzrdir)
1542
self.copy_content_into(result, revision_id=revision_id)
1543
result.set_parent(self.bzrdir.root_transport.base)
1547
def pull(self, source, overwrite=False, stop_revision=None,
1549
# FIXME: This asks the real branch to run the hooks, which means
1550
# they're called with the wrong target branch parameter.
1551
# The test suite specifically allows this at present but it should be
1552
# fixed. It should get a _override_hook_target branch,
1553
# as push does. -- mbp 20070405
1555
return self._real_branch.pull(
1556
source, overwrite=overwrite, stop_revision=stop_revision,
1560
def push(self, target, overwrite=False, stop_revision=None):
1562
return self._real_branch.push(
1563
target, overwrite=overwrite, stop_revision=stop_revision,
1564
_override_hook_source_branch=self)
1566
def is_locked(self):
1567
return self._lock_count >= 1
1570
def set_last_revision_info(self, revno, revision_id):
1571
revision_id = ensure_null(revision_id)
1572
path = self.bzrdir._path_for_remote_call(self._client)
1574
response = self._client.call('Branch.set_last_revision_info',
1575
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1576
except errors.UnknownSmartMethod:
1578
self._clear_cached_state()
1579
return self._real_branch.set_last_revision_info(revno, revision_id)
1580
except errors.ErrorFromSmartServer, err:
1581
if err.error_verb == 'NoSuchRevision':
1582
raise NoSuchRevision(self, err.error_args[0])
1584
if response == ('ok',):
1585
self._clear_cached_state()
1587
raise errors.UnexpectedSmartServerResponse(response)
1589
def generate_revision_history(self, revision_id, last_rev=None,
1592
return self._real_branch.generate_revision_history(
1593
revision_id, last_rev=last_rev, other_branch=other_branch)
1598
return self._real_branch.tags
1600
def set_push_location(self, location):
1602
return self._real_branch.set_push_location(location)
1604
def update_revisions(self, other, stop_revision=None, overwrite=False,
1607
return self._real_branch.update_revisions(
1608
other, stop_revision=stop_revision, overwrite=overwrite,
1612
def _extract_tar(tar, to_dir):
1613
"""Extract all the contents of a tarfile object.
1615
A replacement for extractall, which is not present in python2.4
1618
tar.extract(tarinfo, to_dir)