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
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.config import BranchConfig, TreeConfig
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.pack import ContainerPushParser
44
from bzrlib.smart import client, vfs
45
from bzrlib.symbol_versioning import (
49
from bzrlib.revision import ensure_null, NULL_REVISION
50
from bzrlib.trace import mutter, note, warning
53
# Note: RemoteBzrDirFormat is in bzrdir.py
55
class RemoteBzrDir(BzrDir):
56
"""Control directory on a remote server, accessed via bzr:// or similar."""
58
def __init__(self, transport, _client=None):
59
"""Construct a RemoteBzrDir.
61
:param _client: Private parameter for testing. Disables probing and the
64
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
65
# this object holds a delegated bzrdir that uses file-level operations
66
# to talk to the other side
67
self._real_bzrdir = None
70
medium = transport.get_smart_medium()
71
self._client = client._SmartClient(medium)
73
self._client = _client
76
path = self._path_for_remote_call(self._client)
77
response = self._client.call('BzrDir.open', path)
78
if response not in [('yes',), ('no',)]:
79
raise errors.UnexpectedSmartServerResponse(response)
80
if response == ('no',):
81
raise errors.NotBranchError(path=transport.base)
83
def _ensure_real(self):
84
"""Ensure that there is a _real_bzrdir set.
86
Used before calls to self._real_bzrdir.
88
if not self._real_bzrdir:
89
self._real_bzrdir = BzrDir.open_from_transport(
90
self.root_transport, _server_formats=False)
92
def cloning_metadir(self, stacked=False):
94
return self._real_bzrdir.cloning_metadir(stacked)
96
def _translate_error(self, err, **context):
97
_translate_error(err, bzrdir=self, **context)
99
def create_repository(self, shared=False):
101
self._real_bzrdir.create_repository(shared=shared)
102
return self.open_repository()
104
def destroy_repository(self):
105
"""See BzrDir.destroy_repository"""
107
self._real_bzrdir.destroy_repository()
109
def create_branch(self):
111
real_branch = self._real_bzrdir.create_branch()
112
return RemoteBranch(self, self.find_repository(), real_branch)
114
def destroy_branch(self):
115
"""See BzrDir.destroy_branch"""
117
self._real_bzrdir.destroy_branch()
119
def create_workingtree(self, revision_id=None, from_branch=None):
120
raise errors.NotLocalUrl(self.transport.base)
122
def find_branch_format(self):
123
"""Find the branch 'format' for this bzrdir.
125
This might be a synthetic object for e.g. RemoteBranch and SVN.
127
b = self.open_branch()
130
def get_branch_reference(self):
131
"""See BzrDir.get_branch_reference()."""
132
path = self._path_for_remote_call(self._client)
134
response = self._client.call('BzrDir.open_branch', path)
135
except errors.ErrorFromSmartServer, err:
136
self._translate_error(err)
137
if response[0] == 'ok':
138
if response[1] == '':
139
# branch at this location.
142
# a branch reference, use the existing BranchReference logic.
145
raise errors.UnexpectedSmartServerResponse(response)
147
def _get_tree_branch(self):
148
"""See BzrDir._get_tree_branch()."""
149
return None, self.open_branch()
151
def open_branch(self, _unsupported=False):
153
raise NotImplementedError('unsupported flag support not implemented yet.')
154
reference_url = self.get_branch_reference()
155
if reference_url is None:
156
# branch at this location.
157
return RemoteBranch(self, self.find_repository())
159
# a branch reference, use the existing BranchReference logic.
160
format = BranchReferenceFormat()
161
return format.open(self, _found=True, location=reference_url)
163
def open_repository(self):
164
path = self._path_for_remote_call(self._client)
165
verb = 'BzrDir.find_repositoryV2'
168
response = self._client.call(verb, path)
169
except errors.UnknownSmartMethod:
170
verb = 'BzrDir.find_repository'
171
response = self._client.call(verb, path)
172
except errors.ErrorFromSmartServer, err:
173
self._translate_error(err)
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 _translate_error(self, err, **context):
366
self.bzrdir._translate_error(err, repository=self, **context)
368
def find_text_key_references(self):
369
"""Find the text key references within the repository.
371
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
372
revision_ids. Each altered file-ids has the exact revision_ids that
373
altered it listed explicitly.
374
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
375
to whether they were referred to by the inventory of the
376
revision_id that they contain. The inventory texts from all present
377
revision ids are assessed to generate this report.
380
return self._real_repository.find_text_key_references()
382
def _generate_text_key_index(self):
383
"""Generate a new text key index for the repository.
385
This is an expensive function that will take considerable time to run.
387
:return: A dict mapping (file_id, revision_id) tuples to a list of
388
parents, also (file_id, revision_id) tuples.
391
return self._real_repository._generate_text_key_index()
393
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
394
def get_revision_graph(self, revision_id=None):
395
"""See Repository.get_revision_graph()."""
396
return self._get_revision_graph(revision_id)
398
def _get_revision_graph(self, revision_id):
399
"""Private method for using with old (< 1.2) servers to fallback."""
400
if revision_id is None:
402
elif revision.is_null(revision_id):
405
path = self.bzrdir._path_for_remote_call(self._client)
407
response = self._client.call_expecting_body(
408
'Repository.get_revision_graph', path, revision_id)
409
except errors.ErrorFromSmartServer, err:
410
self._translate_error(err)
411
response_tuple, response_handler = response
412
if response_tuple[0] != 'ok':
413
raise errors.UnexpectedSmartServerResponse(response_tuple)
414
coded = response_handler.read_body_bytes()
416
# no revisions in this repository!
418
lines = coded.split('\n')
421
d = tuple(line.split())
422
revision_graph[d[0]] = d[1:]
424
return revision_graph
426
def has_revision(self, revision_id):
427
"""See Repository.has_revision()."""
428
if revision_id == NULL_REVISION:
429
# The null revision is always present.
431
path = self.bzrdir._path_for_remote_call(self._client)
432
response = self._client.call(
433
'Repository.has_revision', path, revision_id)
434
if response[0] not in ('yes', 'no'):
435
raise errors.UnexpectedSmartServerResponse(response)
436
if response[0] == 'yes':
438
for fallback_repo in self._fallback_repositories:
439
if fallback_repo.has_revision(revision_id):
443
def has_revisions(self, revision_ids):
444
"""See Repository.has_revisions()."""
445
# FIXME: This does many roundtrips, particularly when there are
446
# fallback repositories. -- mbp 20080905
448
for revision_id in revision_ids:
449
if self.has_revision(revision_id):
450
result.add(revision_id)
453
def has_same_location(self, other):
454
return (self.__class__ == other.__class__ and
455
self.bzrdir.transport.base == other.bzrdir.transport.base)
457
def get_graph(self, other_repository=None):
458
"""Return the graph for this repository format"""
459
parents_provider = self
460
if (other_repository is not None and
461
other_repository.bzrdir.transport.base !=
462
self.bzrdir.transport.base):
463
parents_provider = graph._StackedParentsProvider(
464
[parents_provider, other_repository._make_parents_provider()])
465
return graph.Graph(parents_provider)
467
def gather_stats(self, revid=None, committers=None):
468
"""See Repository.gather_stats()."""
469
path = self.bzrdir._path_for_remote_call(self._client)
470
# revid can be None to indicate no revisions, not just NULL_REVISION
471
if revid is None or revision.is_null(revid):
475
if committers is None or not committers:
476
fmt_committers = 'no'
478
fmt_committers = 'yes'
479
response_tuple, response_handler = self._client.call_expecting_body(
480
'Repository.gather_stats', path, fmt_revid, fmt_committers)
481
if response_tuple[0] != 'ok':
482
raise errors.UnexpectedSmartServerResponse(response_tuple)
484
body = response_handler.read_body_bytes()
486
for line in body.split('\n'):
489
key, val_text = line.split(':')
490
if key in ('revisions', 'size', 'committers'):
491
result[key] = int(val_text)
492
elif key in ('firstrev', 'latestrev'):
493
values = val_text.split(' ')[1:]
494
result[key] = (float(values[0]), long(values[1]))
498
def find_branches(self, using=False):
499
"""See Repository.find_branches()."""
500
# should be an API call to the server.
502
return self._real_repository.find_branches(using=using)
504
def get_physical_lock_status(self):
505
"""See Repository.get_physical_lock_status()."""
506
# should be an API call to the server.
508
return self._real_repository.get_physical_lock_status()
510
def is_in_write_group(self):
511
"""Return True if there is an open write group.
513
write groups are only applicable locally for the smart server..
515
if self._real_repository:
516
return self._real_repository.is_in_write_group()
519
return self._lock_count >= 1
522
"""See Repository.is_shared()."""
523
path = self.bzrdir._path_for_remote_call(self._client)
524
response = self._client.call('Repository.is_shared', path)
525
if response[0] not in ('yes', 'no'):
526
raise SmartProtocolError('unexpected response code %s' % (response,))
527
return response[0] == 'yes'
529
def is_write_locked(self):
530
return self._lock_mode == 'w'
533
# wrong eventually - want a local lock cache context
534
if not self._lock_mode:
535
self._lock_mode = 'r'
537
self._parents_map = {}
538
if 'hpss' in debug.debug_flags:
539
self._requested_parents = set()
540
if self._real_repository is not None:
541
self._real_repository.lock_read()
543
self._lock_count += 1
545
def _remote_lock_write(self, token):
546
path = self.bzrdir._path_for_remote_call(self._client)
550
response = self._client.call('Repository.lock_write', path, token)
551
except errors.ErrorFromSmartServer, err:
552
self._translate_error(err, token=token)
554
if response[0] == 'ok':
558
raise errors.UnexpectedSmartServerResponse(response)
560
def lock_write(self, token=None):
561
if not self._lock_mode:
562
self._lock_token = self._remote_lock_write(token)
563
# if self._lock_token is None, then this is something like packs or
564
# svn where we don't get to lock the repo, or a weave style repository
565
# where we cannot lock it over the wire and attempts to do so will
567
if self._real_repository is not None:
568
self._real_repository.lock_write(token=self._lock_token)
569
if token is not None:
570
self._leave_lock = True
572
self._leave_lock = False
573
self._lock_mode = 'w'
575
self._parents_map = {}
576
if 'hpss' in debug.debug_flags:
577
self._requested_parents = set()
578
elif self._lock_mode == 'r':
579
raise errors.ReadOnlyError(self)
581
self._lock_count += 1
582
return self._lock_token or None
584
def leave_lock_in_place(self):
585
if not self._lock_token:
586
raise NotImplementedError(self.leave_lock_in_place)
587
self._leave_lock = True
589
def dont_leave_lock_in_place(self):
590
if not self._lock_token:
591
raise NotImplementedError(self.dont_leave_lock_in_place)
592
self._leave_lock = False
594
def _set_real_repository(self, repository):
595
"""Set the _real_repository for this repository.
597
:param repository: The repository to fallback to for non-hpss
598
implemented operations.
600
if isinstance(repository, RemoteRepository):
601
raise AssertionError()
602
self._real_repository = repository
603
for fb in self._fallback_repositories:
604
self._real_repository.add_fallback_repository(fb)
605
if self._lock_mode == 'w':
606
# if we are already locked, the real repository must be able to
607
# acquire the lock with our token.
608
self._real_repository.lock_write(self._lock_token)
609
elif self._lock_mode == 'r':
610
self._real_repository.lock_read()
612
def start_write_group(self):
613
"""Start a write group on the decorated repository.
615
Smart methods peform operations in a single step so this api
616
is not really applicable except as a compatibility thunk
617
for older plugins that don't use e.g. the CommitBuilder
621
return self._real_repository.start_write_group()
623
def _unlock(self, token):
624
path = self.bzrdir._path_for_remote_call(self._client)
626
# with no token the remote repository is not persistently locked.
629
response = self._client.call('Repository.unlock', path, token)
630
except errors.ErrorFromSmartServer, err:
631
self._translate_error(err, token=token)
632
if response == ('ok',):
635
raise errors.UnexpectedSmartServerResponse(response)
638
self._lock_count -= 1
639
if self._lock_count > 0:
641
self._parents_map = None
642
if 'hpss' in debug.debug_flags:
643
self._requested_parents = None
644
old_mode = self._lock_mode
645
self._lock_mode = None
647
# The real repository is responsible at present for raising an
648
# exception if it's in an unfinished write group. However, it
649
# normally will *not* actually remove the lock from disk - that's
650
# done by the server on receiving the Repository.unlock call.
651
# This is just to let the _real_repository stay up to date.
652
if self._real_repository is not None:
653
self._real_repository.unlock()
655
# The rpc-level lock should be released even if there was a
656
# problem releasing the vfs-based lock.
658
# Only write-locked repositories need to make a remote method
659
# call to perfom the unlock.
660
old_token = self._lock_token
661
self._lock_token = None
662
if not self._leave_lock:
663
self._unlock(old_token)
665
def break_lock(self):
666
# should hand off to the network
668
return self._real_repository.break_lock()
670
def _get_tarball(self, compression):
671
"""Return a TemporaryFile containing a repository tarball.
673
Returns None if the server does not support sending tarballs.
676
path = self.bzrdir._path_for_remote_call(self._client)
678
response, protocol = self._client.call_expecting_body(
679
'Repository.tarball', path, compression)
680
except errors.UnknownSmartMethod:
681
protocol.cancel_read_body()
683
if response[0] == 'ok':
684
# Extract the tarball and return it
685
t = tempfile.NamedTemporaryFile()
686
# TODO: rpc layer should read directly into it...
687
t.write(protocol.read_body_bytes())
690
raise errors.UnexpectedSmartServerResponse(response)
692
def sprout(self, to_bzrdir, revision_id=None):
693
# TODO: Option to control what format is created?
695
dest_repo = self._real_repository._format.initialize(to_bzrdir,
697
dest_repo.fetch(self, revision_id=revision_id)
700
### These methods are just thin shims to the VFS object for now.
702
def revision_tree(self, revision_id):
704
return self._real_repository.revision_tree(revision_id)
706
def get_serializer_format(self):
708
return self._real_repository.get_serializer_format()
710
def get_commit_builder(self, branch, parents, config, timestamp=None,
711
timezone=None, committer=None, revprops=None,
713
# FIXME: It ought to be possible to call this without immediately
714
# triggering _ensure_real. For now it's the easiest thing to do.
716
builder = self._real_repository.get_commit_builder(branch, parents,
717
config, timestamp=timestamp, timezone=timezone,
718
committer=committer, revprops=revprops, revision_id=revision_id)
721
def add_fallback_repository(self, repository):
722
"""Add a repository to use for looking up data not held locally.
724
:param repository: A repository.
726
# XXX: At the moment the RemoteRepository will allow fallbacks
727
# unconditionally - however, a _real_repository will usually exist,
728
# and may raise an error if it's not accommodated by the underlying
729
# format. Eventually we should check when opening the repository
730
# whether it's willing to allow them or not.
732
# We need to accumulate additional repositories here, to pass them in
734
self._fallback_repositories.append(repository)
735
# They are also seen by the fallback repository. If it doesn't exist
736
# yet they'll be added then. This implicitly copies them.
739
def add_inventory(self, revid, inv, parents):
741
return self._real_repository.add_inventory(revid, inv, parents)
743
def add_revision(self, rev_id, rev, inv=None, config=None):
745
return self._real_repository.add_revision(
746
rev_id, rev, inv=inv, config=config)
749
def get_inventory(self, revision_id):
751
return self._real_repository.get_inventory(revision_id)
753
def iter_inventories(self, revision_ids):
755
return self._real_repository.iter_inventories(revision_ids)
758
def get_revision(self, revision_id):
760
return self._real_repository.get_revision(revision_id)
762
def get_transaction(self):
764
return self._real_repository.get_transaction()
767
def clone(self, a_bzrdir, revision_id=None):
769
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
771
def make_working_trees(self):
772
"""See Repository.make_working_trees"""
774
return self._real_repository.make_working_trees()
776
def revision_ids_to_search_result(self, result_set):
777
"""Convert a set of revision ids to a graph SearchResult."""
778
result_parents = set()
779
for parents in self.get_graph().get_parent_map(
780
result_set).itervalues():
781
result_parents.update(parents)
782
included_keys = result_set.intersection(result_parents)
783
start_keys = result_set.difference(included_keys)
784
exclude_keys = result_parents.difference(result_set)
785
result = graph.SearchResult(start_keys, exclude_keys,
786
len(result_set), result_set)
790
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
791
"""Return the revision ids that other has that this does not.
793
These are returned in topological order.
795
revision_id: only return revision ids included by revision_id.
797
return repository.InterRepository.get(
798
other, self).search_missing_revision_ids(revision_id, find_ghosts)
800
def fetch(self, source, revision_id=None, pb=None):
801
if self.has_same_location(source):
802
# check that last_revision is in 'from' and then return a
804
if (revision_id is not None and
805
not revision.is_null(revision_id)):
806
self.get_revision(revision_id)
809
return self._real_repository.fetch(
810
source, revision_id=revision_id, pb=pb)
812
def create_bundle(self, target, base, fileobj, format=None):
814
self._real_repository.create_bundle(target, base, fileobj, format)
817
def get_ancestry(self, revision_id, topo_sorted=True):
819
return self._real_repository.get_ancestry(revision_id, topo_sorted)
821
def fileids_altered_by_revision_ids(self, revision_ids):
823
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
825
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
827
return self._real_repository._get_versioned_file_checker(
828
revisions, revision_versions_cache)
830
def iter_files_bytes(self, desired_files):
831
"""See Repository.iter_file_bytes.
834
return self._real_repository.iter_files_bytes(desired_files)
837
def _fetch_order(self):
838
"""Decorate the real repository for now.
840
In the long term getting this back from the remote repository as part
841
of open would be more efficient.
844
return self._real_repository._fetch_order
847
def _fetch_uses_deltas(self):
848
"""Decorate the real repository for now.
850
In the long term getting this back from the remote repository as part
851
of open would be more efficient.
854
return self._real_repository._fetch_uses_deltas
857
def _fetch_reconcile(self):
858
"""Decorate the real repository for now.
860
In the long term getting this back from the remote repository as part
861
of open would be more efficient.
864
return self._real_repository._fetch_reconcile
866
def get_parent_map(self, keys):
867
"""See bzrlib.Graph.get_parent_map()."""
868
# Hack to build up the caching logic.
869
ancestry = self._parents_map
871
# Repository is not locked, so there's no cache.
872
missing_revisions = set(keys)
875
missing_revisions = set(key for key in keys if key not in ancestry)
876
if missing_revisions:
877
parent_map = self._get_parent_map(missing_revisions)
878
if 'hpss' in debug.debug_flags:
879
mutter('retransmitted revisions: %d of %d',
880
len(set(ancestry).intersection(parent_map)),
882
ancestry.update(parent_map)
883
present_keys = [k for k in keys if k in ancestry]
884
if 'hpss' in debug.debug_flags:
885
if self._requested_parents is not None and len(ancestry) != 0:
886
self._requested_parents.update(present_keys)
887
mutter('Current RemoteRepository graph hit rate: %d%%',
888
100.0 * len(self._requested_parents) / len(ancestry))
889
return dict((k, ancestry[k]) for k in present_keys)
891
def _get_parent_map(self, keys):
892
"""Helper for get_parent_map that performs the RPC."""
893
medium = self._client._medium
894
if medium._is_remote_before((1, 2)):
895
# We already found out that the server can't understand
896
# Repository.get_parent_map requests, so just fetch the whole
898
# XXX: Note that this will issue a deprecation warning. This is ok
899
# :- its because we're working with a deprecated server anyway, and
900
# the user will almost certainly have seen a warning about the
901
# server version already.
902
rg = self.get_revision_graph()
903
# There is an api discrepency between get_parent_map and
904
# get_revision_graph. Specifically, a "key:()" pair in
905
# get_revision_graph just means a node has no parents. For
906
# "get_parent_map" it means the node is a ghost. So fix up the
907
# graph to correct this.
908
# https://bugs.launchpad.net/bzr/+bug/214894
909
# There is one other "bug" which is that ghosts in
910
# get_revision_graph() are not returned at all. But we won't worry
911
# about that for now.
912
for node_id, parent_ids in rg.iteritems():
914
rg[node_id] = (NULL_REVISION,)
915
rg[NULL_REVISION] = ()
920
raise ValueError('get_parent_map(None) is not valid')
921
if NULL_REVISION in keys:
922
keys.discard(NULL_REVISION)
923
found_parents = {NULL_REVISION:()}
928
# TODO(Needs analysis): We could assume that the keys being requested
929
# from get_parent_map are in a breadth first search, so typically they
930
# will all be depth N from some common parent, and we don't have to
931
# have the server iterate from the root parent, but rather from the
932
# keys we're searching; and just tell the server the keyspace we
933
# already have; but this may be more traffic again.
935
# Transform self._parents_map into a search request recipe.
936
# TODO: Manage this incrementally to avoid covering the same path
937
# repeatedly. (The server will have to on each request, but the less
938
# work done the better).
939
parents_map = self._parents_map
940
if parents_map is None:
941
# Repository is not locked, so there's no cache.
943
start_set = set(parents_map)
944
result_parents = set()
945
for parents in parents_map.itervalues():
946
result_parents.update(parents)
947
stop_keys = result_parents.difference(start_set)
948
included_keys = start_set.intersection(result_parents)
949
start_set.difference_update(included_keys)
950
recipe = (start_set, stop_keys, len(parents_map))
951
body = self._serialise_search_recipe(recipe)
952
path = self.bzrdir._path_for_remote_call(self._client)
954
if type(key) is not str:
956
"key %r not a plain string" % (key,))
957
verb = 'Repository.get_parent_map'
958
args = (path,) + tuple(keys)
960
response = self._client.call_with_body_bytes_expecting_body(
961
verb, args, self._serialise_search_recipe(recipe))
962
except errors.UnknownSmartMethod:
963
# Server does not support this method, so get the whole graph.
964
# Worse, we have to force a disconnection, because the server now
965
# doesn't realise it has a body on the wire to consume, so the
966
# only way to recover is to abandon the connection.
968
'Server is too old for fast get_parent_map, reconnecting. '
969
'(Upgrade the server to Bazaar 1.2 to avoid this)')
971
# To avoid having to disconnect repeatedly, we keep track of the
972
# fact the server doesn't understand remote methods added in 1.2.
973
medium._remember_remote_is_before((1, 2))
974
return self.get_revision_graph(None)
975
response_tuple, response_handler = response
976
if response_tuple[0] not in ['ok']:
977
response_handler.cancel_read_body()
978
raise errors.UnexpectedSmartServerResponse(response_tuple)
979
if response_tuple[0] == 'ok':
980
coded = bz2.decompress(response_handler.read_body_bytes())
984
lines = coded.split('\n')
987
d = tuple(line.split())
989
revision_graph[d[0]] = d[1:]
991
# No parents - so give the Graph result (NULL_REVISION,).
992
revision_graph[d[0]] = (NULL_REVISION,)
993
return revision_graph
996
def get_signature_text(self, revision_id):
998
return self._real_repository.get_signature_text(revision_id)
1001
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1002
def get_revision_graph_with_ghosts(self, revision_ids=None):
1004
return self._real_repository.get_revision_graph_with_ghosts(
1005
revision_ids=revision_ids)
1008
def get_inventory_xml(self, revision_id):
1010
return self._real_repository.get_inventory_xml(revision_id)
1012
def deserialise_inventory(self, revision_id, xml):
1014
return self._real_repository.deserialise_inventory(revision_id, xml)
1016
def reconcile(self, other=None, thorough=False):
1018
return self._real_repository.reconcile(other=other, thorough=thorough)
1020
def all_revision_ids(self):
1022
return self._real_repository.all_revision_ids()
1025
def get_deltas_for_revisions(self, revisions):
1027
return self._real_repository.get_deltas_for_revisions(revisions)
1030
def get_revision_delta(self, revision_id):
1032
return self._real_repository.get_revision_delta(revision_id)
1035
def revision_trees(self, revision_ids):
1037
return self._real_repository.revision_trees(revision_ids)
1040
def get_revision_reconcile(self, revision_id):
1042
return self._real_repository.get_revision_reconcile(revision_id)
1045
def check(self, revision_ids=None):
1047
return self._real_repository.check(revision_ids=revision_ids)
1049
def copy_content_into(self, destination, revision_id=None):
1051
return self._real_repository.copy_content_into(
1052
destination, revision_id=revision_id)
1054
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1055
# get a tarball of the remote repository, and copy from that into the
1057
from bzrlib import osutils
1059
# TODO: Maybe a progress bar while streaming the tarball?
1060
note("Copying repository content as tarball...")
1061
tar_file = self._get_tarball('bz2')
1062
if tar_file is None:
1064
destination = to_bzrdir.create_repository()
1066
tar = tarfile.open('repository', fileobj=tar_file,
1068
tmpdir = osutils.mkdtemp()
1070
_extract_tar(tar, tmpdir)
1071
tmp_bzrdir = BzrDir.open(tmpdir)
1072
tmp_repo = tmp_bzrdir.open_repository()
1073
tmp_repo.copy_content_into(destination, revision_id)
1075
osutils.rmtree(tmpdir)
1079
# TODO: Suggestion from john: using external tar is much faster than
1080
# python's tarfile library, but it may not work on windows.
1083
def inventories(self):
1084
"""Decorate the real repository for now.
1086
In the long term a full blown network facility is needed to
1087
avoid creating a real repository object locally.
1090
return self._real_repository.inventories
1094
"""Compress the data within the repository.
1096
This is not currently implemented within the smart server.
1099
return self._real_repository.pack()
1102
def revisions(self):
1103
"""Decorate the real repository for now.
1105
In the short term this should become a real object to intercept graph
1108
In the long term a full blown network facility is needed.
1111
return self._real_repository.revisions
1113
def set_make_working_trees(self, new_value):
1115
self._real_repository.set_make_working_trees(new_value)
1118
def signatures(self):
1119
"""Decorate the real repository for now.
1121
In the long term a full blown network facility is needed to avoid
1122
creating a real repository object locally.
1125
return self._real_repository.signatures
1128
def sign_revision(self, revision_id, gpg_strategy):
1130
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1134
"""Decorate the real repository for now.
1136
In the long term a full blown network facility is needed to avoid
1137
creating a real repository object locally.
1140
return self._real_repository.texts
1143
def get_revisions(self, revision_ids):
1145
return self._real_repository.get_revisions(revision_ids)
1147
def supports_rich_root(self):
1149
return self._real_repository.supports_rich_root()
1151
def iter_reverse_revision_history(self, revision_id):
1153
return self._real_repository.iter_reverse_revision_history(revision_id)
1156
def _serializer(self):
1158
return self._real_repository._serializer
1160
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1162
return self._real_repository.store_revision_signature(
1163
gpg_strategy, plaintext, revision_id)
1165
def add_signature_text(self, revision_id, signature):
1167
return self._real_repository.add_signature_text(revision_id, signature)
1169
def has_signature_for_revision_id(self, revision_id):
1171
return self._real_repository.has_signature_for_revision_id(revision_id)
1173
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1175
return self._real_repository.item_keys_introduced_by(revision_ids,
1176
_files_pb=_files_pb)
1178
def revision_graph_can_have_wrong_parents(self):
1179
# The answer depends on the remote repo format.
1181
return self._real_repository.revision_graph_can_have_wrong_parents()
1183
def _find_inconsistent_revision_parents(self):
1185
return self._real_repository._find_inconsistent_revision_parents()
1187
def _check_for_inconsistent_revision_parents(self):
1189
return self._real_repository._check_for_inconsistent_revision_parents()
1191
def _make_parents_provider(self):
1194
def _serialise_search_recipe(self, recipe):
1195
"""Serialise a graph search recipe.
1197
:param recipe: A search recipe (start, stop, count).
1198
:return: Serialised bytes.
1200
start_keys = ' '.join(recipe[0])
1201
stop_keys = ' '.join(recipe[1])
1202
count = str(recipe[2])
1203
return '\n'.join((start_keys, stop_keys, count))
1206
class RemoteBranchLockableFiles(LockableFiles):
1207
"""A 'LockableFiles' implementation that talks to a smart server.
1209
This is not a public interface class.
1212
def __init__(self, bzrdir, _client):
1213
self.bzrdir = bzrdir
1214
self._client = _client
1215
self._need_find_modes = True
1216
LockableFiles.__init__(
1217
self, bzrdir.get_branch_transport(None),
1218
'lock', lockdir.LockDir)
1220
def _find_modes(self):
1221
# RemoteBranches don't let the client set the mode of control files.
1222
self._dir_mode = None
1223
self._file_mode = None
1226
class RemoteBranchFormat(branch.BranchFormat):
1228
def __eq__(self, other):
1229
return (isinstance(other, RemoteBranchFormat) and
1230
self.__dict__ == other.__dict__)
1232
def get_format_description(self):
1233
return 'Remote BZR Branch'
1235
def get_format_string(self):
1236
return 'Remote BZR Branch'
1238
def open(self, a_bzrdir):
1239
return a_bzrdir.open_branch()
1241
def initialize(self, a_bzrdir):
1242
return a_bzrdir.create_branch()
1244
def supports_tags(self):
1245
# Remote branches might support tags, but we won't know until we
1246
# access the real remote branch.
1250
class RemoteBranch(branch.Branch):
1251
"""Branch stored on a server accessed by HPSS RPC.
1253
At the moment most operations are mapped down to simple file operations.
1256
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1258
"""Create a RemoteBranch instance.
1260
:param real_branch: An optional local implementation of the branch
1261
format, usually accessing the data via the VFS.
1262
:param _client: Private parameter for testing.
1264
# We intentionally don't call the parent class's __init__, because it
1265
# will try to assign to self.tags, which is a property in this subclass.
1266
# And the parent's __init__ doesn't do much anyway.
1267
self._revision_id_to_revno_cache = None
1268
self._revision_history_cache = None
1269
self._last_revision_info_cache = None
1270
self.bzrdir = remote_bzrdir
1271
if _client is not None:
1272
self._client = _client
1274
self._client = remote_bzrdir._client
1275
self.repository = remote_repository
1276
if real_branch is not None:
1277
self._real_branch = real_branch
1278
# Give the remote repository the matching real repo.
1279
real_repo = self._real_branch.repository
1280
if isinstance(real_repo, RemoteRepository):
1281
real_repo._ensure_real()
1282
real_repo = real_repo._real_repository
1283
self.repository._set_real_repository(real_repo)
1284
# Give the branch the remote repository to let fast-pathing happen.
1285
self._real_branch.repository = self.repository
1287
self._real_branch = None
1288
# Fill out expected attributes of branch for bzrlib api users.
1289
self._format = RemoteBranchFormat()
1290
self.base = self.bzrdir.root_transport.base
1291
self._control_files = None
1292
self._lock_mode = None
1293
self._lock_token = None
1294
self._repo_lock_token = None
1295
self._lock_count = 0
1296
self._leave_lock = False
1297
self._setup_stacking()
1299
def _setup_stacking(self):
1300
# configure stacking into the remote repository, by reading it from
1303
fallback_url = self.get_stacked_on_url()
1304
except (errors.NotStacked, errors.UnstackableBranchFormat,
1305
errors.UnstackableRepositoryFormat), e:
1307
# it's relative to this branch...
1308
fallback_url = urlutils.join(self.base, fallback_url)
1309
transports = [self.bzrdir.root_transport]
1310
if self._real_branch is not None:
1311
transports.append(self._real_branch._transport)
1312
fallback_bzrdir = BzrDir.open(fallback_url, transports)
1313
fallback_repo = fallback_bzrdir.open_repository()
1314
self.repository.add_fallback_repository(fallback_repo)
1316
def _get_real_transport(self):
1317
# if we try vfs access, return the real branch's vfs transport
1319
return self._real_branch._transport
1321
_transport = property(_get_real_transport)
1324
return "%s(%s)" % (self.__class__.__name__, self.base)
1328
def _ensure_real(self):
1329
"""Ensure that there is a _real_branch set.
1331
Used before calls to self._real_branch.
1333
if self._real_branch is None:
1334
if not vfs.vfs_enabled():
1335
raise AssertionError('smart server vfs must be enabled '
1336
'to use vfs implementation')
1337
self.bzrdir._ensure_real()
1338
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1339
# Give the remote repository the matching real repo.
1340
real_repo = self._real_branch.repository
1341
if isinstance(real_repo, RemoteRepository):
1342
real_repo._ensure_real()
1343
real_repo = real_repo._real_repository
1344
self.repository._set_real_repository(real_repo)
1345
# Give the branch the remote repository to let fast-pathing happen.
1346
self._real_branch.repository = self.repository
1347
# XXX: deal with _lock_mode == 'w'
1348
if self._lock_mode == 'r':
1349
self._real_branch.lock_read()
1351
def _translate_error(self, err, **context):
1352
self.repository._translate_error(err, branch=self, **context)
1354
def _clear_cached_state(self):
1355
super(RemoteBranch, self)._clear_cached_state()
1356
if self._real_branch is not None:
1357
self._real_branch._clear_cached_state()
1359
def _clear_cached_state_of_remote_branch_only(self):
1360
"""Like _clear_cached_state, but doesn't clear the cache of
1363
This is useful when falling back to calling a method of
1364
self._real_branch that changes state. In that case the underlying
1365
branch changes, so we need to invalidate this RemoteBranch's cache of
1366
it. However, there's no need to invalidate the _real_branch's cache
1367
too, in fact doing so might harm performance.
1369
super(RemoteBranch, self)._clear_cached_state()
1372
def control_files(self):
1373
# Defer actually creating RemoteBranchLockableFiles until its needed,
1374
# because it triggers an _ensure_real that we otherwise might not need.
1375
if self._control_files is None:
1376
self._control_files = RemoteBranchLockableFiles(
1377
self.bzrdir, self._client)
1378
return self._control_files
1380
def _get_checkout_format(self):
1382
return self._real_branch._get_checkout_format()
1384
def get_physical_lock_status(self):
1385
"""See Branch.get_physical_lock_status()."""
1386
# should be an API call to the server, as branches must be lockable.
1388
return self._real_branch.get_physical_lock_status()
1390
def get_stacked_on_url(self):
1391
"""Get the URL this branch is stacked against.
1393
:raises NotStacked: If the branch is not stacked.
1394
:raises UnstackableBranchFormat: If the branch does not support
1396
:raises UnstackableRepositoryFormat: If the repository does not support
1400
response = self._client.call('Branch.get_stacked_on_url',
1401
self._remote_path())
1402
if response[0] != 'ok':
1403
raise errors.UnexpectedSmartServerResponse(response)
1405
except errors.ErrorFromSmartServer, err:
1406
# there may not be a repository yet, so we can't call through
1407
# its _translate_error
1408
_translate_error(err, branch=self)
1409
except errors.UnknownSmartMethod, err:
1411
return self._real_branch.get_stacked_on_url()
1413
def lock_read(self):
1414
if not self._lock_mode:
1415
self._lock_mode = 'r'
1416
self._lock_count = 1
1417
if self._real_branch is not None:
1418
self._real_branch.lock_read()
1420
self._lock_count += 1
1422
def _remote_lock_write(self, token):
1424
branch_token = repo_token = ''
1426
branch_token = token
1427
repo_token = self.repository.lock_write()
1428
self.repository.unlock()
1430
response = self._client.call(
1431
'Branch.lock_write', self._remote_path(),
1432
branch_token, repo_token or '')
1433
except errors.ErrorFromSmartServer, err:
1434
self._translate_error(err, token=token)
1435
if response[0] != 'ok':
1436
raise errors.UnexpectedSmartServerResponse(response)
1437
ok, branch_token, repo_token = response
1438
return branch_token, repo_token
1440
def lock_write(self, token=None):
1441
if not self._lock_mode:
1442
remote_tokens = self._remote_lock_write(token)
1443
self._lock_token, self._repo_lock_token = remote_tokens
1444
if not self._lock_token:
1445
raise SmartProtocolError('Remote server did not return a token!')
1446
# TODO: We really, really, really don't want to call _ensure_real
1447
# here, but it's the easiest way to ensure coherency between the
1448
# state of the RemoteBranch and RemoteRepository objects and the
1449
# physical locks. If we don't materialise the real objects here,
1450
# then getting everything in the right state later is complex, so
1451
# for now we just do it the lazy way.
1452
# -- Andrew Bennetts, 2007-02-22.
1454
if self._real_branch is not None:
1455
self._real_branch.repository.lock_write(
1456
token=self._repo_lock_token)
1458
self._real_branch.lock_write(token=self._lock_token)
1460
self._real_branch.repository.unlock()
1461
if token is not None:
1462
self._leave_lock = True
1464
# XXX: this case seems to be unreachable; token cannot be None.
1465
self._leave_lock = False
1466
self._lock_mode = 'w'
1467
self._lock_count = 1
1468
elif self._lock_mode == 'r':
1469
raise errors.ReadOnlyTransaction
1471
if token is not None:
1472
# A token was given to lock_write, and we're relocking, so check
1473
# that the given token actually matches the one we already have.
1474
if token != self._lock_token:
1475
raise errors.TokenMismatch(token, self._lock_token)
1476
self._lock_count += 1
1477
return self._lock_token or None
1479
def _unlock(self, branch_token, repo_token):
1481
response = self._client.call('Branch.unlock', self._remote_path(), branch_token,
1483
except errors.ErrorFromSmartServer, err:
1484
self._translate_error(err, token=str((branch_token, repo_token)))
1485
if response == ('ok',):
1487
raise errors.UnexpectedSmartServerResponse(response)
1490
self._lock_count -= 1
1491
if not self._lock_count:
1492
self._clear_cached_state()
1493
mode = self._lock_mode
1494
self._lock_mode = None
1495
if self._real_branch is not None:
1496
if (not self._leave_lock and mode == 'w' and
1497
self._repo_lock_token):
1498
# If this RemoteBranch will remove the physical lock for the
1499
# repository, make sure the _real_branch doesn't do it
1500
# first. (Because the _real_branch's repository is set to
1501
# be the RemoteRepository.)
1502
self._real_branch.repository.leave_lock_in_place()
1503
self._real_branch.unlock()
1505
# Only write-locked branched need to make a remote method call
1506
# to perfom the unlock.
1508
if not self._lock_token:
1509
raise AssertionError('Locked, but no token!')
1510
branch_token = self._lock_token
1511
repo_token = self._repo_lock_token
1512
self._lock_token = None
1513
self._repo_lock_token = None
1514
if not self._leave_lock:
1515
self._unlock(branch_token, repo_token)
1517
def break_lock(self):
1519
return self._real_branch.break_lock()
1521
def leave_lock_in_place(self):
1522
if not self._lock_token:
1523
raise NotImplementedError(self.leave_lock_in_place)
1524
self._leave_lock = True
1526
def dont_leave_lock_in_place(self):
1527
if not self._lock_token:
1528
raise NotImplementedError(self.dont_leave_lock_in_place)
1529
self._leave_lock = False
1531
def _last_revision_info(self):
1532
response = self._client.call('Branch.last_revision_info', self._remote_path())
1533
if response[0] != 'ok':
1534
raise SmartProtocolError('unexpected response code %s' % (response,))
1535
revno = int(response[1])
1536
last_revision = response[2]
1537
return (revno, last_revision)
1539
def _gen_revision_history(self):
1540
"""See Branch._gen_revision_history()."""
1541
response_tuple, response_handler = self._client.call_expecting_body(
1542
'Branch.revision_history', self._remote_path())
1543
if response_tuple[0] != 'ok':
1544
raise errors.UnexpectedSmartServerResponse(response_tuple)
1545
result = response_handler.read_body_bytes().split('\x00')
1550
def _remote_path(self):
1551
return self.bzrdir._path_for_remote_call(self._client)
1553
def _set_last_revision_descendant(self, revision_id, other_branch,
1554
allow_diverged=False, allow_overwrite_descendant=False):
1556
response = self._client.call('Branch.set_last_revision_ex',
1557
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id,
1558
int(allow_diverged), int(allow_overwrite_descendant))
1559
except errors.ErrorFromSmartServer, err:
1560
self._translate_error(err, other_branch=other_branch)
1561
self._clear_cached_state()
1562
if len(response) != 3 and response[0] != 'ok':
1563
raise errors.UnexpectedSmartServerResponse(response)
1564
new_revno, new_revision_id = response[1:]
1565
self._last_revision_info_cache = new_revno, new_revision_id
1566
self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1568
def _set_last_revision(self, revision_id):
1569
self._clear_cached_state()
1571
response = self._client.call('Branch.set_last_revision',
1572
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id)
1573
except errors.ErrorFromSmartServer, err:
1574
self._translate_error(err)
1575
if response != ('ok',):
1576
raise errors.UnexpectedSmartServerResponse(response)
1579
def set_revision_history(self, rev_history):
1580
# Send just the tip revision of the history; the server will generate
1581
# the full history from that. If the revision doesn't exist in this
1582
# branch, NoSuchRevision will be raised.
1583
if rev_history == []:
1586
rev_id = rev_history[-1]
1587
self._set_last_revision(rev_id)
1588
self._cache_revision_history(rev_history)
1590
def get_parent(self):
1592
return self._real_branch.get_parent()
1594
def set_parent(self, url):
1596
return self._real_branch.set_parent(url)
1598
def set_stacked_on_url(self, stacked_location):
1599
"""Set the URL this branch is stacked against.
1601
:raises UnstackableBranchFormat: If the branch does not support
1603
:raises UnstackableRepositoryFormat: If the repository does not support
1607
return self._real_branch.set_stacked_on_url(stacked_location)
1609
def sprout(self, to_bzrdir, revision_id=None):
1610
# Like Branch.sprout, except that it sprouts a branch in the default
1611
# format, because RemoteBranches can't be created at arbitrary URLs.
1612
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1613
# to_bzrdir.create_branch...
1615
result = self._real_branch._format.initialize(to_bzrdir)
1616
self.copy_content_into(result, revision_id=revision_id)
1617
result.set_parent(self.bzrdir.root_transport.base)
1621
def pull(self, source, overwrite=False, stop_revision=None,
1623
self._clear_cached_state_of_remote_branch_only()
1625
return self._real_branch.pull(
1626
source, overwrite=overwrite, stop_revision=stop_revision,
1627
_override_hook_target=self, **kwargs)
1630
def push(self, target, overwrite=False, stop_revision=None):
1632
return self._real_branch.push(
1633
target, overwrite=overwrite, stop_revision=stop_revision,
1634
_override_hook_source_branch=self)
1636
def is_locked(self):
1637
return self._lock_count >= 1
1640
def revision_id_to_revno(self, revision_id):
1642
return self._real_branch.revision_id_to_revno(revision_id)
1645
def set_last_revision_info(self, revno, revision_id):
1646
revision_id = ensure_null(revision_id)
1648
response = self._client.call('Branch.set_last_revision_info',
1649
self._remote_path(), self._lock_token, self._repo_lock_token, str(revno), revision_id)
1650
except errors.UnknownSmartMethod:
1652
self._clear_cached_state_of_remote_branch_only()
1653
self._real_branch.set_last_revision_info(revno, revision_id)
1654
self._last_revision_info_cache = revno, revision_id
1656
except errors.ErrorFromSmartServer, err:
1657
self._translate_error(err)
1658
if response == ('ok',):
1659
self._clear_cached_state()
1660
self._last_revision_info_cache = revno, revision_id
1661
# Update the _real_branch's cache too.
1662
if self._real_branch is not None:
1663
cache = self._last_revision_info_cache
1664
self._real_branch._last_revision_info_cache = cache
1666
raise errors.UnexpectedSmartServerResponse(response)
1669
def generate_revision_history(self, revision_id, last_rev=None,
1671
medium = self._client._medium
1672
if not medium._is_remote_before((1, 6)):
1674
self._set_last_revision_descendant(revision_id, other_branch,
1675
allow_diverged=True, allow_overwrite_descendant=True)
1677
except errors.UnknownSmartMethod:
1678
medium._remember_remote_is_before((1, 6))
1679
self._clear_cached_state_of_remote_branch_only()
1681
self._real_branch.generate_revision_history(
1682
revision_id, last_rev=last_rev, other_branch=other_branch)
1687
return self._real_branch.tags
1689
def set_push_location(self, location):
1691
return self._real_branch.set_push_location(location)
1694
def update_revisions(self, other, stop_revision=None, overwrite=False,
1696
"""See Branch.update_revisions."""
1699
if stop_revision is None:
1700
stop_revision = other.last_revision()
1701
if revision.is_null(stop_revision):
1702
# if there are no commits, we're done.
1704
self.fetch(other, stop_revision)
1707
# Just unconditionally set the new revision. We don't care if
1708
# the branches have diverged.
1709
self._set_last_revision(stop_revision)
1711
medium = self._client._medium
1712
if not medium._is_remote_before((1, 6)):
1714
self._set_last_revision_descendant(stop_revision, other)
1716
except errors.UnknownSmartMethod:
1717
medium._remember_remote_is_before((1, 6))
1718
# Fallback for pre-1.6 servers: check for divergence
1719
# client-side, then do _set_last_revision.
1720
last_rev = revision.ensure_null(self.last_revision())
1722
graph = self.repository.get_graph()
1723
if self._check_if_descendant_or_diverged(
1724
stop_revision, last_rev, graph, other):
1725
# stop_revision is a descendant of last_rev, but we aren't
1726
# overwriting, so we're done.
1728
self._set_last_revision(stop_revision)
1733
def _extract_tar(tar, to_dir):
1734
"""Extract all the contents of a tarfile object.
1736
A replacement for extractall, which is not present in python2.4
1739
tar.extract(tarinfo, to_dir)
1742
def _translate_error(err, **context):
1743
"""Translate an ErrorFromSmartServer into a more useful error.
1745
Possible context keys:
1754
return context[name]
1755
except KeyError, keyErr:
1756
mutter('Missing key %r in context %r', keyErr.args[0], context)
1758
if err.error_verb == 'NoSuchRevision':
1759
raise NoSuchRevision(find('branch'), err.error_args[0])
1760
elif err.error_verb == 'nosuchrevision':
1761
raise NoSuchRevision(find('repository'), err.error_args[0])
1762
elif err.error_tuple == ('nobranch',):
1763
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1764
elif err.error_verb == 'norepository':
1765
raise errors.NoRepositoryPresent(find('bzrdir'))
1766
elif err.error_verb == 'LockContention':
1767
raise errors.LockContention('(remote lock)')
1768
elif err.error_verb == 'UnlockableTransport':
1769
raise errors.UnlockableTransport(find('bzrdir').root_transport)
1770
elif err.error_verb == 'LockFailed':
1771
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1772
elif err.error_verb == 'TokenMismatch':
1773
raise errors.TokenMismatch(find('token'), '(remote token)')
1774
elif err.error_verb == 'Diverged':
1775
raise errors.DivergedBranches(find('branch'), find('other_branch'))
1776
elif err.error_verb == 'TipChangeRejected':
1777
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1778
elif err.error_verb == 'UnstackableBranchFormat':
1779
raise errors.UnstackableBranchFormat(*err.error_args)
1780
elif err.error_verb == 'UnstackableRepositoryFormat':
1781
raise errors.UnstackableRepositoryFormat(*err.error_args)
1782
elif err.error_verb == 'NotStacked':
1783
raise errors.NotStacked(branch=find('branch'))