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)
231
def get_config(self):
233
return self._real_bzrdir.get_config()
236
class RemoteRepositoryFormat(repository.RepositoryFormat):
237
"""Format for repositories accessed over a _SmartClient.
239
Instances of this repository are represented by RemoteRepository
242
The RemoteRepositoryFormat is parameterized during construction
243
to reflect the capabilities of the real, remote format. Specifically
244
the attributes rich_root_data and supports_tree_reference are set
245
on a per instance basis, and are not set (and should not be) at
249
_matchingbzrdir = RemoteBzrDirFormat()
251
def initialize(self, a_bzrdir, shared=False):
252
if not isinstance(a_bzrdir, RemoteBzrDir):
253
prior_repo = self._creating_bzrdir.open_repository()
254
prior_repo._ensure_real()
255
return prior_repo._real_repository._format.initialize(
256
a_bzrdir, shared=shared)
257
return a_bzrdir.create_repository(shared=shared)
259
def open(self, a_bzrdir):
260
if not isinstance(a_bzrdir, RemoteBzrDir):
261
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
262
return a_bzrdir.open_repository()
264
def get_format_description(self):
265
return 'bzr remote repository'
267
def __eq__(self, other):
268
return self.__class__ == other.__class__
270
def check_conversion_target(self, target_format):
271
if self.rich_root_data and not target_format.rich_root_data:
272
raise errors.BadConversionTarget(
273
'Does not support rich root data.', target_format)
274
if (self.supports_tree_reference and
275
not getattr(target_format, 'supports_tree_reference', False)):
276
raise errors.BadConversionTarget(
277
'Does not support nested trees', target_format)
280
class RemoteRepository(object):
281
"""Repository accessed over rpc.
283
For the moment most operations are performed using local transport-backed
287
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
288
"""Create a RemoteRepository instance.
290
:param remote_bzrdir: The bzrdir hosting this repository.
291
:param format: The RemoteFormat object to use.
292
:param real_repository: If not None, a local implementation of the
293
repository logic for the repository, usually accessing the data
295
:param _client: Private testing parameter - override the smart client
296
to be used by the repository.
299
self._real_repository = real_repository
301
self._real_repository = None
302
self.bzrdir = remote_bzrdir
304
self._client = remote_bzrdir._client
306
self._client = _client
307
self._format = format
308
self._lock_mode = None
309
self._lock_token = None
311
self._leave_lock = False
312
# A cache of looked up revision parent data; reset at unlock time.
313
self._parents_map = None
314
if 'hpss' in debug.debug_flags:
315
self._requested_parents = None
317
# These depend on the actual remote format, so force them off for
318
# maximum compatibility. XXX: In future these should depend on the
319
# remote repository instance, but this is irrelevant until we perform
320
# reconcile via an RPC call.
321
self._reconcile_does_inventory_gc = False
322
self._reconcile_fixes_text_parents = False
323
self._reconcile_backsup_inventory = False
324
self.base = self.bzrdir.transport.base
325
# Additional places to query for data.
326
self._fallback_repositories = []
329
return "%s(%s)" % (self.__class__.__name__, self.base)
333
def abort_write_group(self):
334
"""Complete a write group on the decorated repository.
336
Smart methods peform operations in a single step so this api
337
is not really applicable except as a compatibility thunk
338
for older plugins that don't use e.g. the CommitBuilder
342
return self._real_repository.abort_write_group()
344
def commit_write_group(self):
345
"""Complete a write group on the decorated repository.
347
Smart methods peform operations in a single step so this api
348
is not really applicable except as a compatibility thunk
349
for older plugins that don't use e.g. the CommitBuilder
353
return self._real_repository.commit_write_group()
355
def _ensure_real(self):
356
"""Ensure that there is a _real_repository set.
358
Used before calls to self._real_repository.
360
if not self._real_repository:
361
self.bzrdir._ensure_real()
362
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
363
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
365
def find_text_key_references(self):
366
"""Find the text key references within the repository.
368
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
369
revision_ids. Each altered file-ids has the exact revision_ids that
370
altered it listed explicitly.
371
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
372
to whether they were referred to by the inventory of the
373
revision_id that they contain. The inventory texts from all present
374
revision ids are assessed to generate this report.
377
return self._real_repository.find_text_key_references()
379
def _generate_text_key_index(self):
380
"""Generate a new text key index for the repository.
382
This is an expensive function that will take considerable time to run.
384
:return: A dict mapping (file_id, revision_id) tuples to a list of
385
parents, also (file_id, revision_id) tuples.
388
return self._real_repository._generate_text_key_index()
390
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
391
def get_revision_graph(self, revision_id=None):
392
"""See Repository.get_revision_graph()."""
393
return self._get_revision_graph(revision_id)
395
def _get_revision_graph(self, revision_id):
396
"""Private method for using with old (< 1.2) servers to fallback."""
397
if revision_id is None:
399
elif revision.is_null(revision_id):
402
path = self.bzrdir._path_for_remote_call(self._client)
404
response = self._client.call_expecting_body(
405
'Repository.get_revision_graph', path, revision_id)
406
except errors.ErrorFromSmartServer, err:
407
if err.error_verb == 'nosuchrevision':
408
raise NoSuchRevision(self, revision_id)
410
response_tuple, response_handler = response
411
if response_tuple[0] != 'ok':
412
raise errors.UnexpectedSmartServerResponse(response_tuple)
413
coded = response_handler.read_body_bytes()
415
# no revisions in this repository!
417
lines = coded.split('\n')
420
d = tuple(line.split())
421
revision_graph[d[0]] = d[1:]
423
return revision_graph
425
def has_revision(self, revision_id):
426
"""See Repository.has_revision()."""
427
if revision_id == NULL_REVISION:
428
# The null revision is always present.
430
path = self.bzrdir._path_for_remote_call(self._client)
431
response = self._client.call(
432
'Repository.has_revision', path, revision_id)
433
if response[0] not in ('yes', 'no'):
434
raise errors.UnexpectedSmartServerResponse(response)
435
return response[0] == 'yes'
437
def has_revisions(self, revision_ids):
438
"""See Repository.has_revisions()."""
440
for revision_id in revision_ids:
441
if self.has_revision(revision_id):
442
result.add(revision_id)
445
def has_same_location(self, other):
446
return (self.__class__ == other.__class__ and
447
self.bzrdir.transport.base == other.bzrdir.transport.base)
449
def get_graph(self, other_repository=None):
450
"""Return the graph for this repository format"""
451
parents_provider = self
452
if (other_repository is not None and
453
other_repository.bzrdir.transport.base !=
454
self.bzrdir.transport.base):
455
parents_provider = graph._StackedParentsProvider(
456
[parents_provider, other_repository._make_parents_provider()])
457
return graph.Graph(parents_provider)
459
def gather_stats(self, revid=None, committers=None):
460
"""See Repository.gather_stats()."""
461
path = self.bzrdir._path_for_remote_call(self._client)
462
# revid can be None to indicate no revisions, not just NULL_REVISION
463
if revid is None or revision.is_null(revid):
467
if committers is None or not committers:
468
fmt_committers = 'no'
470
fmt_committers = 'yes'
471
response_tuple, response_handler = self._client.call_expecting_body(
472
'Repository.gather_stats', path, fmt_revid, fmt_committers)
473
if response_tuple[0] != 'ok':
474
raise errors.UnexpectedSmartServerResponse(response_tuple)
476
body = response_handler.read_body_bytes()
478
for line in body.split('\n'):
481
key, val_text = line.split(':')
482
if key in ('revisions', 'size', 'committers'):
483
result[key] = int(val_text)
484
elif key in ('firstrev', 'latestrev'):
485
values = val_text.split(' ')[1:]
486
result[key] = (float(values[0]), long(values[1]))
490
def find_branches(self, using=False):
491
"""See Repository.find_branches()."""
492
# should be an API call to the server.
494
return self._real_repository.find_branches(using=using)
496
def get_physical_lock_status(self):
497
"""See Repository.get_physical_lock_status()."""
498
# should be an API call to the server.
500
return self._real_repository.get_physical_lock_status()
502
def is_in_write_group(self):
503
"""Return True if there is an open write group.
505
write groups are only applicable locally for the smart server..
507
if self._real_repository:
508
return self._real_repository.is_in_write_group()
511
return self._lock_count >= 1
514
"""See Repository.is_shared()."""
515
path = self.bzrdir._path_for_remote_call(self._client)
516
response = self._client.call('Repository.is_shared', path)
517
if response[0] not in ('yes', 'no'):
518
raise SmartProtocolError('unexpected response code %s' % (response,))
519
return response[0] == 'yes'
521
def is_write_locked(self):
522
return self._lock_mode == 'w'
525
# wrong eventually - want a local lock cache context
526
if not self._lock_mode:
527
self._lock_mode = 'r'
529
self._parents_map = {}
530
if 'hpss' in debug.debug_flags:
531
self._requested_parents = set()
532
if self._real_repository is not None:
533
self._real_repository.lock_read()
535
self._lock_count += 1
537
def _remote_lock_write(self, token):
538
path = self.bzrdir._path_for_remote_call(self._client)
542
response = self._client.call('Repository.lock_write', path, token)
543
except errors.ErrorFromSmartServer, err:
544
if err.error_verb == 'LockContention':
545
raise errors.LockContention('(remote lock)')
546
elif err.error_verb == 'UnlockableTransport':
547
raise errors.UnlockableTransport(self.bzrdir.root_transport)
548
elif err.error_verb == 'LockFailed':
549
raise errors.LockFailed(err.error_args[0], err.error_args[1])
552
if response[0] == 'ok':
556
raise errors.UnexpectedSmartServerResponse(response)
558
def lock_write(self, token=None):
559
if not self._lock_mode:
560
self._lock_token = self._remote_lock_write(token)
561
# if self._lock_token is None, then this is something like packs or
562
# svn where we don't get to lock the repo, or a weave style repository
563
# where we cannot lock it over the wire and attempts to do so will
565
if self._real_repository is not None:
566
self._real_repository.lock_write(token=self._lock_token)
567
if token is not None:
568
self._leave_lock = True
570
self._leave_lock = False
571
self._lock_mode = 'w'
573
self._parents_map = {}
574
if 'hpss' in debug.debug_flags:
575
self._requested_parents = set()
576
elif self._lock_mode == 'r':
577
raise errors.ReadOnlyError(self)
579
self._lock_count += 1
580
return self._lock_token or None
582
def leave_lock_in_place(self):
583
if not self._lock_token:
584
raise NotImplementedError(self.leave_lock_in_place)
585
self._leave_lock = True
587
def dont_leave_lock_in_place(self):
588
if not self._lock_token:
589
raise NotImplementedError(self.dont_leave_lock_in_place)
590
self._leave_lock = False
592
def _set_real_repository(self, repository):
593
"""Set the _real_repository for this repository.
595
:param repository: The repository to fallback to for non-hpss
596
implemented operations.
598
if isinstance(repository, RemoteRepository):
599
raise AssertionError()
600
self._real_repository = repository
601
if self._lock_mode == 'w':
602
# if we are already locked, the real repository must be able to
603
# acquire the lock with our token.
604
self._real_repository.lock_write(self._lock_token)
605
elif self._lock_mode == 'r':
606
self._real_repository.lock_read()
608
def start_write_group(self):
609
"""Start a write group on the decorated repository.
611
Smart methods peform operations in a single step so this api
612
is not really applicable except as a compatibility thunk
613
for older plugins that don't use e.g. the CommitBuilder
617
return self._real_repository.start_write_group()
619
def _unlock(self, token):
620
path = self.bzrdir._path_for_remote_call(self._client)
622
# with no token the remote repository is not persistently locked.
625
response = self._client.call('Repository.unlock', path, token)
626
except errors.ErrorFromSmartServer, err:
627
if err.error_verb == 'TokenMismatch':
628
raise errors.TokenMismatch(token, '(remote token)')
630
if response == ('ok',):
633
raise errors.UnexpectedSmartServerResponse(response)
636
self._lock_count -= 1
637
if self._lock_count > 0:
639
self._parents_map = None
640
if 'hpss' in debug.debug_flags:
641
self._requested_parents = None
642
old_mode = self._lock_mode
643
self._lock_mode = None
645
# The real repository is responsible at present for raising an
646
# exception if it's in an unfinished write group. However, it
647
# normally will *not* actually remove the lock from disk - that's
648
# done by the server on receiving the Repository.unlock call.
649
# This is just to let the _real_repository stay up to date.
650
if self._real_repository is not None:
651
self._real_repository.unlock()
653
# The rpc-level lock should be released even if there was a
654
# problem releasing the vfs-based lock.
656
# Only write-locked repositories need to make a remote method
657
# call to perfom the unlock.
658
old_token = self._lock_token
659
self._lock_token = None
660
if not self._leave_lock:
661
self._unlock(old_token)
663
def break_lock(self):
664
# should hand off to the network
666
return self._real_repository.break_lock()
668
def _get_tarball(self, compression):
669
"""Return a TemporaryFile containing a repository tarball.
671
Returns None if the server does not support sending tarballs.
674
path = self.bzrdir._path_for_remote_call(self._client)
676
response, protocol = self._client.call_expecting_body(
677
'Repository.tarball', path, compression)
678
except errors.UnknownSmartMethod:
679
protocol.cancel_read_body()
681
if response[0] == 'ok':
682
# Extract the tarball and return it
683
t = tempfile.NamedTemporaryFile()
684
# TODO: rpc layer should read directly into it...
685
t.write(protocol.read_body_bytes())
688
raise errors.UnexpectedSmartServerResponse(response)
690
def sprout(self, to_bzrdir, revision_id=None):
691
# TODO: Option to control what format is created?
693
dest_repo = self._real_repository._format.initialize(to_bzrdir,
695
dest_repo.fetch(self, revision_id=revision_id)
698
### These methods are just thin shims to the VFS object for now.
700
def revision_tree(self, revision_id):
702
return self._real_repository.revision_tree(revision_id)
704
def get_serializer_format(self):
706
return self._real_repository.get_serializer_format()
708
def get_commit_builder(self, branch, parents, config, timestamp=None,
709
timezone=None, committer=None, revprops=None,
711
# FIXME: It ought to be possible to call this without immediately
712
# triggering _ensure_real. For now it's the easiest thing to do.
714
builder = self._real_repository.get_commit_builder(branch, parents,
715
config, timestamp=timestamp, timezone=timezone,
716
committer=committer, revprops=revprops, revision_id=revision_id)
719
def add_fallback_repository(self, repository):
720
"""Add a repository to use for looking up data not held locally.
722
:param repository: A repository.
724
if not self._format.supports_external_lookups:
725
raise errors.UnstackableRepositoryFormat(self._format, self.base)
726
# We need to accumulate additional repositories here, to pass them in
728
self._fallback_repositories.append(repository)
730
def add_inventory(self, revid, inv, parents):
732
return self._real_repository.add_inventory(revid, inv, parents)
734
def add_revision(self, rev_id, rev, inv=None, config=None):
736
return self._real_repository.add_revision(
737
rev_id, rev, inv=inv, config=config)
740
def get_inventory(self, revision_id):
742
return self._real_repository.get_inventory(revision_id)
744
def iter_inventories(self, revision_ids):
746
return self._real_repository.iter_inventories(revision_ids)
749
def get_revision(self, revision_id):
751
return self._real_repository.get_revision(revision_id)
753
def get_transaction(self):
755
return self._real_repository.get_transaction()
758
def clone(self, a_bzrdir, revision_id=None):
760
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
762
def make_working_trees(self):
763
"""See Repository.make_working_trees"""
765
return self._real_repository.make_working_trees()
767
def revision_ids_to_search_result(self, result_set):
768
"""Convert a set of revision ids to a graph SearchResult."""
769
result_parents = set()
770
for parents in self.get_graph().get_parent_map(
771
result_set).itervalues():
772
result_parents.update(parents)
773
included_keys = result_set.intersection(result_parents)
774
start_keys = result_set.difference(included_keys)
775
exclude_keys = result_parents.difference(result_set)
776
result = graph.SearchResult(start_keys, exclude_keys,
777
len(result_set), result_set)
781
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
782
"""Return the revision ids that other has that this does not.
784
These are returned in topological order.
786
revision_id: only return revision ids included by revision_id.
788
return repository.InterRepository.get(
789
other, self).search_missing_revision_ids(revision_id, find_ghosts)
791
def fetch(self, source, revision_id=None, pb=None):
792
if self.has_same_location(source):
793
# check that last_revision is in 'from' and then return a
795
if (revision_id is not None and
796
not revision.is_null(revision_id)):
797
self.get_revision(revision_id)
800
return self._real_repository.fetch(
801
source, revision_id=revision_id, pb=pb)
803
def create_bundle(self, target, base, fileobj, format=None):
805
self._real_repository.create_bundle(target, base, fileobj, format)
808
def get_ancestry(self, revision_id, topo_sorted=True):
810
return self._real_repository.get_ancestry(revision_id, topo_sorted)
812
def fileids_altered_by_revision_ids(self, revision_ids):
814
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
816
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
818
return self._real_repository._get_versioned_file_checker(
819
revisions, revision_versions_cache)
821
def iter_files_bytes(self, desired_files):
822
"""See Repository.iter_file_bytes.
825
return self._real_repository.iter_files_bytes(desired_files)
827
def get_parent_map(self, keys):
828
"""See bzrlib.Graph.get_parent_map()."""
829
# Hack to build up the caching logic.
830
ancestry = self._parents_map
832
# Repository is not locked, so there's no cache.
833
missing_revisions = set(keys)
836
missing_revisions = set(key for key in keys if key not in ancestry)
837
if missing_revisions:
838
parent_map = self._get_parent_map(missing_revisions)
839
if 'hpss' in debug.debug_flags:
840
mutter('retransmitted revisions: %d of %d',
841
len(set(ancestry).intersection(parent_map)),
843
ancestry.update(parent_map)
844
present_keys = [k for k in keys if k in ancestry]
845
if 'hpss' in debug.debug_flags:
846
if self._requested_parents is not None and len(ancestry) != 0:
847
self._requested_parents.update(present_keys)
848
mutter('Current RemoteRepository graph hit rate: %d%%',
849
100.0 * len(self._requested_parents) / len(ancestry))
850
return dict((k, ancestry[k]) for k in present_keys)
852
def _get_parent_map(self, keys):
853
"""Helper for get_parent_map that performs the RPC."""
854
medium = self._client._medium
855
if medium._is_remote_before((1, 2)):
856
# We already found out that the server can't understand
857
# Repository.get_parent_map requests, so just fetch the whole
859
# XXX: Note that this will issue a deprecation warning. This is ok
860
# :- its because we're working with a deprecated server anyway, and
861
# the user will almost certainly have seen a warning about the
862
# server version already.
863
rg = self.get_revision_graph()
864
# There is an api discrepency between get_parent_map and
865
# get_revision_graph. Specifically, a "key:()" pair in
866
# get_revision_graph just means a node has no parents. For
867
# "get_parent_map" it means the node is a ghost. So fix up the
868
# graph to correct this.
869
# https://bugs.launchpad.net/bzr/+bug/214894
870
# There is one other "bug" which is that ghosts in
871
# get_revision_graph() are not returned at all. But we won't worry
872
# about that for now.
873
for node_id, parent_ids in rg.iteritems():
875
rg[node_id] = (NULL_REVISION,)
876
rg[NULL_REVISION] = ()
881
raise ValueError('get_parent_map(None) is not valid')
882
if NULL_REVISION in keys:
883
keys.discard(NULL_REVISION)
884
found_parents = {NULL_REVISION:()}
889
# TODO(Needs analysis): We could assume that the keys being requested
890
# from get_parent_map are in a breadth first search, so typically they
891
# will all be depth N from some common parent, and we don't have to
892
# have the server iterate from the root parent, but rather from the
893
# keys we're searching; and just tell the server the keyspace we
894
# already have; but this may be more traffic again.
896
# Transform self._parents_map into a search request recipe.
897
# TODO: Manage this incrementally to avoid covering the same path
898
# repeatedly. (The server will have to on each request, but the less
899
# work done the better).
900
parents_map = self._parents_map
901
if parents_map is None:
902
# Repository is not locked, so there's no cache.
904
start_set = set(parents_map)
905
result_parents = set()
906
for parents in parents_map.itervalues():
907
result_parents.update(parents)
908
stop_keys = result_parents.difference(start_set)
909
included_keys = start_set.intersection(result_parents)
910
start_set.difference_update(included_keys)
911
recipe = (start_set, stop_keys, len(parents_map))
912
body = self._serialise_search_recipe(recipe)
913
path = self.bzrdir._path_for_remote_call(self._client)
915
if type(key) is not str:
917
"key %r not a plain string" % (key,))
918
verb = 'Repository.get_parent_map'
919
args = (path,) + tuple(keys)
921
response = self._client.call_with_body_bytes_expecting_body(
922
verb, args, self._serialise_search_recipe(recipe))
923
except errors.UnknownSmartMethod:
924
# Server does not support this method, so get the whole graph.
925
# Worse, we have to force a disconnection, because the server now
926
# doesn't realise it has a body on the wire to consume, so the
927
# only way to recover is to abandon the connection.
929
'Server is too old for fast get_parent_map, reconnecting. '
930
'(Upgrade the server to Bazaar 1.2 to avoid this)')
932
# To avoid having to disconnect repeatedly, we keep track of the
933
# fact the server doesn't understand remote methods added in 1.2.
934
medium._remember_remote_is_before((1, 2))
935
return self.get_revision_graph(None)
936
response_tuple, response_handler = response
937
if response_tuple[0] not in ['ok']:
938
response_handler.cancel_read_body()
939
raise errors.UnexpectedSmartServerResponse(response_tuple)
940
if response_tuple[0] == 'ok':
941
coded = bz2.decompress(response_handler.read_body_bytes())
945
lines = coded.split('\n')
948
d = tuple(line.split())
950
revision_graph[d[0]] = d[1:]
952
# No parents - so give the Graph result (NULL_REVISION,).
953
revision_graph[d[0]] = (NULL_REVISION,)
954
return revision_graph
957
def get_signature_text(self, revision_id):
959
return self._real_repository.get_signature_text(revision_id)
962
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
963
def get_revision_graph_with_ghosts(self, revision_ids=None):
965
return self._real_repository.get_revision_graph_with_ghosts(
966
revision_ids=revision_ids)
969
def get_inventory_xml(self, revision_id):
971
return self._real_repository.get_inventory_xml(revision_id)
973
def deserialise_inventory(self, revision_id, xml):
975
return self._real_repository.deserialise_inventory(revision_id, xml)
977
def reconcile(self, other=None, thorough=False):
979
return self._real_repository.reconcile(other=other, thorough=thorough)
981
def all_revision_ids(self):
983
return self._real_repository.all_revision_ids()
986
def get_deltas_for_revisions(self, revisions):
988
return self._real_repository.get_deltas_for_revisions(revisions)
991
def get_revision_delta(self, revision_id):
993
return self._real_repository.get_revision_delta(revision_id)
996
def revision_trees(self, revision_ids):
998
return self._real_repository.revision_trees(revision_ids)
1001
def get_revision_reconcile(self, revision_id):
1003
return self._real_repository.get_revision_reconcile(revision_id)
1006
def check(self, revision_ids=None):
1008
return self._real_repository.check(revision_ids=revision_ids)
1010
def copy_content_into(self, destination, revision_id=None):
1012
return self._real_repository.copy_content_into(
1013
destination, revision_id=revision_id)
1015
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1016
# get a tarball of the remote repository, and copy from that into the
1018
from bzrlib import osutils
1021
# TODO: Maybe a progress bar while streaming the tarball?
1022
note("Copying repository content as tarball...")
1023
tar_file = self._get_tarball('bz2')
1024
if tar_file is None:
1026
destination = to_bzrdir.create_repository()
1028
tar = tarfile.open('repository', fileobj=tar_file,
1030
tmpdir = tempfile.mkdtemp()
1032
_extract_tar(tar, tmpdir)
1033
tmp_bzrdir = BzrDir.open(tmpdir)
1034
tmp_repo = tmp_bzrdir.open_repository()
1035
tmp_repo.copy_content_into(destination, revision_id)
1037
osutils.rmtree(tmpdir)
1041
# TODO: Suggestion from john: using external tar is much faster than
1042
# python's tarfile library, but it may not work on windows.
1045
def inventories(self):
1046
"""Decorate the real repository for now.
1048
In the long term a full blown network facility is needed to
1049
avoid creating a real repository object locally.
1052
return self._real_repository.inventories
1056
"""Compress the data within the repository.
1058
This is not currently implemented within the smart server.
1061
return self._real_repository.pack()
1064
def revisions(self):
1065
"""Decorate the real repository for now.
1067
In the short term this should become a real object to intercept graph
1070
In the long term a full blown network facility is needed.
1073
return self._real_repository.revisions
1075
def set_make_working_trees(self, new_value):
1077
self._real_repository.set_make_working_trees(new_value)
1080
def signatures(self):
1081
"""Decorate the real repository for now.
1083
In the long term a full blown network facility is needed to avoid
1084
creating a real repository object locally.
1087
return self._real_repository.signatures
1090
def sign_revision(self, revision_id, gpg_strategy):
1092
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1096
"""Decorate the real repository for now.
1098
In the long term a full blown network facility is needed to avoid
1099
creating a real repository object locally.
1102
return self._real_repository.texts
1105
def get_revisions(self, revision_ids):
1107
return self._real_repository.get_revisions(revision_ids)
1109
def supports_rich_root(self):
1111
return self._real_repository.supports_rich_root()
1113
def iter_reverse_revision_history(self, revision_id):
1115
return self._real_repository.iter_reverse_revision_history(revision_id)
1118
def _serializer(self):
1120
return self._real_repository._serializer
1122
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1124
return self._real_repository.store_revision_signature(
1125
gpg_strategy, plaintext, revision_id)
1127
def add_signature_text(self, revision_id, signature):
1129
return self._real_repository.add_signature_text(revision_id, signature)
1131
def has_signature_for_revision_id(self, revision_id):
1133
return self._real_repository.has_signature_for_revision_id(revision_id)
1135
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1137
return self._real_repository.item_keys_introduced_by(revision_ids,
1138
_files_pb=_files_pb)
1140
def revision_graph_can_have_wrong_parents(self):
1141
# The answer depends on the remote repo format.
1143
return self._real_repository.revision_graph_can_have_wrong_parents()
1145
def _find_inconsistent_revision_parents(self):
1147
return self._real_repository._find_inconsistent_revision_parents()
1149
def _check_for_inconsistent_revision_parents(self):
1151
return self._real_repository._check_for_inconsistent_revision_parents()
1153
def _make_parents_provider(self):
1156
def _serialise_search_recipe(self, recipe):
1157
"""Serialise a graph search recipe.
1159
:param recipe: A search recipe (start, stop, count).
1160
:return: Serialised bytes.
1162
start_keys = ' '.join(recipe[0])
1163
stop_keys = ' '.join(recipe[1])
1164
count = str(recipe[2])
1165
return '\n'.join((start_keys, stop_keys, count))
1168
class RemoteBranchLockableFiles(LockableFiles):
1169
"""A 'LockableFiles' implementation that talks to a smart server.
1171
This is not a public interface class.
1174
def __init__(self, bzrdir, _client):
1175
self.bzrdir = bzrdir
1176
self._client = _client
1177
self._need_find_modes = True
1178
LockableFiles.__init__(
1179
self, bzrdir.get_branch_transport(None),
1180
'lock', lockdir.LockDir)
1182
def _find_modes(self):
1183
# RemoteBranches don't let the client set the mode of control files.
1184
self._dir_mode = None
1185
self._file_mode = None
1188
class RemoteBranchFormat(branch.BranchFormat):
1190
def __eq__(self, other):
1191
return (isinstance(other, RemoteBranchFormat) and
1192
self.__dict__ == other.__dict__)
1194
def get_format_description(self):
1195
return 'Remote BZR Branch'
1197
def get_format_string(self):
1198
return 'Remote BZR Branch'
1200
def open(self, a_bzrdir):
1201
return a_bzrdir.open_branch()
1203
def initialize(self, a_bzrdir):
1204
return a_bzrdir.create_branch()
1206
def supports_tags(self):
1207
# Remote branches might support tags, but we won't know until we
1208
# access the real remote branch.
1212
class RemoteBranch(branch.Branch):
1213
"""Branch stored on a server accessed by HPSS RPC.
1215
At the moment most operations are mapped down to simple file operations.
1218
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1220
"""Create a RemoteBranch instance.
1222
:param real_branch: An optional local implementation of the branch
1223
format, usually accessing the data via the VFS.
1224
:param _client: Private parameter for testing.
1226
# We intentionally don't call the parent class's __init__, because it
1227
# will try to assign to self.tags, which is a property in this subclass.
1228
# And the parent's __init__ doesn't do much anyway.
1229
self._revision_id_to_revno_cache = None
1230
self._revision_history_cache = None
1231
self._last_revision_info_cache = None
1232
self.bzrdir = remote_bzrdir
1233
if _client is not None:
1234
self._client = _client
1236
self._client = remote_bzrdir._client
1237
self.repository = remote_repository
1238
if real_branch is not None:
1239
self._real_branch = real_branch
1240
# Give the remote repository the matching real repo.
1241
real_repo = self._real_branch.repository
1242
if isinstance(real_repo, RemoteRepository):
1243
real_repo._ensure_real()
1244
real_repo = real_repo._real_repository
1245
self.repository._set_real_repository(real_repo)
1246
# Give the branch the remote repository to let fast-pathing happen.
1247
self._real_branch.repository = self.repository
1249
self._real_branch = None
1250
# Fill out expected attributes of branch for bzrlib api users.
1251
self._format = RemoteBranchFormat()
1252
self.base = self.bzrdir.root_transport.base
1253
self._control_files = None
1254
self._lock_mode = None
1255
self._lock_token = None
1256
self._repo_lock_token = None
1257
self._lock_count = 0
1258
self._leave_lock = False
1260
def _get_real_transport(self):
1261
# if we try vfs access, return the real branch's vfs transport
1263
return self._real_branch._transport
1265
_transport = property(_get_real_transport)
1268
return "%s(%s)" % (self.__class__.__name__, self.base)
1272
def _ensure_real(self):
1273
"""Ensure that there is a _real_branch set.
1275
Used before calls to self._real_branch.
1277
if self._real_branch is None:
1278
if not vfs.vfs_enabled():
1279
raise AssertionError('smart server vfs must be enabled '
1280
'to use vfs implementation')
1281
self.bzrdir._ensure_real()
1282
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1283
# Give the remote repository the matching real repo.
1284
real_repo = self._real_branch.repository
1285
if isinstance(real_repo, RemoteRepository):
1286
real_repo._ensure_real()
1287
real_repo = real_repo._real_repository
1288
self.repository._set_real_repository(real_repo)
1289
# Give the branch the remote repository to let fast-pathing happen.
1290
self._real_branch.repository = self.repository
1291
# XXX: deal with _lock_mode == 'w'
1292
if self._lock_mode == 'r':
1293
self._real_branch.lock_read()
1295
def _clear_cached_state(self):
1296
super(RemoteBranch, self)._clear_cached_state()
1297
if self._real_branch is not None:
1298
self._real_branch._clear_cached_state()
1300
def _clear_cached_state_of_remote_branch_only(self):
1301
"""Like _clear_cached_state, but doesn't clear the cache of
1304
This is useful when falling back to calling a method of
1305
self._real_branch that changes state. In that case the underlying
1306
branch changes, so we need to invalidate this RemoteBranch's cache of
1307
it. However, there's no need to invalidate the _real_branch's cache
1308
too, in fact doing so might harm performance.
1310
super(RemoteBranch, self)._clear_cached_state()
1313
def control_files(self):
1314
# Defer actually creating RemoteBranchLockableFiles until its needed,
1315
# because it triggers an _ensure_real that we otherwise might not need.
1316
if self._control_files is None:
1317
self._control_files = RemoteBranchLockableFiles(
1318
self.bzrdir, self._client)
1319
return self._control_files
1321
def _get_checkout_format(self):
1323
return self._real_branch._get_checkout_format()
1325
def get_physical_lock_status(self):
1326
"""See Branch.get_physical_lock_status()."""
1327
# should be an API call to the server, as branches must be lockable.
1329
return self._real_branch.get_physical_lock_status()
1331
def get_stacked_on_url(self):
1332
"""Get the URL this branch is stacked against.
1334
:raises NotStacked: If the branch is not stacked.
1335
:raises UnstackableBranchFormat: If the branch does not support
1337
:raises UnstackableRepositoryFormat: If the repository does not support
1341
return self._real_branch.get_stacked_on_url()
1343
def lock_read(self):
1344
if not self._lock_mode:
1345
self._lock_mode = 'r'
1346
self._lock_count = 1
1347
if self._real_branch is not None:
1348
self._real_branch.lock_read()
1350
self._lock_count += 1
1352
def _remote_lock_write(self, token):
1354
branch_token = repo_token = ''
1356
branch_token = token
1357
repo_token = self.repository.lock_write()
1358
self.repository.unlock()
1359
path = self.bzrdir._path_for_remote_call(self._client)
1361
response = self._client.call(
1362
'Branch.lock_write', path, branch_token, repo_token or '')
1363
except errors.ErrorFromSmartServer, err:
1364
if err.error_verb == 'LockContention':
1365
raise errors.LockContention('(remote lock)')
1366
elif err.error_verb == 'TokenMismatch':
1367
raise errors.TokenMismatch(token, '(remote token)')
1368
elif err.error_verb == 'UnlockableTransport':
1369
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1370
elif err.error_verb == 'ReadOnlyError':
1371
raise errors.ReadOnlyError(self)
1372
elif err.error_verb == 'LockFailed':
1373
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1375
if response[0] != 'ok':
1376
raise errors.UnexpectedSmartServerResponse(response)
1377
ok, branch_token, repo_token = response
1378
return branch_token, repo_token
1380
def lock_write(self, token=None):
1381
if not self._lock_mode:
1382
remote_tokens = self._remote_lock_write(token)
1383
self._lock_token, self._repo_lock_token = remote_tokens
1384
if not self._lock_token:
1385
raise SmartProtocolError('Remote server did not return a token!')
1386
# TODO: We really, really, really don't want to call _ensure_real
1387
# here, but it's the easiest way to ensure coherency between the
1388
# state of the RemoteBranch and RemoteRepository objects and the
1389
# physical locks. If we don't materialise the real objects here,
1390
# then getting everything in the right state later is complex, so
1391
# for now we just do it the lazy way.
1392
# -- Andrew Bennetts, 2007-02-22.
1394
if self._real_branch is not None:
1395
self._real_branch.repository.lock_write(
1396
token=self._repo_lock_token)
1398
self._real_branch.lock_write(token=self._lock_token)
1400
self._real_branch.repository.unlock()
1401
if token is not None:
1402
self._leave_lock = True
1404
# XXX: this case seems to be unreachable; token cannot be None.
1405
self._leave_lock = False
1406
self._lock_mode = 'w'
1407
self._lock_count = 1
1408
elif self._lock_mode == 'r':
1409
raise errors.ReadOnlyTransaction
1411
if token is not None:
1412
# A token was given to lock_write, and we're relocking, so check
1413
# that the given token actually matches the one we already have.
1414
if token != self._lock_token:
1415
raise errors.TokenMismatch(token, self._lock_token)
1416
self._lock_count += 1
1417
return self._lock_token or None
1419
def _unlock(self, branch_token, repo_token):
1420
path = self.bzrdir._path_for_remote_call(self._client)
1422
response = self._client.call('Branch.unlock', path, branch_token,
1424
except errors.ErrorFromSmartServer, err:
1425
if err.error_verb == 'TokenMismatch':
1426
raise errors.TokenMismatch(
1427
str((branch_token, repo_token)), '(remote tokens)')
1429
if response == ('ok',):
1431
raise errors.UnexpectedSmartServerResponse(response)
1434
self._lock_count -= 1
1435
if not self._lock_count:
1436
self._clear_cached_state()
1437
mode = self._lock_mode
1438
self._lock_mode = None
1439
if self._real_branch is not None:
1440
if (not self._leave_lock and mode == 'w' and
1441
self._repo_lock_token):
1442
# If this RemoteBranch will remove the physical lock for the
1443
# repository, make sure the _real_branch doesn't do it
1444
# first. (Because the _real_branch's repository is set to
1445
# be the RemoteRepository.)
1446
self._real_branch.repository.leave_lock_in_place()
1447
self._real_branch.unlock()
1449
# Only write-locked branched need to make a remote method call
1450
# to perfom the unlock.
1452
if not self._lock_token:
1453
raise AssertionError('Locked, but no token!')
1454
branch_token = self._lock_token
1455
repo_token = self._repo_lock_token
1456
self._lock_token = None
1457
self._repo_lock_token = None
1458
if not self._leave_lock:
1459
self._unlock(branch_token, repo_token)
1461
def break_lock(self):
1463
return self._real_branch.break_lock()
1465
def leave_lock_in_place(self):
1466
if not self._lock_token:
1467
raise NotImplementedError(self.leave_lock_in_place)
1468
self._leave_lock = True
1470
def dont_leave_lock_in_place(self):
1471
if not self._lock_token:
1472
raise NotImplementedError(self.dont_leave_lock_in_place)
1473
self._leave_lock = False
1475
def _last_revision_info(self):
1476
path = self.bzrdir._path_for_remote_call(self._client)
1477
response = self._client.call('Branch.last_revision_info', path)
1478
if response[0] != 'ok':
1479
raise SmartProtocolError('unexpected response code %s' % (response,))
1480
revno = int(response[1])
1481
last_revision = response[2]
1482
return (revno, last_revision)
1484
def _gen_revision_history(self):
1485
"""See Branch._gen_revision_history()."""
1486
path = self.bzrdir._path_for_remote_call(self._client)
1487
response_tuple, response_handler = self._client.call_expecting_body(
1488
'Branch.revision_history', path)
1489
if response_tuple[0] != 'ok':
1490
raise errors.UnexpectedSmartServerResponse(response_tuple)
1491
result = response_handler.read_body_bytes().split('\x00')
1496
def _set_last_revision_descendant(self, revision_id, other_branch,
1497
allow_diverged=False, allow_overwrite_descendant=False):
1498
path = self.bzrdir._path_for_remote_call(self._client)
1500
response = self._client.call('Branch.set_last_revision_ex',
1501
path, self._lock_token, self._repo_lock_token, revision_id,
1502
int(allow_diverged), int(allow_overwrite_descendant))
1503
except errors.ErrorFromSmartServer, err:
1504
if err.error_verb == 'NoSuchRevision':
1505
raise NoSuchRevision(self, revision_id)
1506
elif err.error_verb == 'Diverged':
1507
raise errors.DivergedBranches(self, other_branch)
1509
self._clear_cached_state()
1510
if len(response) != 3 and response[0] != 'ok':
1511
raise errors.UnexpectedSmartServerResponse(response)
1512
new_revno, new_revision_id = response[1:]
1513
self._last_revision_info_cache = new_revno, new_revision_id
1514
self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1516
def _set_last_revision(self, revision_id):
1517
path = self.bzrdir._path_for_remote_call(self._client)
1518
self._clear_cached_state()
1520
response = self._client.call('Branch.set_last_revision',
1521
path, self._lock_token, self._repo_lock_token, revision_id)
1522
except errors.ErrorFromSmartServer, err:
1523
if err.error_verb == 'NoSuchRevision':
1524
raise NoSuchRevision(self, revision_id)
1526
if response != ('ok',):
1527
raise errors.UnexpectedSmartServerResponse(response)
1530
def set_revision_history(self, rev_history):
1531
# Send just the tip revision of the history; the server will generate
1532
# the full history from that. If the revision doesn't exist in this
1533
# branch, NoSuchRevision will be raised.
1534
if rev_history == []:
1537
rev_id = rev_history[-1]
1538
self._set_last_revision(rev_id)
1539
self._cache_revision_history(rev_history)
1541
def get_parent(self):
1543
return self._real_branch.get_parent()
1545
def set_parent(self, url):
1547
return self._real_branch.set_parent(url)
1549
def set_stacked_on_url(self, stacked_location):
1550
"""Set the URL this branch is stacked against.
1552
:raises UnstackableBranchFormat: If the branch does not support
1554
:raises UnstackableRepositoryFormat: If the repository does not support
1558
return self._real_branch.set_stacked_on_url(stacked_location)
1560
def sprout(self, to_bzrdir, revision_id=None):
1561
# Like Branch.sprout, except that it sprouts a branch in the default
1562
# format, because RemoteBranches can't be created at arbitrary URLs.
1563
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1564
# to_bzrdir.create_branch...
1566
result = self._real_branch._format.initialize(to_bzrdir)
1567
self.copy_content_into(result, revision_id=revision_id)
1568
result.set_parent(self.bzrdir.root_transport.base)
1572
def pull(self, source, overwrite=False, stop_revision=None,
1574
self._clear_cached_state_of_remote_branch_only()
1576
return self._real_branch.pull(
1577
source, overwrite=overwrite, stop_revision=stop_revision,
1578
_override_hook_target=self, **kwargs)
1581
def push(self, target, overwrite=False, stop_revision=None):
1583
return self._real_branch.push(
1584
target, overwrite=overwrite, stop_revision=stop_revision,
1585
_override_hook_source_branch=self)
1587
def is_locked(self):
1588
return self._lock_count >= 1
1591
def set_last_revision_info(self, revno, revision_id):
1592
revision_id = ensure_null(revision_id)
1593
path = self.bzrdir._path_for_remote_call(self._client)
1595
response = self._client.call('Branch.set_last_revision_info',
1596
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1597
except errors.UnknownSmartMethod:
1599
self._clear_cached_state_of_remote_branch_only()
1600
self._real_branch.set_last_revision_info(revno, revision_id)
1601
self._last_revision_info_cache = revno, revision_id
1603
except errors.ErrorFromSmartServer, err:
1604
if err.error_verb == 'NoSuchRevision':
1605
raise NoSuchRevision(self, err.error_args[0])
1607
if response == ('ok',):
1608
self._clear_cached_state()
1609
self._last_revision_info_cache = revno, revision_id
1610
# Update the _real_branch's cache too.
1611
if self._real_branch is not None:
1612
cache = self._last_revision_info_cache
1613
self._real_branch._last_revision_info_cache = cache
1615
raise errors.UnexpectedSmartServerResponse(response)
1618
def generate_revision_history(self, revision_id, last_rev=None,
1620
medium = self._client._medium
1621
if not medium._is_remote_before((1, 6)):
1623
self._set_last_revision_descendant(revision_id, other_branch,
1624
allow_diverged=True, allow_overwrite_descendant=True)
1626
except errors.UnknownSmartMethod:
1627
medium._remember_remote_is_before((1, 6))
1628
self._clear_cached_state_of_remote_branch_only()
1630
self._real_branch.generate_revision_history(
1631
revision_id, last_rev=last_rev, other_branch=other_branch)
1636
return self._real_branch.tags
1638
def set_push_location(self, location):
1640
return self._real_branch.set_push_location(location)
1643
def update_revisions(self, other, stop_revision=None, overwrite=False,
1645
"""See Branch.update_revisions."""
1648
if stop_revision is None:
1649
stop_revision = other.last_revision()
1650
if revision.is_null(stop_revision):
1651
# if there are no commits, we're done.
1653
self.fetch(other, stop_revision)
1656
# Just unconditionally set the new revision. We don't care if
1657
# the branches have diverged.
1658
self._set_last_revision(stop_revision)
1660
medium = self._client._medium
1661
if not medium._is_remote_before((1, 6)):
1663
self._set_last_revision_descendant(stop_revision, other)
1665
except errors.UnknownSmartMethod:
1666
medium._remember_remote_is_before((1, 6))
1667
# Fallback for pre-1.6 servers: check for divergence
1668
# client-side, then do _set_last_revision.
1669
last_rev = revision.ensure_null(self.last_revision())
1671
graph = self.repository.get_graph()
1672
if self._check_if_descendant_or_diverged(
1673
stop_revision, last_rev, graph, other):
1674
# stop_revision is a descendant of last_rev, but we aren't
1675
# overwriting, so we're done.
1677
self._set_last_revision(stop_revision)
1682
def _extract_tar(tar, to_dir):
1683
"""Extract all the contents of a tarfile object.
1685
A replacement for extractall, which is not present in python2.4
1688
tar.extract(tarinfo, to_dir)