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)
823
def get_parent_map(self, keys):
824
"""See bzrlib.Graph.get_parent_map()."""
825
# Hack to build up the caching logic.
826
ancestry = self._parents_map
828
# Repository is not locked, so there's no cache.
829
missing_revisions = set(keys)
832
missing_revisions = set(key for key in keys if key not in ancestry)
833
if missing_revisions:
834
parent_map = self._get_parent_map(missing_revisions)
835
if 'hpss' in debug.debug_flags:
836
mutter('retransmitted revisions: %d of %d',
837
len(set(ancestry).intersection(parent_map)),
839
ancestry.update(parent_map)
840
present_keys = [k for k in keys if k in ancestry]
841
if 'hpss' in debug.debug_flags:
842
if self._requested_parents is not None and len(ancestry) != 0:
843
self._requested_parents.update(present_keys)
844
mutter('Current RemoteRepository graph hit rate: %d%%',
845
100.0 * len(self._requested_parents) / len(ancestry))
846
return dict((k, ancestry[k]) for k in present_keys)
848
def _get_parent_map(self, keys):
849
"""Helper for get_parent_map that performs the RPC."""
850
medium = self._client._medium
851
if medium._is_remote_before((1, 2)):
852
# We already found out that the server can't understand
853
# Repository.get_parent_map requests, so just fetch the whole
855
# XXX: Note that this will issue a deprecation warning. This is ok
856
# :- its because we're working with a deprecated server anyway, and
857
# the user will almost certainly have seen a warning about the
858
# server version already.
859
rg = self.get_revision_graph()
860
# There is an api discrepency between get_parent_map and
861
# get_revision_graph. Specifically, a "key:()" pair in
862
# get_revision_graph just means a node has no parents. For
863
# "get_parent_map" it means the node is a ghost. So fix up the
864
# graph to correct this.
865
# https://bugs.launchpad.net/bzr/+bug/214894
866
# There is one other "bug" which is that ghosts in
867
# get_revision_graph() are not returned at all. But we won't worry
868
# about that for now.
869
for node_id, parent_ids in rg.iteritems():
871
rg[node_id] = (NULL_REVISION,)
872
rg[NULL_REVISION] = ()
877
raise ValueError('get_parent_map(None) is not valid')
878
if NULL_REVISION in keys:
879
keys.discard(NULL_REVISION)
880
found_parents = {NULL_REVISION:()}
885
# TODO(Needs analysis): We could assume that the keys being requested
886
# from get_parent_map are in a breadth first search, so typically they
887
# will all be depth N from some common parent, and we don't have to
888
# have the server iterate from the root parent, but rather from the
889
# keys we're searching; and just tell the server the keyspace we
890
# already have; but this may be more traffic again.
892
# Transform self._parents_map into a search request recipe.
893
# TODO: Manage this incrementally to avoid covering the same path
894
# repeatedly. (The server will have to on each request, but the less
895
# work done the better).
896
parents_map = self._parents_map
897
if parents_map is None:
898
# Repository is not locked, so there's no cache.
900
start_set = set(parents_map)
901
result_parents = set()
902
for parents in parents_map.itervalues():
903
result_parents.update(parents)
904
stop_keys = result_parents.difference(start_set)
905
included_keys = start_set.intersection(result_parents)
906
start_set.difference_update(included_keys)
907
recipe = (start_set, stop_keys, len(parents_map))
908
body = self._serialise_search_recipe(recipe)
909
path = self.bzrdir._path_for_remote_call(self._client)
911
if type(key) is not str:
913
"key %r not a plain string" % (key,))
914
verb = 'Repository.get_parent_map'
915
args = (path,) + tuple(keys)
917
response = self._client.call_with_body_bytes_expecting_body(
918
verb, args, self._serialise_search_recipe(recipe))
919
except errors.UnknownSmartMethod:
920
# Server does not support this method, so get the whole graph.
921
# Worse, we have to force a disconnection, because the server now
922
# doesn't realise it has a body on the wire to consume, so the
923
# only way to recover is to abandon the connection.
925
'Server is too old for fast get_parent_map, reconnecting. '
926
'(Upgrade the server to Bazaar 1.2 to avoid this)')
928
# To avoid having to disconnect repeatedly, we keep track of the
929
# fact the server doesn't understand remote methods added in 1.2.
930
medium._remember_remote_is_before((1, 2))
931
return self.get_revision_graph(None)
932
response_tuple, response_handler = response
933
if response_tuple[0] not in ['ok']:
934
response_handler.cancel_read_body()
935
raise errors.UnexpectedSmartServerResponse(response_tuple)
936
if response_tuple[0] == 'ok':
937
coded = bz2.decompress(response_handler.read_body_bytes())
941
lines = coded.split('\n')
944
d = tuple(line.split())
946
revision_graph[d[0]] = d[1:]
948
# No parents - so give the Graph result (NULL_REVISION,).
949
revision_graph[d[0]] = (NULL_REVISION,)
950
return revision_graph
953
def get_signature_text(self, revision_id):
955
return self._real_repository.get_signature_text(revision_id)
958
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
959
def get_revision_graph_with_ghosts(self, revision_ids=None):
961
return self._real_repository.get_revision_graph_with_ghosts(
962
revision_ids=revision_ids)
965
def get_inventory_xml(self, revision_id):
967
return self._real_repository.get_inventory_xml(revision_id)
969
def deserialise_inventory(self, revision_id, xml):
971
return self._real_repository.deserialise_inventory(revision_id, xml)
973
def reconcile(self, other=None, thorough=False):
975
return self._real_repository.reconcile(other=other, thorough=thorough)
977
def all_revision_ids(self):
979
return self._real_repository.all_revision_ids()
982
def get_deltas_for_revisions(self, revisions):
984
return self._real_repository.get_deltas_for_revisions(revisions)
987
def get_revision_delta(self, revision_id):
989
return self._real_repository.get_revision_delta(revision_id)
992
def revision_trees(self, revision_ids):
994
return self._real_repository.revision_trees(revision_ids)
997
def get_revision_reconcile(self, revision_id):
999
return self._real_repository.get_revision_reconcile(revision_id)
1002
def check(self, revision_ids=None):
1004
return self._real_repository.check(revision_ids=revision_ids)
1006
def copy_content_into(self, destination, revision_id=None):
1008
return self._real_repository.copy_content_into(
1009
destination, revision_id=revision_id)
1011
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1012
# get a tarball of the remote repository, and copy from that into the
1014
from bzrlib import osutils
1017
# TODO: Maybe a progress bar while streaming the tarball?
1018
note("Copying repository content as tarball...")
1019
tar_file = self._get_tarball('bz2')
1020
if tar_file is None:
1022
destination = to_bzrdir.create_repository()
1024
tar = tarfile.open('repository', fileobj=tar_file,
1026
tmpdir = tempfile.mkdtemp()
1028
_extract_tar(tar, tmpdir)
1029
tmp_bzrdir = BzrDir.open(tmpdir)
1030
tmp_repo = tmp_bzrdir.open_repository()
1031
tmp_repo.copy_content_into(destination, revision_id)
1033
osutils.rmtree(tmpdir)
1037
# TODO: Suggestion from john: using external tar is much faster than
1038
# python's tarfile library, but it may not work on windows.
1041
def inventories(self):
1042
"""Decorate the real repository for now.
1044
In the long term a full blown network facility is needed to
1045
avoid creating a real repository object locally.
1048
return self._real_repository.inventories
1052
"""Compress the data within the repository.
1054
This is not currently implemented within the smart server.
1057
return self._real_repository.pack()
1060
def revisions(self):
1061
"""Decorate the real repository for now.
1063
In the short term this should become a real object to intercept graph
1066
In the long term a full blown network facility is needed.
1069
return self._real_repository.revisions
1071
def set_make_working_trees(self, new_value):
1073
self._real_repository.set_make_working_trees(new_value)
1076
def signatures(self):
1077
"""Decorate the real repository for now.
1079
In the long term a full blown network facility is needed to avoid
1080
creating a real repository object locally.
1083
return self._real_repository.signatures
1086
def sign_revision(self, revision_id, gpg_strategy):
1088
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1092
"""Decorate the real repository for now.
1094
In the long term a full blown network facility is needed to avoid
1095
creating a real repository object locally.
1098
return self._real_repository.texts
1101
def get_revisions(self, revision_ids):
1103
return self._real_repository.get_revisions(revision_ids)
1105
def supports_rich_root(self):
1107
return self._real_repository.supports_rich_root()
1109
def iter_reverse_revision_history(self, revision_id):
1111
return self._real_repository.iter_reverse_revision_history(revision_id)
1114
def _serializer(self):
1116
return self._real_repository._serializer
1118
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1120
return self._real_repository.store_revision_signature(
1121
gpg_strategy, plaintext, revision_id)
1123
def add_signature_text(self, revision_id, signature):
1125
return self._real_repository.add_signature_text(revision_id, signature)
1127
def has_signature_for_revision_id(self, revision_id):
1129
return self._real_repository.has_signature_for_revision_id(revision_id)
1131
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1133
return self._real_repository.item_keys_introduced_by(revision_ids,
1134
_files_pb=_files_pb)
1136
def revision_graph_can_have_wrong_parents(self):
1137
# The answer depends on the remote repo format.
1139
return self._real_repository.revision_graph_can_have_wrong_parents()
1141
def _find_inconsistent_revision_parents(self):
1143
return self._real_repository._find_inconsistent_revision_parents()
1145
def _check_for_inconsistent_revision_parents(self):
1147
return self._real_repository._check_for_inconsistent_revision_parents()
1149
def _make_parents_provider(self):
1152
def _serialise_search_recipe(self, recipe):
1153
"""Serialise a graph search recipe.
1155
:param recipe: A search recipe (start, stop, count).
1156
:return: Serialised bytes.
1158
start_keys = ' '.join(recipe[0])
1159
stop_keys = ' '.join(recipe[1])
1160
count = str(recipe[2])
1161
return '\n'.join((start_keys, stop_keys, count))
1164
class RemoteBranchLockableFiles(LockableFiles):
1165
"""A 'LockableFiles' implementation that talks to a smart server.
1167
This is not a public interface class.
1170
def __init__(self, bzrdir, _client):
1171
self.bzrdir = bzrdir
1172
self._client = _client
1173
self._need_find_modes = True
1174
LockableFiles.__init__(
1175
self, bzrdir.get_branch_transport(None),
1176
'lock', lockdir.LockDir)
1178
def _find_modes(self):
1179
# RemoteBranches don't let the client set the mode of control files.
1180
self._dir_mode = None
1181
self._file_mode = None
1184
class RemoteBranchFormat(branch.BranchFormat):
1186
def __eq__(self, other):
1187
return (isinstance(other, RemoteBranchFormat) and
1188
self.__dict__ == other.__dict__)
1190
def get_format_description(self):
1191
return 'Remote BZR Branch'
1193
def get_format_string(self):
1194
return 'Remote BZR Branch'
1196
def open(self, a_bzrdir):
1197
return a_bzrdir.open_branch()
1199
def initialize(self, a_bzrdir):
1200
return a_bzrdir.create_branch()
1202
def supports_tags(self):
1203
# Remote branches might support tags, but we won't know until we
1204
# access the real remote branch.
1208
class RemoteBranch(branch.Branch):
1209
"""Branch stored on a server accessed by HPSS RPC.
1211
At the moment most operations are mapped down to simple file operations.
1214
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1216
"""Create a RemoteBranch instance.
1218
:param real_branch: An optional local implementation of the branch
1219
format, usually accessing the data via the VFS.
1220
:param _client: Private parameter for testing.
1222
# We intentionally don't call the parent class's __init__, because it
1223
# will try to assign to self.tags, which is a property in this subclass.
1224
# And the parent's __init__ doesn't do much anyway.
1225
self._revision_id_to_revno_cache = None
1226
self._revision_history_cache = None
1227
self._last_revision_info_cache = None
1228
self.bzrdir = remote_bzrdir
1229
if _client is not None:
1230
self._client = _client
1232
self._client = remote_bzrdir._client
1233
self.repository = remote_repository
1234
if real_branch is not None:
1235
self._real_branch = real_branch
1236
# Give the remote repository the matching real repo.
1237
real_repo = self._real_branch.repository
1238
if isinstance(real_repo, RemoteRepository):
1239
real_repo._ensure_real()
1240
real_repo = real_repo._real_repository
1241
self.repository._set_real_repository(real_repo)
1242
# Give the branch the remote repository to let fast-pathing happen.
1243
self._real_branch.repository = self.repository
1245
self._real_branch = None
1246
# Fill out expected attributes of branch for bzrlib api users.
1247
self._format = RemoteBranchFormat()
1248
self.base = self.bzrdir.root_transport.base
1249
self._control_files = None
1250
self._lock_mode = None
1251
self._lock_token = None
1252
self._repo_lock_token = None
1253
self._lock_count = 0
1254
self._leave_lock = False
1256
def _get_real_transport(self):
1257
# if we try vfs access, return the real branch's vfs transport
1259
return self._real_branch._transport
1261
_transport = property(_get_real_transport)
1264
return "%s(%s)" % (self.__class__.__name__, self.base)
1268
def _ensure_real(self):
1269
"""Ensure that there is a _real_branch set.
1271
Used before calls to self._real_branch.
1273
if self._real_branch is None:
1274
if not vfs.vfs_enabled():
1275
raise AssertionError('smart server vfs must be enabled '
1276
'to use vfs implementation')
1277
self.bzrdir._ensure_real()
1278
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1279
# Give the remote repository the matching real repo.
1280
real_repo = self._real_branch.repository
1281
if isinstance(real_repo, RemoteRepository):
1282
real_repo._ensure_real()
1283
real_repo = real_repo._real_repository
1284
self.repository._set_real_repository(real_repo)
1285
# Give the branch the remote repository to let fast-pathing happen.
1286
self._real_branch.repository = self.repository
1287
# XXX: deal with _lock_mode == 'w'
1288
if self._lock_mode == 'r':
1289
self._real_branch.lock_read()
1291
def _clear_cached_state(self):
1292
super(RemoteBranch, self)._clear_cached_state()
1293
if self._real_branch is not None:
1294
self._real_branch._clear_cached_state()
1296
def _clear_cached_state_of_remote_branch_only(self):
1297
"""Like _clear_cached_state, but doesn't clear the cache of
1300
This is useful when falling back to calling a method of
1301
self._real_branch that changes state. In that case the underlying
1302
branch changes, so we need to invalidate this RemoteBranch's cache of
1303
it. However, there's no need to invalidate the _real_branch's cache
1304
too, in fact doing so might harm performance.
1306
super(RemoteBranch, self)._clear_cached_state()
1309
def control_files(self):
1310
# Defer actually creating RemoteBranchLockableFiles until its needed,
1311
# because it triggers an _ensure_real that we otherwise might not need.
1312
if self._control_files is None:
1313
self._control_files = RemoteBranchLockableFiles(
1314
self.bzrdir, self._client)
1315
return self._control_files
1317
def _get_checkout_format(self):
1319
return self._real_branch._get_checkout_format()
1321
def get_physical_lock_status(self):
1322
"""See Branch.get_physical_lock_status()."""
1323
# should be an API call to the server, as branches must be lockable.
1325
return self._real_branch.get_physical_lock_status()
1327
def get_stacked_on_url(self):
1328
"""Get the URL this branch is stacked against.
1330
:raises NotStacked: If the branch is not stacked.
1331
:raises UnstackableBranchFormat: If the branch does not support
1333
:raises UnstackableRepositoryFormat: If the repository does not support
1337
return self._real_branch.get_stacked_on_url()
1339
def lock_read(self):
1340
if not self._lock_mode:
1341
self._lock_mode = 'r'
1342
self._lock_count = 1
1343
if self._real_branch is not None:
1344
self._real_branch.lock_read()
1346
self._lock_count += 1
1348
def _remote_lock_write(self, token):
1350
branch_token = repo_token = ''
1352
branch_token = token
1353
repo_token = self.repository.lock_write()
1354
self.repository.unlock()
1355
path = self.bzrdir._path_for_remote_call(self._client)
1357
response = self._client.call(
1358
'Branch.lock_write', path, branch_token, repo_token or '')
1359
except errors.ErrorFromSmartServer, err:
1360
if err.error_verb == 'LockContention':
1361
raise errors.LockContention('(remote lock)')
1362
elif err.error_verb == 'TokenMismatch':
1363
raise errors.TokenMismatch(token, '(remote token)')
1364
elif err.error_verb == 'UnlockableTransport':
1365
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1366
elif err.error_verb == 'ReadOnlyError':
1367
raise errors.ReadOnlyError(self)
1368
elif err.error_verb == 'LockFailed':
1369
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1371
if response[0] != 'ok':
1372
raise errors.UnexpectedSmartServerResponse(response)
1373
ok, branch_token, repo_token = response
1374
return branch_token, repo_token
1376
def lock_write(self, token=None):
1377
if not self._lock_mode:
1378
remote_tokens = self._remote_lock_write(token)
1379
self._lock_token, self._repo_lock_token = remote_tokens
1380
if not self._lock_token:
1381
raise SmartProtocolError('Remote server did not return a token!')
1382
# TODO: We really, really, really don't want to call _ensure_real
1383
# here, but it's the easiest way to ensure coherency between the
1384
# state of the RemoteBranch and RemoteRepository objects and the
1385
# physical locks. If we don't materialise the real objects here,
1386
# then getting everything in the right state later is complex, so
1387
# for now we just do it the lazy way.
1388
# -- Andrew Bennetts, 2007-02-22.
1390
if self._real_branch is not None:
1391
self._real_branch.repository.lock_write(
1392
token=self._repo_lock_token)
1394
self._real_branch.lock_write(token=self._lock_token)
1396
self._real_branch.repository.unlock()
1397
if token is not None:
1398
self._leave_lock = True
1400
# XXX: this case seems to be unreachable; token cannot be None.
1401
self._leave_lock = False
1402
self._lock_mode = 'w'
1403
self._lock_count = 1
1404
elif self._lock_mode == 'r':
1405
raise errors.ReadOnlyTransaction
1407
if token is not None:
1408
# A token was given to lock_write, and we're relocking, so check
1409
# that the given token actually matches the one we already have.
1410
if token != self._lock_token:
1411
raise errors.TokenMismatch(token, self._lock_token)
1412
self._lock_count += 1
1413
return self._lock_token or None
1415
def _unlock(self, branch_token, repo_token):
1416
path = self.bzrdir._path_for_remote_call(self._client)
1418
response = self._client.call('Branch.unlock', path, branch_token,
1420
except errors.ErrorFromSmartServer, err:
1421
if err.error_verb == 'TokenMismatch':
1422
raise errors.TokenMismatch(
1423
str((branch_token, repo_token)), '(remote tokens)')
1425
if response == ('ok',):
1427
raise errors.UnexpectedSmartServerResponse(response)
1430
self._lock_count -= 1
1431
if not self._lock_count:
1432
self._clear_cached_state()
1433
mode = self._lock_mode
1434
self._lock_mode = None
1435
if self._real_branch is not None:
1436
if (not self._leave_lock and mode == 'w' and
1437
self._repo_lock_token):
1438
# If this RemoteBranch will remove the physical lock for the
1439
# repository, make sure the _real_branch doesn't do it
1440
# first. (Because the _real_branch's repository is set to
1441
# be the RemoteRepository.)
1442
self._real_branch.repository.leave_lock_in_place()
1443
self._real_branch.unlock()
1445
# Only write-locked branched need to make a remote method call
1446
# to perfom the unlock.
1448
if not self._lock_token:
1449
raise AssertionError('Locked, but no token!')
1450
branch_token = self._lock_token
1451
repo_token = self._repo_lock_token
1452
self._lock_token = None
1453
self._repo_lock_token = None
1454
if not self._leave_lock:
1455
self._unlock(branch_token, repo_token)
1457
def break_lock(self):
1459
return self._real_branch.break_lock()
1461
def leave_lock_in_place(self):
1462
if not self._lock_token:
1463
raise NotImplementedError(self.leave_lock_in_place)
1464
self._leave_lock = True
1466
def dont_leave_lock_in_place(self):
1467
if not self._lock_token:
1468
raise NotImplementedError(self.dont_leave_lock_in_place)
1469
self._leave_lock = False
1471
def _last_revision_info(self):
1472
path = self.bzrdir._path_for_remote_call(self._client)
1473
response = self._client.call('Branch.last_revision_info', path)
1474
if response[0] != 'ok':
1475
raise SmartProtocolError('unexpected response code %s' % (response,))
1476
revno = int(response[1])
1477
last_revision = response[2]
1478
return (revno, last_revision)
1480
def _gen_revision_history(self):
1481
"""See Branch._gen_revision_history()."""
1482
path = self.bzrdir._path_for_remote_call(self._client)
1483
response_tuple, response_handler = self._client.call_expecting_body(
1484
'Branch.revision_history', path)
1485
if response_tuple[0] != 'ok':
1486
raise errors.UnexpectedSmartServerResponse(response_tuple)
1487
result = response_handler.read_body_bytes().split('\x00')
1492
def _set_last_revision_descendant(self, revision_id, other_branch,
1493
allow_diverged=False, allow_overwrite_descendant=False):
1494
path = self.bzrdir._path_for_remote_call(self._client)
1496
response = self._client.call('Branch.set_last_revision_ex',
1497
path, self._lock_token, self._repo_lock_token, revision_id,
1498
int(allow_diverged), int(allow_overwrite_descendant))
1499
except errors.ErrorFromSmartServer, err:
1500
if err.error_verb == 'NoSuchRevision':
1501
raise NoSuchRevision(self, revision_id)
1502
elif err.error_verb == 'Diverged':
1503
raise errors.DivergedBranches(self, other_branch)
1505
self._clear_cached_state()
1506
if len(response) != 3 and response[0] != 'ok':
1507
raise errors.UnexpectedSmartServerResponse(response)
1508
new_revno, new_revision_id = response[1:]
1509
self._last_revision_info_cache = new_revno, new_revision_id
1510
self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1512
def _set_last_revision(self, revision_id):
1513
path = self.bzrdir._path_for_remote_call(self._client)
1514
self._clear_cached_state()
1516
response = self._client.call('Branch.set_last_revision',
1517
path, self._lock_token, self._repo_lock_token, revision_id)
1518
except errors.ErrorFromSmartServer, err:
1519
if err.error_verb == 'NoSuchRevision':
1520
raise NoSuchRevision(self, revision_id)
1522
if response != ('ok',):
1523
raise errors.UnexpectedSmartServerResponse(response)
1526
def set_revision_history(self, rev_history):
1527
# Send just the tip revision of the history; the server will generate
1528
# the full history from that. If the revision doesn't exist in this
1529
# branch, NoSuchRevision will be raised.
1530
if rev_history == []:
1533
rev_id = rev_history[-1]
1534
self._set_last_revision(rev_id)
1535
self._cache_revision_history(rev_history)
1537
def get_parent(self):
1539
return self._real_branch.get_parent()
1541
def set_parent(self, url):
1543
return self._real_branch.set_parent(url)
1545
def set_stacked_on_url(self, stacked_location):
1546
"""Set the URL this branch is stacked against.
1548
:raises UnstackableBranchFormat: If the branch does not support
1550
:raises UnstackableRepositoryFormat: If the repository does not support
1554
return self._real_branch.set_stacked_on_url(stacked_location)
1556
def sprout(self, to_bzrdir, revision_id=None):
1557
# Like Branch.sprout, except that it sprouts a branch in the default
1558
# format, because RemoteBranches can't be created at arbitrary URLs.
1559
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1560
# to_bzrdir.create_branch...
1562
result = self._real_branch._format.initialize(to_bzrdir)
1563
self.copy_content_into(result, revision_id=revision_id)
1564
result.set_parent(self.bzrdir.root_transport.base)
1568
def pull(self, source, overwrite=False, stop_revision=None,
1570
self._clear_cached_state_of_remote_branch_only()
1572
return self._real_branch.pull(
1573
source, overwrite=overwrite, stop_revision=stop_revision,
1574
_override_hook_target=self, **kwargs)
1577
def push(self, target, overwrite=False, stop_revision=None):
1579
return self._real_branch.push(
1580
target, overwrite=overwrite, stop_revision=stop_revision,
1581
_override_hook_source_branch=self)
1583
def is_locked(self):
1584
return self._lock_count >= 1
1587
def set_last_revision_info(self, revno, revision_id):
1588
revision_id = ensure_null(revision_id)
1589
path = self.bzrdir._path_for_remote_call(self._client)
1591
response = self._client.call('Branch.set_last_revision_info',
1592
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1593
except errors.UnknownSmartMethod:
1595
self._clear_cached_state_of_remote_branch_only()
1596
self._real_branch.set_last_revision_info(revno, revision_id)
1597
self._last_revision_info_cache = revno, revision_id
1599
except errors.ErrorFromSmartServer, err:
1600
if err.error_verb == 'NoSuchRevision':
1601
raise NoSuchRevision(self, err.error_args[0])
1603
if response == ('ok',):
1604
self._clear_cached_state()
1605
self._last_revision_info_cache = revno, revision_id
1606
# Update the _real_branch's cache too.
1607
if self._real_branch is not None:
1608
cache = self._last_revision_info_cache
1609
self._real_branch._last_revision_info_cache = cache
1611
raise errors.UnexpectedSmartServerResponse(response)
1614
def generate_revision_history(self, revision_id, last_rev=None,
1616
medium = self._client._medium
1617
if not medium._is_remote_before((1, 6)):
1619
self._set_last_revision_descendant(revision_id, other_branch,
1620
allow_diverged=True, allow_overwrite_descendant=True)
1622
except errors.UnknownSmartMethod:
1623
medium._remember_remote_is_before((1, 6))
1624
self._clear_cached_state_of_remote_branch_only()
1626
self._real_branch.generate_revision_history(
1627
revision_id, last_rev=last_rev, other_branch=other_branch)
1632
return self._real_branch.tags
1634
def set_push_location(self, location):
1636
return self._real_branch.set_push_location(location)
1639
def update_revisions(self, other, stop_revision=None, overwrite=False,
1641
"""See Branch.update_revisions."""
1644
if stop_revision is None:
1645
stop_revision = other.last_revision()
1646
if revision.is_null(stop_revision):
1647
# if there are no commits, we're done.
1649
self.fetch(other, stop_revision)
1652
# Just unconditionally set the new revision. We don't care if
1653
# the branches have diverged.
1654
self._set_last_revision(stop_revision)
1656
medium = self._client._medium
1657
if not medium._is_remote_before((1, 6)):
1659
self._set_last_revision_descendant(stop_revision, other)
1661
except errors.UnknownSmartMethod:
1662
medium._remember_remote_is_before((1, 6))
1663
# Fallback for pre-1.6 servers: check for divergence
1664
# client-side, then do _set_last_revision.
1665
last_rev = revision.ensure_null(self.last_revision())
1667
graph = self.repository.get_graph()
1668
if self._check_if_descendant_or_diverged(
1669
stop_revision, last_rev, graph, other):
1670
# stop_revision is a descendant of last_rev, but we aren't
1671
# overwriting, so we're done.
1673
self._set_last_revision(stop_revision)
1678
def _extract_tar(tar, to_dir):
1679
"""Extract all the contents of a tarfile object.
1681
A replacement for extractall, which is not present in python2.4
1684
tar.extract(tarinfo, to_dir)