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 cloning_metadir(self):
93
return self._real_bzrdir.cloning_metadir()
95
def create_repository(self, shared=False):
97
self._real_bzrdir.create_repository(shared=shared)
98
return self.open_repository()
100
def destroy_repository(self):
101
"""See BzrDir.destroy_repository"""
103
self._real_bzrdir.destroy_repository()
105
def create_branch(self):
107
real_branch = self._real_bzrdir.create_branch()
108
return RemoteBranch(self, self.find_repository(), real_branch)
110
def destroy_branch(self):
111
"""See BzrDir.destroy_branch"""
113
self._real_bzrdir.destroy_branch()
115
def create_workingtree(self, revision_id=None, from_branch=None):
116
raise errors.NotLocalUrl(self.transport.base)
118
def find_branch_format(self):
119
"""Find the branch 'format' for this bzrdir.
121
This might be a synthetic object for e.g. RemoteBranch and SVN.
123
b = self.open_branch()
126
def get_branch_reference(self):
127
"""See BzrDir.get_branch_reference()."""
128
path = self._path_for_remote_call(self._client)
130
response = self._client.call('BzrDir.open_branch', path)
131
except errors.ErrorFromSmartServer, err:
132
if err.error_tuple == ('nobranch',):
133
raise errors.NotBranchError(path=self.root_transport.base)
135
if response[0] == 'ok':
136
if response[1] == '':
137
# branch at this location.
140
# a branch reference, use the existing BranchReference logic.
143
raise errors.UnexpectedSmartServerResponse(response)
145
def _get_tree_branch(self):
146
"""See BzrDir._get_tree_branch()."""
147
return None, self.open_branch()
149
def open_branch(self, _unsupported=False):
151
raise NotImplementedError('unsupported flag support not implemented yet.')
152
reference_url = self.get_branch_reference()
153
if reference_url is None:
154
# branch at this location.
155
return RemoteBranch(self, self.find_repository())
157
# a branch reference, use the existing BranchReference logic.
158
format = BranchReferenceFormat()
159
return format.open(self, _found=True, location=reference_url)
161
def open_repository(self):
162
path = self._path_for_remote_call(self._client)
163
verb = 'BzrDir.find_repositoryV2'
166
response = self._client.call(verb, path)
167
except errors.UnknownSmartMethod:
168
verb = 'BzrDir.find_repository'
169
response = self._client.call(verb, path)
170
except errors.ErrorFromSmartServer, err:
171
if err.error_verb == 'norepository':
172
raise errors.NoRepositoryPresent(self)
174
if response[0] != 'ok':
175
raise errors.UnexpectedSmartServerResponse(response)
176
if verb == 'BzrDir.find_repository':
177
# servers that don't support the V2 method don't support external
179
response = response + ('no', )
180
if not (len(response) == 5):
181
raise SmartProtocolError('incorrect response length %s' % (response,))
182
if response[1] == '':
183
format = RemoteRepositoryFormat()
184
format.rich_root_data = (response[2] == 'yes')
185
format.supports_tree_reference = (response[3] == 'yes')
186
# No wire format to check this yet.
187
format.supports_external_lookups = (response[4] == 'yes')
188
# Used to support creating a real format instance when needed.
189
format._creating_bzrdir = self
190
return RemoteRepository(self, format)
192
raise errors.NoRepositoryPresent(self)
194
def open_workingtree(self, recommend_upgrade=True):
196
if self._real_bzrdir.has_workingtree():
197
raise errors.NotLocalUrl(self.root_transport)
199
raise errors.NoWorkingTree(self.root_transport.base)
201
def _path_for_remote_call(self, client):
202
"""Return the path to be used for this bzrdir in a remote call."""
203
return client.remote_path_from_transport(self.root_transport)
205
def get_branch_transport(self, branch_format):
207
return self._real_bzrdir.get_branch_transport(branch_format)
209
def get_repository_transport(self, repository_format):
211
return self._real_bzrdir.get_repository_transport(repository_format)
213
def get_workingtree_transport(self, workingtree_format):
215
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
217
def can_convert_format(self):
218
"""Upgrading of remote bzrdirs is not supported yet."""
221
def needs_format_conversion(self, format=None):
222
"""Upgrading of remote bzrdirs is not supported yet."""
225
def clone(self, url, revision_id=None, force_new_repo=False,
226
preserve_stacking=False):
228
return self._real_bzrdir.clone(url, revision_id=revision_id,
229
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
232
class RemoteRepositoryFormat(repository.RepositoryFormat):
233
"""Format for repositories accessed over a _SmartClient.
235
Instances of this repository are represented by RemoteRepository
238
The RemoteRepositoryFormat is parameterized during construction
239
to reflect the capabilities of the real, remote format. Specifically
240
the attributes rich_root_data and supports_tree_reference are set
241
on a per instance basis, and are not set (and should not be) at
245
_matchingbzrdir = RemoteBzrDirFormat()
247
def initialize(self, a_bzrdir, shared=False):
248
if not isinstance(a_bzrdir, RemoteBzrDir):
249
prior_repo = self._creating_bzrdir.open_repository()
250
prior_repo._ensure_real()
251
return prior_repo._real_repository._format.initialize(
252
a_bzrdir, shared=shared)
253
return a_bzrdir.create_repository(shared=shared)
255
def open(self, a_bzrdir):
256
if not isinstance(a_bzrdir, RemoteBzrDir):
257
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
258
return a_bzrdir.open_repository()
260
def get_format_description(self):
261
return 'bzr remote repository'
263
def __eq__(self, other):
264
return self.__class__ == other.__class__
266
def check_conversion_target(self, target_format):
267
if self.rich_root_data and not target_format.rich_root_data:
268
raise errors.BadConversionTarget(
269
'Does not support rich root data.', target_format)
270
if (self.supports_tree_reference and
271
not getattr(target_format, 'supports_tree_reference', False)):
272
raise errors.BadConversionTarget(
273
'Does not support nested trees', target_format)
276
class RemoteRepository(object):
277
"""Repository accessed over rpc.
279
For the moment most operations are performed using local transport-backed
283
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
284
"""Create a RemoteRepository instance.
286
:param remote_bzrdir: The bzrdir hosting this repository.
287
:param format: The RemoteFormat object to use.
288
:param real_repository: If not None, a local implementation of the
289
repository logic for the repository, usually accessing the data
291
:param _client: Private testing parameter - override the smart client
292
to be used by the repository.
295
self._real_repository = real_repository
297
self._real_repository = None
298
self.bzrdir = remote_bzrdir
300
self._client = remote_bzrdir._client
302
self._client = _client
303
self._format = format
304
self._lock_mode = None
305
self._lock_token = None
307
self._leave_lock = False
308
# A cache of looked up revision parent data; reset at unlock time.
309
self._parents_map = None
310
if 'hpss' in debug.debug_flags:
311
self._requested_parents = None
313
# These depend on the actual remote format, so force them off for
314
# maximum compatibility. XXX: In future these should depend on the
315
# remote repository instance, but this is irrelevant until we perform
316
# reconcile via an RPC call.
317
self._reconcile_does_inventory_gc = False
318
self._reconcile_fixes_text_parents = False
319
self._reconcile_backsup_inventory = False
320
self.base = self.bzrdir.transport.base
321
# Additional places to query for data.
322
self._fallback_repositories = []
325
return "%s(%s)" % (self.__class__.__name__, self.base)
329
def abort_write_group(self):
330
"""Complete a write group on the decorated repository.
332
Smart methods peform operations in a single step so this api
333
is not really applicable except as a compatibility thunk
334
for older plugins that don't use e.g. the CommitBuilder
338
return self._real_repository.abort_write_group()
340
def commit_write_group(self):
341
"""Complete a write group on the decorated repository.
343
Smart methods peform operations in a single step so this api
344
is not really applicable except as a compatibility thunk
345
for older plugins that don't use e.g. the CommitBuilder
349
return self._real_repository.commit_write_group()
351
def _ensure_real(self):
352
"""Ensure that there is a _real_repository set.
354
Used before calls to self._real_repository.
356
if not self._real_repository:
357
self.bzrdir._ensure_real()
358
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
359
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
361
def find_text_key_references(self):
362
"""Find the text key references within the repository.
364
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
365
revision_ids. Each altered file-ids has the exact revision_ids that
366
altered it listed explicitly.
367
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
368
to whether they were referred to by the inventory of the
369
revision_id that they contain. The inventory texts from all present
370
revision ids are assessed to generate this report.
373
return self._real_repository.find_text_key_references()
375
def _generate_text_key_index(self):
376
"""Generate a new text key index for the repository.
378
This is an expensive function that will take considerable time to run.
380
:return: A dict mapping (file_id, revision_id) tuples to a list of
381
parents, also (file_id, revision_id) tuples.
384
return self._real_repository._generate_text_key_index()
386
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
387
def get_revision_graph(self, revision_id=None):
388
"""See Repository.get_revision_graph()."""
389
return self._get_revision_graph(revision_id)
391
def _get_revision_graph(self, revision_id):
392
"""Private method for using with old (< 1.2) servers to fallback."""
393
if revision_id is None:
395
elif revision.is_null(revision_id):
398
path = self.bzrdir._path_for_remote_call(self._client)
400
response = self._client.call_expecting_body(
401
'Repository.get_revision_graph', path, revision_id)
402
except errors.ErrorFromSmartServer, err:
403
if err.error_verb == 'nosuchrevision':
404
raise NoSuchRevision(self, revision_id)
406
response_tuple, response_handler = response
407
if response_tuple[0] != 'ok':
408
raise errors.UnexpectedSmartServerResponse(response_tuple)
409
coded = response_handler.read_body_bytes()
411
# no revisions in this repository!
413
lines = coded.split('\n')
416
d = tuple(line.split())
417
revision_graph[d[0]] = d[1:]
419
return revision_graph
421
def has_revision(self, revision_id):
422
"""See Repository.has_revision()."""
423
if revision_id == NULL_REVISION:
424
# The null revision is always present.
426
path = self.bzrdir._path_for_remote_call(self._client)
427
response = self._client.call(
428
'Repository.has_revision', path, revision_id)
429
if response[0] not in ('yes', 'no'):
430
raise errors.UnexpectedSmartServerResponse(response)
431
return response[0] == 'yes'
433
def has_revisions(self, revision_ids):
434
"""See Repository.has_revisions()."""
436
for revision_id in revision_ids:
437
if self.has_revision(revision_id):
438
result.add(revision_id)
441
def has_same_location(self, other):
442
return (self.__class__ == other.__class__ and
443
self.bzrdir.transport.base == other.bzrdir.transport.base)
445
def get_graph(self, other_repository=None):
446
"""Return the graph for this repository format"""
447
parents_provider = self
448
if (other_repository is not None and
449
other_repository.bzrdir.transport.base !=
450
self.bzrdir.transport.base):
451
parents_provider = graph._StackedParentsProvider(
452
[parents_provider, other_repository._make_parents_provider()])
453
return graph.Graph(parents_provider)
455
def gather_stats(self, revid=None, committers=None):
456
"""See Repository.gather_stats()."""
457
path = self.bzrdir._path_for_remote_call(self._client)
458
# revid can be None to indicate no revisions, not just NULL_REVISION
459
if revid is None or revision.is_null(revid):
463
if committers is None or not committers:
464
fmt_committers = 'no'
466
fmt_committers = 'yes'
467
response_tuple, response_handler = self._client.call_expecting_body(
468
'Repository.gather_stats', path, fmt_revid, fmt_committers)
469
if response_tuple[0] != 'ok':
470
raise errors.UnexpectedSmartServerResponse(response_tuple)
472
body = response_handler.read_body_bytes()
474
for line in body.split('\n'):
477
key, val_text = line.split(':')
478
if key in ('revisions', 'size', 'committers'):
479
result[key] = int(val_text)
480
elif key in ('firstrev', 'latestrev'):
481
values = val_text.split(' ')[1:]
482
result[key] = (float(values[0]), long(values[1]))
486
def find_branches(self, using=False):
487
"""See Repository.find_branches()."""
488
# should be an API call to the server.
490
return self._real_repository.find_branches(using=using)
492
def get_physical_lock_status(self):
493
"""See Repository.get_physical_lock_status()."""
494
# should be an API call to the server.
496
return self._real_repository.get_physical_lock_status()
498
def is_in_write_group(self):
499
"""Return True if there is an open write group.
501
write groups are only applicable locally for the smart server..
503
if self._real_repository:
504
return self._real_repository.is_in_write_group()
507
return self._lock_count >= 1
510
"""See Repository.is_shared()."""
511
path = self.bzrdir._path_for_remote_call(self._client)
512
response = self._client.call('Repository.is_shared', path)
513
if response[0] not in ('yes', 'no'):
514
raise SmartProtocolError('unexpected response code %s' % (response,))
515
return response[0] == 'yes'
517
def is_write_locked(self):
518
return self._lock_mode == 'w'
521
# wrong eventually - want a local lock cache context
522
if not self._lock_mode:
523
self._lock_mode = 'r'
525
self._parents_map = {}
526
if 'hpss' in debug.debug_flags:
527
self._requested_parents = set()
528
if self._real_repository is not None:
529
self._real_repository.lock_read()
531
self._lock_count += 1
533
def _remote_lock_write(self, token):
534
path = self.bzrdir._path_for_remote_call(self._client)
538
response = self._client.call('Repository.lock_write', path, token)
539
except errors.ErrorFromSmartServer, err:
540
if err.error_verb == 'LockContention':
541
raise errors.LockContention('(remote lock)')
542
elif err.error_verb == 'UnlockableTransport':
543
raise errors.UnlockableTransport(self.bzrdir.root_transport)
544
elif err.error_verb == 'LockFailed':
545
raise errors.LockFailed(err.error_args[0], err.error_args[1])
548
if response[0] == 'ok':
552
raise errors.UnexpectedSmartServerResponse(response)
554
def lock_write(self, token=None):
555
if not self._lock_mode:
556
self._lock_token = self._remote_lock_write(token)
557
# if self._lock_token is None, then this is something like packs or
558
# svn where we don't get to lock the repo, or a weave style repository
559
# where we cannot lock it over the wire and attempts to do so will
561
if self._real_repository is not None:
562
self._real_repository.lock_write(token=self._lock_token)
563
if token is not None:
564
self._leave_lock = True
566
self._leave_lock = False
567
self._lock_mode = 'w'
569
self._parents_map = {}
570
if 'hpss' in debug.debug_flags:
571
self._requested_parents = set()
572
elif self._lock_mode == 'r':
573
raise errors.ReadOnlyError(self)
575
self._lock_count += 1
576
return self._lock_token or None
578
def leave_lock_in_place(self):
579
if not self._lock_token:
580
raise NotImplementedError(self.leave_lock_in_place)
581
self._leave_lock = True
583
def dont_leave_lock_in_place(self):
584
if not self._lock_token:
585
raise NotImplementedError(self.dont_leave_lock_in_place)
586
self._leave_lock = False
588
def _set_real_repository(self, repository):
589
"""Set the _real_repository for this repository.
591
:param repository: The repository to fallback to for non-hpss
592
implemented operations.
594
if isinstance(repository, RemoteRepository):
595
raise AssertionError()
596
self._real_repository = repository
597
if self._lock_mode == 'w':
598
# if we are already locked, the real repository must be able to
599
# acquire the lock with our token.
600
self._real_repository.lock_write(self._lock_token)
601
elif self._lock_mode == 'r':
602
self._real_repository.lock_read()
604
def start_write_group(self):
605
"""Start a write group on the decorated repository.
607
Smart methods peform operations in a single step so this api
608
is not really applicable except as a compatibility thunk
609
for older plugins that don't use e.g. the CommitBuilder
613
return self._real_repository.start_write_group()
615
def _unlock(self, token):
616
path = self.bzrdir._path_for_remote_call(self._client)
618
# with no token the remote repository is not persistently locked.
621
response = self._client.call('Repository.unlock', path, token)
622
except errors.ErrorFromSmartServer, err:
623
if err.error_verb == 'TokenMismatch':
624
raise errors.TokenMismatch(token, '(remote token)')
626
if response == ('ok',):
629
raise errors.UnexpectedSmartServerResponse(response)
632
self._lock_count -= 1
633
if self._lock_count > 0:
635
self._parents_map = None
636
if 'hpss' in debug.debug_flags:
637
self._requested_parents = None
638
old_mode = self._lock_mode
639
self._lock_mode = None
641
# The real repository is responsible at present for raising an
642
# exception if it's in an unfinished write group. However, it
643
# normally will *not* actually remove the lock from disk - that's
644
# done by the server on receiving the Repository.unlock call.
645
# This is just to let the _real_repository stay up to date.
646
if self._real_repository is not None:
647
self._real_repository.unlock()
649
# The rpc-level lock should be released even if there was a
650
# problem releasing the vfs-based lock.
652
# Only write-locked repositories need to make a remote method
653
# call to perfom the unlock.
654
old_token = self._lock_token
655
self._lock_token = None
656
if not self._leave_lock:
657
self._unlock(old_token)
659
def break_lock(self):
660
# should hand off to the network
662
return self._real_repository.break_lock()
664
def _get_tarball(self, compression):
665
"""Return a TemporaryFile containing a repository tarball.
667
Returns None if the server does not support sending tarballs.
670
path = self.bzrdir._path_for_remote_call(self._client)
672
response, protocol = self._client.call_expecting_body(
673
'Repository.tarball', path, compression)
674
except errors.UnknownSmartMethod:
675
protocol.cancel_read_body()
677
if response[0] == 'ok':
678
# Extract the tarball and return it
679
t = tempfile.NamedTemporaryFile()
680
# TODO: rpc layer should read directly into it...
681
t.write(protocol.read_body_bytes())
684
raise errors.UnexpectedSmartServerResponse(response)
686
def sprout(self, to_bzrdir, revision_id=None):
687
# TODO: Option to control what format is created?
689
dest_repo = self._real_repository._format.initialize(to_bzrdir,
691
dest_repo.fetch(self, revision_id=revision_id)
694
### These methods are just thin shims to the VFS object for now.
696
def revision_tree(self, revision_id):
698
return self._real_repository.revision_tree(revision_id)
700
def get_serializer_format(self):
702
return self._real_repository.get_serializer_format()
704
def get_commit_builder(self, branch, parents, config, timestamp=None,
705
timezone=None, committer=None, revprops=None,
707
# FIXME: It ought to be possible to call this without immediately
708
# triggering _ensure_real. For now it's the easiest thing to do.
710
builder = self._real_repository.get_commit_builder(branch, parents,
711
config, timestamp=timestamp, timezone=timezone,
712
committer=committer, revprops=revprops, revision_id=revision_id)
715
def add_fallback_repository(self, repository):
716
"""Add a repository to use for looking up data not held locally.
718
:param repository: A repository.
720
if not self._format.supports_external_lookups:
721
raise errors.UnstackableRepositoryFormat(self._format, self.base)
722
# We need to accumulate additional repositories here, to pass them in
724
self._fallback_repositories.append(repository)
726
def add_inventory(self, revid, inv, parents):
728
return self._real_repository.add_inventory(revid, inv, parents)
730
def add_revision(self, rev_id, rev, inv=None, config=None):
732
return self._real_repository.add_revision(
733
rev_id, rev, inv=inv, config=config)
736
def get_inventory(self, revision_id):
738
return self._real_repository.get_inventory(revision_id)
740
def iter_inventories(self, revision_ids):
742
return self._real_repository.iter_inventories(revision_ids)
745
def get_revision(self, revision_id):
747
return self._real_repository.get_revision(revision_id)
749
def get_transaction(self):
751
return self._real_repository.get_transaction()
754
def clone(self, a_bzrdir, revision_id=None):
756
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
758
def make_working_trees(self):
759
"""See Repository.make_working_trees"""
761
return self._real_repository.make_working_trees()
763
def revision_ids_to_search_result(self, result_set):
764
"""Convert a set of revision ids to a graph SearchResult."""
765
result_parents = set()
766
for parents in self.get_graph().get_parent_map(
767
result_set).itervalues():
768
result_parents.update(parents)
769
included_keys = result_set.intersection(result_parents)
770
start_keys = result_set.difference(included_keys)
771
exclude_keys = result_parents.difference(result_set)
772
result = graph.SearchResult(start_keys, exclude_keys,
773
len(result_set), result_set)
777
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
778
"""Return the revision ids that other has that this does not.
780
These are returned in topological order.
782
revision_id: only return revision ids included by revision_id.
784
return repository.InterRepository.get(
785
other, self).search_missing_revision_ids(revision_id, find_ghosts)
787
def fetch(self, source, revision_id=None, pb=None):
788
if self.has_same_location(source):
789
# check that last_revision is in 'from' and then return a
791
if (revision_id is not None and
792
not revision.is_null(revision_id)):
793
self.get_revision(revision_id)
796
return self._real_repository.fetch(
797
source, revision_id=revision_id, pb=pb)
799
def create_bundle(self, target, base, fileobj, format=None):
801
self._real_repository.create_bundle(target, base, fileobj, format)
804
def get_ancestry(self, revision_id, topo_sorted=True):
806
return self._real_repository.get_ancestry(revision_id, topo_sorted)
808
def fileids_altered_by_revision_ids(self, revision_ids):
810
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
812
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
814
return self._real_repository._get_versioned_file_checker(
815
revisions, revision_versions_cache)
817
def iter_files_bytes(self, desired_files):
818
"""See Repository.iter_file_bytes.
821
return self._real_repository.iter_files_bytes(desired_files)
824
def _fetch_order(self):
825
"""Decorate the real repository for now.
827
In the long term getting this back from the remote repository as part
828
of open would be more efficient.
831
return self._real_repository._fetch_order
834
def _fetch_uses_deltas(self):
835
"""Decorate the real repository for now.
837
In the long term getting this back from the remote repository as part
838
of open would be more efficient.
841
return self._real_repository._fetch_uses_deltas
843
def get_parent_map(self, keys):
844
"""See bzrlib.Graph.get_parent_map()."""
845
# Hack to build up the caching logic.
846
ancestry = self._parents_map
848
# Repository is not locked, so there's no cache.
849
missing_revisions = set(keys)
852
missing_revisions = set(key for key in keys if key not in ancestry)
853
if missing_revisions:
854
parent_map = self._get_parent_map(missing_revisions)
855
if 'hpss' in debug.debug_flags:
856
mutter('retransmitted revisions: %d of %d',
857
len(set(ancestry).intersection(parent_map)),
859
ancestry.update(parent_map)
860
present_keys = [k for k in keys if k in ancestry]
861
if 'hpss' in debug.debug_flags:
862
if self._requested_parents is not None and len(ancestry) != 0:
863
self._requested_parents.update(present_keys)
864
mutter('Current RemoteRepository graph hit rate: %d%%',
865
100.0 * len(self._requested_parents) / len(ancestry))
866
return dict((k, ancestry[k]) for k in present_keys)
868
def _get_parent_map(self, keys):
869
"""Helper for get_parent_map that performs the RPC."""
870
medium = self._client._medium
871
if medium._is_remote_before((1, 2)):
872
# We already found out that the server can't understand
873
# Repository.get_parent_map requests, so just fetch the whole
875
# XXX: Note that this will issue a deprecation warning. This is ok
876
# :- its because we're working with a deprecated server anyway, and
877
# the user will almost certainly have seen a warning about the
878
# server version already.
879
rg = self.get_revision_graph()
880
# There is an api discrepency between get_parent_map and
881
# get_revision_graph. Specifically, a "key:()" pair in
882
# get_revision_graph just means a node has no parents. For
883
# "get_parent_map" it means the node is a ghost. So fix up the
884
# graph to correct this.
885
# https://bugs.launchpad.net/bzr/+bug/214894
886
# There is one other "bug" which is that ghosts in
887
# get_revision_graph() are not returned at all. But we won't worry
888
# about that for now.
889
for node_id, parent_ids in rg.iteritems():
891
rg[node_id] = (NULL_REVISION,)
892
rg[NULL_REVISION] = ()
897
raise ValueError('get_parent_map(None) is not valid')
898
if NULL_REVISION in keys:
899
keys.discard(NULL_REVISION)
900
found_parents = {NULL_REVISION:()}
905
# TODO(Needs analysis): We could assume that the keys being requested
906
# from get_parent_map are in a breadth first search, so typically they
907
# will all be depth N from some common parent, and we don't have to
908
# have the server iterate from the root parent, but rather from the
909
# keys we're searching; and just tell the server the keyspace we
910
# already have; but this may be more traffic again.
912
# Transform self._parents_map into a search request recipe.
913
# TODO: Manage this incrementally to avoid covering the same path
914
# repeatedly. (The server will have to on each request, but the less
915
# work done the better).
916
parents_map = self._parents_map
917
if parents_map is None:
918
# Repository is not locked, so there's no cache.
920
start_set = set(parents_map)
921
result_parents = set()
922
for parents in parents_map.itervalues():
923
result_parents.update(parents)
924
stop_keys = result_parents.difference(start_set)
925
included_keys = start_set.intersection(result_parents)
926
start_set.difference_update(included_keys)
927
recipe = (start_set, stop_keys, len(parents_map))
928
body = self._serialise_search_recipe(recipe)
929
path = self.bzrdir._path_for_remote_call(self._client)
931
if type(key) is not str:
933
"key %r not a plain string" % (key,))
934
verb = 'Repository.get_parent_map'
935
args = (path,) + tuple(keys)
937
response = self._client.call_with_body_bytes_expecting_body(
938
verb, args, self._serialise_search_recipe(recipe))
939
except errors.UnknownSmartMethod:
940
# Server does not support this method, so get the whole graph.
941
# Worse, we have to force a disconnection, because the server now
942
# doesn't realise it has a body on the wire to consume, so the
943
# only way to recover is to abandon the connection.
945
'Server is too old for fast get_parent_map, reconnecting. '
946
'(Upgrade the server to Bazaar 1.2 to avoid this)')
948
# To avoid having to disconnect repeatedly, we keep track of the
949
# fact the server doesn't understand remote methods added in 1.2.
950
medium._remember_remote_is_before((1, 2))
951
return self.get_revision_graph(None)
952
response_tuple, response_handler = response
953
if response_tuple[0] not in ['ok']:
954
response_handler.cancel_read_body()
955
raise errors.UnexpectedSmartServerResponse(response_tuple)
956
if response_tuple[0] == 'ok':
957
coded = bz2.decompress(response_handler.read_body_bytes())
961
lines = coded.split('\n')
964
d = tuple(line.split())
966
revision_graph[d[0]] = d[1:]
968
# No parents - so give the Graph result (NULL_REVISION,).
969
revision_graph[d[0]] = (NULL_REVISION,)
970
return revision_graph
973
def get_signature_text(self, revision_id):
975
return self._real_repository.get_signature_text(revision_id)
978
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
979
def get_revision_graph_with_ghosts(self, revision_ids=None):
981
return self._real_repository.get_revision_graph_with_ghosts(
982
revision_ids=revision_ids)
985
def get_inventory_xml(self, revision_id):
987
return self._real_repository.get_inventory_xml(revision_id)
989
def deserialise_inventory(self, revision_id, xml):
991
return self._real_repository.deserialise_inventory(revision_id, xml)
993
def reconcile(self, other=None, thorough=False):
995
return self._real_repository.reconcile(other=other, thorough=thorough)
997
def all_revision_ids(self):
999
return self._real_repository.all_revision_ids()
1002
def get_deltas_for_revisions(self, revisions):
1004
return self._real_repository.get_deltas_for_revisions(revisions)
1007
def get_revision_delta(self, revision_id):
1009
return self._real_repository.get_revision_delta(revision_id)
1012
def revision_trees(self, revision_ids):
1014
return self._real_repository.revision_trees(revision_ids)
1017
def get_revision_reconcile(self, revision_id):
1019
return self._real_repository.get_revision_reconcile(revision_id)
1022
def check(self, revision_ids=None):
1024
return self._real_repository.check(revision_ids=revision_ids)
1026
def copy_content_into(self, destination, revision_id=None):
1028
return self._real_repository.copy_content_into(
1029
destination, revision_id=revision_id)
1031
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1032
# get a tarball of the remote repository, and copy from that into the
1034
from bzrlib import osutils
1037
# TODO: Maybe a progress bar while streaming the tarball?
1038
note("Copying repository content as tarball...")
1039
tar_file = self._get_tarball('bz2')
1040
if tar_file is None:
1042
destination = to_bzrdir.create_repository()
1044
tar = tarfile.open('repository', fileobj=tar_file,
1046
tmpdir = tempfile.mkdtemp()
1048
_extract_tar(tar, tmpdir)
1049
tmp_bzrdir = BzrDir.open(tmpdir)
1050
tmp_repo = tmp_bzrdir.open_repository()
1051
tmp_repo.copy_content_into(destination, revision_id)
1053
osutils.rmtree(tmpdir)
1057
# TODO: Suggestion from john: using external tar is much faster than
1058
# python's tarfile library, but it may not work on windows.
1061
def inventories(self):
1062
"""Decorate the real repository for now.
1064
In the long term a full blown network facility is needed to
1065
avoid creating a real repository object locally.
1068
return self._real_repository.inventories
1072
"""Compress the data within the repository.
1074
This is not currently implemented within the smart server.
1077
return self._real_repository.pack()
1080
def revisions(self):
1081
"""Decorate the real repository for now.
1083
In the short term this should become a real object to intercept graph
1086
In the long term a full blown network facility is needed.
1089
return self._real_repository.revisions
1091
def set_make_working_trees(self, new_value):
1093
self._real_repository.set_make_working_trees(new_value)
1096
def signatures(self):
1097
"""Decorate the real repository for now.
1099
In the long term a full blown network facility is needed to avoid
1100
creating a real repository object locally.
1103
return self._real_repository.signatures
1106
def sign_revision(self, revision_id, gpg_strategy):
1108
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1112
"""Decorate the real repository for now.
1114
In the long term a full blown network facility is needed to avoid
1115
creating a real repository object locally.
1118
return self._real_repository.texts
1121
def get_revisions(self, revision_ids):
1123
return self._real_repository.get_revisions(revision_ids)
1125
def supports_rich_root(self):
1127
return self._real_repository.supports_rich_root()
1129
def iter_reverse_revision_history(self, revision_id):
1131
return self._real_repository.iter_reverse_revision_history(revision_id)
1134
def _serializer(self):
1136
return self._real_repository._serializer
1138
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1140
return self._real_repository.store_revision_signature(
1141
gpg_strategy, plaintext, revision_id)
1143
def add_signature_text(self, revision_id, signature):
1145
return self._real_repository.add_signature_text(revision_id, signature)
1147
def has_signature_for_revision_id(self, revision_id):
1149
return self._real_repository.has_signature_for_revision_id(revision_id)
1151
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1153
return self._real_repository.item_keys_introduced_by(revision_ids,
1154
_files_pb=_files_pb)
1156
def revision_graph_can_have_wrong_parents(self):
1157
# The answer depends on the remote repo format.
1159
return self._real_repository.revision_graph_can_have_wrong_parents()
1161
def _find_inconsistent_revision_parents(self):
1163
return self._real_repository._find_inconsistent_revision_parents()
1165
def _check_for_inconsistent_revision_parents(self):
1167
return self._real_repository._check_for_inconsistent_revision_parents()
1169
def _make_parents_provider(self):
1172
def _serialise_search_recipe(self, recipe):
1173
"""Serialise a graph search recipe.
1175
:param recipe: A search recipe (start, stop, count).
1176
:return: Serialised bytes.
1178
start_keys = ' '.join(recipe[0])
1179
stop_keys = ' '.join(recipe[1])
1180
count = str(recipe[2])
1181
return '\n'.join((start_keys, stop_keys, count))
1184
class RemoteBranchLockableFiles(LockableFiles):
1185
"""A 'LockableFiles' implementation that talks to a smart server.
1187
This is not a public interface class.
1190
def __init__(self, bzrdir, _client):
1191
self.bzrdir = bzrdir
1192
self._client = _client
1193
self._need_find_modes = True
1194
LockableFiles.__init__(
1195
self, bzrdir.get_branch_transport(None),
1196
'lock', lockdir.LockDir)
1198
def _find_modes(self):
1199
# RemoteBranches don't let the client set the mode of control files.
1200
self._dir_mode = None
1201
self._file_mode = None
1204
class RemoteBranchFormat(branch.BranchFormat):
1206
def __eq__(self, other):
1207
return (isinstance(other, RemoteBranchFormat) and
1208
self.__dict__ == other.__dict__)
1210
def get_format_description(self):
1211
return 'Remote BZR Branch'
1213
def get_format_string(self):
1214
return 'Remote BZR Branch'
1216
def open(self, a_bzrdir):
1217
return a_bzrdir.open_branch()
1219
def initialize(self, a_bzrdir):
1220
return a_bzrdir.create_branch()
1222
def supports_tags(self):
1223
# Remote branches might support tags, but we won't know until we
1224
# access the real remote branch.
1228
class RemoteBranch(branch.Branch):
1229
"""Branch stored on a server accessed by HPSS RPC.
1231
At the moment most operations are mapped down to simple file operations.
1234
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1236
"""Create a RemoteBranch instance.
1238
:param real_branch: An optional local implementation of the branch
1239
format, usually accessing the data via the VFS.
1240
:param _client: Private parameter for testing.
1242
# We intentionally don't call the parent class's __init__, because it
1243
# will try to assign to self.tags, which is a property in this subclass.
1244
# And the parent's __init__ doesn't do much anyway.
1245
self._revision_id_to_revno_cache = None
1246
self._revision_history_cache = None
1247
self._last_revision_info_cache = None
1248
self.bzrdir = remote_bzrdir
1249
if _client is not None:
1250
self._client = _client
1252
self._client = remote_bzrdir._client
1253
self.repository = remote_repository
1254
if real_branch is not None:
1255
self._real_branch = real_branch
1256
# Give the remote repository the matching real repo.
1257
real_repo = self._real_branch.repository
1258
if isinstance(real_repo, RemoteRepository):
1259
real_repo._ensure_real()
1260
real_repo = real_repo._real_repository
1261
self.repository._set_real_repository(real_repo)
1262
# Give the branch the remote repository to let fast-pathing happen.
1263
self._real_branch.repository = self.repository
1265
self._real_branch = None
1266
# Fill out expected attributes of branch for bzrlib api users.
1267
self._format = RemoteBranchFormat()
1268
self.base = self.bzrdir.root_transport.base
1269
self._control_files = None
1270
self._lock_mode = None
1271
self._lock_token = None
1272
self._repo_lock_token = None
1273
self._lock_count = 0
1274
self._leave_lock = False
1276
def _get_real_transport(self):
1277
# if we try vfs access, return the real branch's vfs transport
1279
return self._real_branch._transport
1281
_transport = property(_get_real_transport)
1284
return "%s(%s)" % (self.__class__.__name__, self.base)
1288
def _ensure_real(self):
1289
"""Ensure that there is a _real_branch set.
1291
Used before calls to self._real_branch.
1293
if self._real_branch is None:
1294
if not vfs.vfs_enabled():
1295
raise AssertionError('smart server vfs must be enabled '
1296
'to use vfs implementation')
1297
self.bzrdir._ensure_real()
1298
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1299
# Give the remote repository the matching real repo.
1300
real_repo = self._real_branch.repository
1301
if isinstance(real_repo, RemoteRepository):
1302
real_repo._ensure_real()
1303
real_repo = real_repo._real_repository
1304
self.repository._set_real_repository(real_repo)
1305
# Give the branch the remote repository to let fast-pathing happen.
1306
self._real_branch.repository = self.repository
1307
# XXX: deal with _lock_mode == 'w'
1308
if self._lock_mode == 'r':
1309
self._real_branch.lock_read()
1311
def _clear_cached_state(self):
1312
super(RemoteBranch, self)._clear_cached_state()
1313
if self._real_branch is not None:
1314
self._real_branch._clear_cached_state()
1316
def _clear_cached_state_of_remote_branch_only(self):
1317
"""Like _clear_cached_state, but doesn't clear the cache of
1320
This is useful when falling back to calling a method of
1321
self._real_branch that changes state. In that case the underlying
1322
branch changes, so we need to invalidate this RemoteBranch's cache of
1323
it. However, there's no need to invalidate the _real_branch's cache
1324
too, in fact doing so might harm performance.
1326
super(RemoteBranch, self)._clear_cached_state()
1329
def control_files(self):
1330
# Defer actually creating RemoteBranchLockableFiles until its needed,
1331
# because it triggers an _ensure_real that we otherwise might not need.
1332
if self._control_files is None:
1333
self._control_files = RemoteBranchLockableFiles(
1334
self.bzrdir, self._client)
1335
return self._control_files
1337
def _get_checkout_format(self):
1339
return self._real_branch._get_checkout_format()
1341
def get_physical_lock_status(self):
1342
"""See Branch.get_physical_lock_status()."""
1343
# should be an API call to the server, as branches must be lockable.
1345
return self._real_branch.get_physical_lock_status()
1347
def get_stacked_on_url(self):
1348
"""Get the URL this branch is stacked against.
1350
:raises NotStacked: If the branch is not stacked.
1351
:raises UnstackableBranchFormat: If the branch does not support
1353
:raises UnstackableRepositoryFormat: If the repository does not support
1357
return self._real_branch.get_stacked_on_url()
1359
def lock_read(self):
1360
if not self._lock_mode:
1361
self._lock_mode = 'r'
1362
self._lock_count = 1
1363
if self._real_branch is not None:
1364
self._real_branch.lock_read()
1366
self._lock_count += 1
1368
def _remote_lock_write(self, token):
1370
branch_token = repo_token = ''
1372
branch_token = token
1373
repo_token = self.repository.lock_write()
1374
self.repository.unlock()
1375
path = self.bzrdir._path_for_remote_call(self._client)
1377
response = self._client.call(
1378
'Branch.lock_write', path, branch_token, repo_token or '')
1379
except errors.ErrorFromSmartServer, err:
1380
if err.error_verb == 'LockContention':
1381
raise errors.LockContention('(remote lock)')
1382
elif err.error_verb == 'TokenMismatch':
1383
raise errors.TokenMismatch(token, '(remote token)')
1384
elif err.error_verb == 'UnlockableTransport':
1385
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1386
elif err.error_verb == 'ReadOnlyError':
1387
raise errors.ReadOnlyError(self)
1388
elif err.error_verb == 'LockFailed':
1389
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1391
if response[0] != 'ok':
1392
raise errors.UnexpectedSmartServerResponse(response)
1393
ok, branch_token, repo_token = response
1394
return branch_token, repo_token
1396
def lock_write(self, token=None):
1397
if not self._lock_mode:
1398
remote_tokens = self._remote_lock_write(token)
1399
self._lock_token, self._repo_lock_token = remote_tokens
1400
if not self._lock_token:
1401
raise SmartProtocolError('Remote server did not return a token!')
1402
# TODO: We really, really, really don't want to call _ensure_real
1403
# here, but it's the easiest way to ensure coherency between the
1404
# state of the RemoteBranch and RemoteRepository objects and the
1405
# physical locks. If we don't materialise the real objects here,
1406
# then getting everything in the right state later is complex, so
1407
# for now we just do it the lazy way.
1408
# -- Andrew Bennetts, 2007-02-22.
1410
if self._real_branch is not None:
1411
self._real_branch.repository.lock_write(
1412
token=self._repo_lock_token)
1414
self._real_branch.lock_write(token=self._lock_token)
1416
self._real_branch.repository.unlock()
1417
if token is not None:
1418
self._leave_lock = True
1420
# XXX: this case seems to be unreachable; token cannot be None.
1421
self._leave_lock = False
1422
self._lock_mode = 'w'
1423
self._lock_count = 1
1424
elif self._lock_mode == 'r':
1425
raise errors.ReadOnlyTransaction
1427
if token is not None:
1428
# A token was given to lock_write, and we're relocking, so check
1429
# that the given token actually matches the one we already have.
1430
if token != self._lock_token:
1431
raise errors.TokenMismatch(token, self._lock_token)
1432
self._lock_count += 1
1433
return self._lock_token or None
1435
def _unlock(self, branch_token, repo_token):
1436
path = self.bzrdir._path_for_remote_call(self._client)
1438
response = self._client.call('Branch.unlock', path, branch_token,
1440
except errors.ErrorFromSmartServer, err:
1441
if err.error_verb == 'TokenMismatch':
1442
raise errors.TokenMismatch(
1443
str((branch_token, repo_token)), '(remote tokens)')
1445
if response == ('ok',):
1447
raise errors.UnexpectedSmartServerResponse(response)
1450
self._lock_count -= 1
1451
if not self._lock_count:
1452
self._clear_cached_state()
1453
mode = self._lock_mode
1454
self._lock_mode = None
1455
if self._real_branch is not None:
1456
if (not self._leave_lock and mode == 'w' and
1457
self._repo_lock_token):
1458
# If this RemoteBranch will remove the physical lock for the
1459
# repository, make sure the _real_branch doesn't do it
1460
# first. (Because the _real_branch's repository is set to
1461
# be the RemoteRepository.)
1462
self._real_branch.repository.leave_lock_in_place()
1463
self._real_branch.unlock()
1465
# Only write-locked branched need to make a remote method call
1466
# to perfom the unlock.
1468
if not self._lock_token:
1469
raise AssertionError('Locked, but no token!')
1470
branch_token = self._lock_token
1471
repo_token = self._repo_lock_token
1472
self._lock_token = None
1473
self._repo_lock_token = None
1474
if not self._leave_lock:
1475
self._unlock(branch_token, repo_token)
1477
def break_lock(self):
1479
return self._real_branch.break_lock()
1481
def leave_lock_in_place(self):
1482
if not self._lock_token:
1483
raise NotImplementedError(self.leave_lock_in_place)
1484
self._leave_lock = True
1486
def dont_leave_lock_in_place(self):
1487
if not self._lock_token:
1488
raise NotImplementedError(self.dont_leave_lock_in_place)
1489
self._leave_lock = False
1491
def _last_revision_info(self):
1492
path = self.bzrdir._path_for_remote_call(self._client)
1493
response = self._client.call('Branch.last_revision_info', path)
1494
if response[0] != 'ok':
1495
raise SmartProtocolError('unexpected response code %s' % (response,))
1496
revno = int(response[1])
1497
last_revision = response[2]
1498
return (revno, last_revision)
1500
def _gen_revision_history(self):
1501
"""See Branch._gen_revision_history()."""
1502
path = self.bzrdir._path_for_remote_call(self._client)
1503
response_tuple, response_handler = self._client.call_expecting_body(
1504
'Branch.revision_history', path)
1505
if response_tuple[0] != 'ok':
1506
raise errors.UnexpectedSmartServerResponse(response_tuple)
1507
result = response_handler.read_body_bytes().split('\x00')
1512
def _set_last_revision_descendant(self, revision_id, other_branch,
1513
allow_diverged=False, allow_overwrite_descendant=False):
1514
path = self.bzrdir._path_for_remote_call(self._client)
1516
response = self._client.call('Branch.set_last_revision_ex',
1517
path, self._lock_token, self._repo_lock_token, revision_id,
1518
int(allow_diverged), int(allow_overwrite_descendant))
1519
except errors.ErrorFromSmartServer, err:
1520
if err.error_verb == 'NoSuchRevision':
1521
raise NoSuchRevision(self, revision_id)
1522
elif err.error_verb == 'Diverged':
1523
raise errors.DivergedBranches(self, other_branch)
1525
self._clear_cached_state()
1526
if len(response) != 3 and response[0] != 'ok':
1527
raise errors.UnexpectedSmartServerResponse(response)
1528
new_revno, new_revision_id = response[1:]
1529
self._last_revision_info_cache = new_revno, new_revision_id
1530
self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1532
def _set_last_revision(self, revision_id):
1533
path = self.bzrdir._path_for_remote_call(self._client)
1534
self._clear_cached_state()
1536
response = self._client.call('Branch.set_last_revision',
1537
path, self._lock_token, self._repo_lock_token, revision_id)
1538
except errors.ErrorFromSmartServer, err:
1539
if err.error_verb == 'NoSuchRevision':
1540
raise NoSuchRevision(self, revision_id)
1542
if response != ('ok',):
1543
raise errors.UnexpectedSmartServerResponse(response)
1546
def set_revision_history(self, rev_history):
1547
# Send just the tip revision of the history; the server will generate
1548
# the full history from that. If the revision doesn't exist in this
1549
# branch, NoSuchRevision will be raised.
1550
if rev_history == []:
1553
rev_id = rev_history[-1]
1554
self._set_last_revision(rev_id)
1555
self._cache_revision_history(rev_history)
1557
def get_parent(self):
1559
return self._real_branch.get_parent()
1561
def set_parent(self, url):
1563
return self._real_branch.set_parent(url)
1565
def set_stacked_on_url(self, stacked_location):
1566
"""Set the URL this branch is stacked against.
1568
:raises UnstackableBranchFormat: If the branch does not support
1570
:raises UnstackableRepositoryFormat: If the repository does not support
1574
return self._real_branch.set_stacked_on_url(stacked_location)
1576
def sprout(self, to_bzrdir, revision_id=None):
1577
# Like Branch.sprout, except that it sprouts a branch in the default
1578
# format, because RemoteBranches can't be created at arbitrary URLs.
1579
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1580
# to_bzrdir.create_branch...
1582
result = self._real_branch._format.initialize(to_bzrdir)
1583
self.copy_content_into(result, revision_id=revision_id)
1584
result.set_parent(self.bzrdir.root_transport.base)
1588
def pull(self, source, overwrite=False, stop_revision=None,
1590
self._clear_cached_state_of_remote_branch_only()
1592
return self._real_branch.pull(
1593
source, overwrite=overwrite, stop_revision=stop_revision,
1594
_override_hook_target=self, **kwargs)
1597
def push(self, target, overwrite=False, stop_revision=None):
1599
return self._real_branch.push(
1600
target, overwrite=overwrite, stop_revision=stop_revision,
1601
_override_hook_source_branch=self)
1603
def is_locked(self):
1604
return self._lock_count >= 1
1607
def set_last_revision_info(self, revno, revision_id):
1608
revision_id = ensure_null(revision_id)
1609
path = self.bzrdir._path_for_remote_call(self._client)
1611
response = self._client.call('Branch.set_last_revision_info',
1612
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1613
except errors.UnknownSmartMethod:
1615
self._clear_cached_state_of_remote_branch_only()
1616
self._real_branch.set_last_revision_info(revno, revision_id)
1617
self._last_revision_info_cache = revno, revision_id
1619
except errors.ErrorFromSmartServer, err:
1620
if err.error_verb == 'NoSuchRevision':
1621
raise NoSuchRevision(self, err.error_args[0])
1623
if response == ('ok',):
1624
self._clear_cached_state()
1625
self._last_revision_info_cache = revno, revision_id
1626
# Update the _real_branch's cache too.
1627
if self._real_branch is not None:
1628
cache = self._last_revision_info_cache
1629
self._real_branch._last_revision_info_cache = cache
1631
raise errors.UnexpectedSmartServerResponse(response)
1634
def generate_revision_history(self, revision_id, last_rev=None,
1636
medium = self._client._medium
1637
if not medium._is_remote_before((1, 6)):
1639
self._set_last_revision_descendant(revision_id, other_branch,
1640
allow_diverged=True, allow_overwrite_descendant=True)
1642
except errors.UnknownSmartMethod:
1643
medium._remember_remote_is_before((1, 6))
1644
self._clear_cached_state_of_remote_branch_only()
1646
self._real_branch.generate_revision_history(
1647
revision_id, last_rev=last_rev, other_branch=other_branch)
1652
return self._real_branch.tags
1654
def set_push_location(self, location):
1656
return self._real_branch.set_push_location(location)
1659
def update_revisions(self, other, stop_revision=None, overwrite=False,
1661
"""See Branch.update_revisions."""
1664
if stop_revision is None:
1665
stop_revision = other.last_revision()
1666
if revision.is_null(stop_revision):
1667
# if there are no commits, we're done.
1669
self.fetch(other, stop_revision)
1672
# Just unconditionally set the new revision. We don't care if
1673
# the branches have diverged.
1674
self._set_last_revision(stop_revision)
1676
medium = self._client._medium
1677
if not medium._is_remote_before((1, 6)):
1679
self._set_last_revision_descendant(stop_revision, other)
1681
except errors.UnknownSmartMethod:
1682
medium._remember_remote_is_before((1, 6))
1683
# Fallback for pre-1.6 servers: check for divergence
1684
# client-side, then do _set_last_revision.
1685
last_rev = revision.ensure_null(self.last_revision())
1687
graph = self.repository.get_graph()
1688
if self._check_if_descendant_or_diverged(
1689
stop_revision, last_rev, graph, other):
1690
# stop_revision is a descendant of last_rev, but we aren't
1691
# overwriting, so we're done.
1693
self._set_last_revision(stop_revision)
1698
def _extract_tar(tar, to_dir):
1699
"""Extract all the contents of a tarfile object.
1701
A replacement for extractall, which is not present in python2.4
1704
tar.extract(tarinfo, to_dir)