1
# Copyright (C) 2006, 2007 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.
20
from cStringIO import StringIO
21
from urlparse import urlparse
23
from bzrlib import branch, errors, repository
24
from bzrlib.branch import BranchReferenceFormat
25
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
26
from bzrlib.config import BranchConfig, TreeConfig
27
from bzrlib.decorators import needs_read_lock, needs_write_lock
28
from bzrlib.errors import NoSuchRevision
29
from bzrlib.revision import NULL_REVISION
30
from bzrlib.smart import client, vfs
31
from bzrlib.urlutils import unescape
33
# Note: RemoteBzrDirFormat is in bzrdir.py
35
class RemoteBzrDir(BzrDir):
36
"""Control directory on a remote server, accessed by HPSS."""
38
def __init__(self, transport, _client=None):
39
"""Construct a RemoteBzrDir.
41
:param _client: Private parameter for testing. Disables probing and the
44
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
45
# this object holds a delegated bzrdir that uses file-level operations
46
# to talk to the other side
47
# XXX: We should go into find_format, but not allow it to find
48
# RemoteBzrDirFormat and make sure it finds the real underlying format.
49
self._real_bzrdir = None
52
self._medium = transport.get_smart_client()
53
self._client = client.SmartClient(self._medium)
55
self._client = _client
60
path = self._path_for_remote_call(self._client)
61
#self._real_bzrdir._format.probe_transport(transport)
62
response = self._client.call('probe_dont_use', path)
63
if response == ('no',):
64
raise errors.NotBranchError(path=transport.base)
66
def _ensure_real(self):
67
"""Ensure that there is a _real_bzrdir set.
69
used before calls to self._real_bzrdir.
71
if not self._real_bzrdir:
72
default_format = BzrDirFormat.get_default_format()
73
self._real_bzrdir = default_format.open(self.root_transport,
76
def create_repository(self, shared=False):
77
return RemoteRepository(
78
self, self._real_bzrdir.create_repository(shared=shared))
80
def create_branch(self):
81
real_branch = self._real_bzrdir.create_branch()
82
return RemoteBranch(self, self.find_repository(), real_branch)
84
def create_workingtree(self, revision_id=None):
85
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
86
return RemoteWorkingTree(self, real_workingtree)
88
def open_branch(self, _unsupported=False):
89
assert _unsupported == False, 'unsupported flag support not implemented yet.'
90
path = self._path_for_remote_call(self._client)
91
response = self._client.call('BzrDir.open_branch', path)
92
if response[0] == 'ok':
94
# branch at this location.
95
return RemoteBranch(self, self.find_repository())
97
# a branch reference, use the existing BranchReference logic.
98
format = BranchReferenceFormat()
99
return format.open(self, _found=True, location=response[1])
100
elif response == ('nobranch',):
101
raise errors.NotBranchError(path=self.root_transport.base)
103
assert False, 'unexpected response code %r' % (response,)
105
def open_repository(self):
106
path = self._path_for_remote_call(self._client)
107
response = self._client.call('BzrDir.find_repository', path)
108
assert response[0] in ('ok', 'norepository'), \
109
'unexpected response code %s' % (response,)
110
if response[0] == 'norepository':
111
raise errors.NoRepositoryPresent(self)
112
if response[1] == '':
113
return RemoteRepository(self)
115
raise errors.NoRepositoryPresent(self)
117
def open_workingtree(self):
118
return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
120
def _path_for_remote_call(self, client):
121
"""Return the path to be used for this bzrdir in a remote call."""
122
return client.remote_path_from_transport(self.root_transport)
124
def get_branch_transport(self, branch_format):
125
return self._real_bzrdir.get_branch_transport(branch_format)
127
def get_repository_transport(self, repository_format):
128
return self._real_bzrdir.get_repository_transport(repository_format)
130
def get_workingtree_transport(self, workingtree_format):
131
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
133
def can_convert_format(self):
134
"""Upgrading of remote bzrdirs is not supported yet."""
137
def needs_format_conversion(self, format=None):
138
"""Upgrading of remote bzrdirs is not supported yet."""
141
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
143
return self._real_bzrdir.clone(url, revision_id=revision_id,
144
basis=basis, force_new_repo=force_new_repo)
146
#def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
147
# self._ensure_real()
148
# return self._real_bzrdir.sprout(url, revision_id=revision_id,
149
# basis=basis, force_new_repo=force_new_repo)
152
class RemoteRepositoryFormat(repository.RepositoryFormat):
153
"""Format for repositories accessed over rpc.
155
Instances of this repository are represented by RemoteRepository
159
_matchingbzrdir = RemoteBzrDirFormat
161
def initialize(self, a_bzrdir, shared=False):
162
assert isinstance(a_bzrdir, RemoteBzrDir)
163
return a_bzrdir.create_repository(shared=shared)
165
def open(self, a_bzrdir):
166
assert isinstance(a_bzrdir, RemoteBzrDir)
167
return a_bzrdir.open_repository()
169
def get_format_description(self):
170
return 'bzr remote repository'
172
def __eq__(self, other):
173
return self.__class__ == other.__class__
175
rich_root_data = False
178
class RemoteRepository(object):
179
"""Repository accessed over rpc.
181
For the moment everything is delegated to IO-like operations over
185
def __init__(self, remote_bzrdir, real_repository=None, _client=None):
186
"""Create a RemoteRepository instance.
188
:param remote_bzrdir: The bzrdir hosting this repository.
189
:param real_repository: If not None, a local implementation of the
190
repository logic for the repository, usually accessing the data
192
:param _client: Private testing parameter - override the smart client
193
to be used by the repository.
196
self._real_repository = real_repository
198
self._real_repository = None
199
self.bzrdir = remote_bzrdir
201
self._client = client.SmartClient(self.bzrdir._medium)
203
self._client = _client
204
self._format = RemoteRepositoryFormat()
205
self._lock_mode = None
206
self._lock_token = None
208
self._leave_lock = False
210
def _ensure_real(self):
211
"""Ensure that there is a _real_repository set.
213
used before calls to self._real_repository.
215
if not self._real_repository:
216
self.bzrdir._ensure_real()
217
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
218
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
220
def get_revision_graph(self, revision_id=None):
221
"""See Repository.get_revision_graph()."""
222
if revision_id is None:
224
elif revision_id == NULL_REVISION:
227
path = self.bzrdir._path_for_remote_call(self._client)
228
assert type(revision_id) is str
229
response = self._client.call2(
230
'Repository.get_revision_graph', path, revision_id)
231
assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
232
if response[0][0] == 'ok':
233
coded = response[1].read_body_bytes()
235
# no revisions in this repository!
237
lines = coded.split('\n')
241
d = list(line.split())
242
revision_graph[d[0]] = d[1:]
244
return revision_graph
246
response_body = response[1].read_body_bytes()
247
assert response_body == ''
248
raise NoSuchRevision(self, revision_id)
250
def has_revision(self, revision_id):
251
"""See Repository.has_revision()."""
252
if revision_id is None:
253
# The null revision is always present.
255
path = self.bzrdir._path_for_remote_call(self._client)
256
response = self._client.call('Repository.has_revision', path, revision_id)
257
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
258
return response[0] == 'ok'
260
def gather_stats(self, revid=None, committers=None):
261
"""See Repository.gather_stats()."""
262
path = self.bzrdir._path_for_remote_call(self._client)
263
if revid in (None, NULL_REVISION):
267
if committers is None or not committers:
268
fmt_committers = 'no'
270
fmt_committers = 'yes'
271
response = self._client.call2('Repository.gather_stats', path,
272
fmt_revid, fmt_committers)
273
assert response[0][0] == 'ok', \
274
'unexpected response code %s' % (response[0],)
276
body = response[1].read_body_bytes()
278
for line in body.split('\n'):
281
key, val_text = line.split(':')
282
if key in ('revisions', 'size', 'committers'):
283
result[key] = int(val_text)
284
elif key in ('firstrev', 'latestrev'):
285
values = val_text.split(' ')[1:]
286
result[key] = (float(values[0]), long(values[1]))
290
def get_physical_lock_status(self):
291
"""See Repository.get_physical_lock_status()."""
295
"""See Repository.is_shared()."""
296
path = self.bzrdir._path_for_remote_call(self._client)
297
response = self._client.call('Repository.is_shared', path)
298
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
299
return response[0] == 'yes'
302
# wrong eventually - want a local lock cache context
303
if not self._lock_mode:
304
self._lock_mode = 'r'
306
if self._real_repository is not None:
307
self._real_repository.lock_read()
309
self._lock_count += 1
311
def _remote_lock_write(self, token):
312
path = self.bzrdir._path_for_remote_call(self._client)
315
response = self._client.call('Repository.lock_write', path, token)
316
if response[0] == 'ok':
319
elif response[0] == 'LockContention':
320
raise errors.LockContention('(remote lock)')
321
elif response[0] == 'UnlockableTransport':
322
raise errors.UnlockableTransport(self.bzrdir.root_transport)
324
assert False, 'unexpected response code %s' % (response,)
326
def lock_write(self, token=None):
327
if not self._lock_mode:
328
self._lock_token = self._remote_lock_write(token)
329
assert self._lock_token, 'Remote server did not return a token!'
330
if self._real_repository is not None:
331
self._real_repository.lock_write(token=self._lock_token)
332
if token is not None:
333
self._leave_lock = True
335
self._leave_lock = False
336
self._lock_mode = 'w'
338
elif self._lock_mode == 'r':
339
raise errors.ReadOnlyError(self)
341
self._lock_count += 1
342
return self._lock_token
344
def leave_lock_in_place(self):
345
self._leave_lock = True
347
def dont_leave_lock_in_place(self):
348
self._leave_lock = False
350
def _set_real_repository(self, repository):
351
"""Set the _real_repository for this repository.
353
:param repository: The repository to fallback to for non-hpss
354
implemented operations.
356
assert not isinstance(repository, RemoteRepository)
357
self._real_repository = repository
358
if self._lock_mode == 'w':
359
# if we are already locked, the real repository must be able to
360
# acquire the lock with our token.
361
self._real_repository.lock_write(self._lock_token)
362
elif self._lock_mode == 'r':
363
self._real_repository.lock_read()
365
def _unlock(self, token):
366
path = self.bzrdir._path_for_remote_call(self._client)
367
response = self._client.call('Repository.unlock', path, token)
368
if response == ('ok',):
370
elif response[0] == 'TokenMismatch':
371
raise errors.TokenMismatch(token, '(remote token)')
373
assert False, 'unexpected response code %s' % (response,)
376
self._lock_count -= 1
377
if not self._lock_count:
378
mode = self._lock_mode
379
self._lock_mode = None
380
if self._real_repository is not None:
381
self._real_repository.unlock()
384
assert self._lock_token, 'Locked, but no token!'
385
token = self._lock_token
386
self._lock_token = None
387
if not self._leave_lock:
390
def break_lock(self):
391
# should hand off to the network
393
return self._real_repository.break_lock()
395
### These methods are just thin shims to the VFS object for now.
397
def revision_tree(self, revision_id):
399
return self._real_repository.revision_tree(revision_id)
401
def get_commit_builder(self, branch, parents, config, timestamp=None,
402
timezone=None, committer=None, revprops=None,
404
# FIXME: It ought to be possible to call this without immediately
405
# triggering _ensure_real. For now it's the easiest thing to do.
407
builder = self._real_repository.get_commit_builder(branch, parents,
408
config, timestamp=timestamp, timezone=timezone,
409
committer=committer, revprops=revprops, revision_id=revision_id)
410
# Make the builder use this RemoteRepository rather than the real one.
411
builder.repository = self
415
def add_inventory(self, revid, inv, parents):
417
return self._real_repository.add_inventory(revid, inv, parents)
420
def add_revision(self, rev_id, rev, inv=None, config=None):
422
return self._real_repository.add_revision(
423
rev_id, rev, inv=inv, config=config)
426
def get_inventory(self, revision_id):
428
return self._real_repository.get_inventory(revision_id)
431
def get_revision(self, revision_id):
433
return self._real_repository.get_revision(revision_id)
436
def weave_store(self):
438
return self._real_repository.weave_store
440
def get_transaction(self):
442
return self._real_repository.get_transaction()
445
def clone(self, a_bzrdir, revision_id=None, basis=None):
447
return self._real_repository.clone(
448
a_bzrdir, revision_id=revision_id, basis=basis)
450
def make_working_trees(self):
453
def fetch(self, source, revision_id=None, pb=None):
455
return self._real_repository.fetch(
456
source, revision_id=revision_id, pb=pb)
459
def control_weaves(self):
461
return self._real_repository.control_weaves
464
def get_ancestry(self, revision_id):
466
return self._real_repository.get_ancestry(revision_id)
469
def get_inventory_weave(self):
471
return self._real_repository.get_inventory_weave()
473
def fileids_altered_by_revision_ids(self, revision_ids):
475
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
478
def get_signature_text(self, revision_id):
480
return self._real_repository.get_signature_text(revision_id)
483
def get_revision_graph_with_ghosts(self, revision_ids=None):
485
return self._real_repository.get_revision_graph_with_ghosts(
486
revision_ids=revision_ids)
489
def get_inventory_xml(self, revision_id):
491
return self._real_repository.get_inventory_xml(revision_id)
493
def deserialise_inventory(self, revision_id, xml):
495
return self._real_repository.deserialise_inventory(revision_id, xml)
497
def reconcile(self, other=None, thorough=False):
499
return self._real_repository.reconcile(other=other, thorough=thorough)
501
def all_revision_ids(self):
503
return self._real_repository.all_revision_ids()
506
def get_deltas_for_revisions(self, revisions):
508
return self._real_repository.get_deltas_for_revisions(revisions)
511
def get_revision_delta(self, revision_id):
513
return self._real_repository.get_revision_delta(revision_id)
516
def revision_trees(self, revision_ids):
518
return self._real_repository.revision_trees(revision_ids)
521
def get_revision_reconcile(self, revision_id):
523
return self._real_repository.get_revision_reconcile(revision_id)
526
def check(self, revision_ids):
528
return self._real_repository.check(revision_ids)
530
def copy_content_into(self, destination, revision_id=None, basis=None):
532
return self._real_repository.copy_content_into(
533
destination, revision_id=revision_id, basis=basis)
535
def set_make_working_trees(self, new_value):
536
raise NotImplementedError(self.set_make_working_trees)
539
def sign_revision(self, revision_id, gpg_strategy):
541
return self._real_repository.sign_revision(revision_id, gpg_strategy)
544
def get_revisions(self, revision_ids):
546
return self._real_repository.get_revisions(revision_ids)
548
def supports_rich_root(self):
550
return self._real_repository.supports_rich_root()
552
def iter_reverse_revision_history(self, revision_id):
554
return self._real_repository.iter_reverse_revision_history(revision_id)
557
def _serializer(self):
559
return self._real_repository._serializer
561
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
563
return self._real_repository.store_revision_signature(
564
gpg_strategy, plaintext, revision_id)
566
def has_signature_for_revision_id(self, revision_id):
568
return self._real_repository.has_signature_for_revision_id(revision_id)
571
class RemoteBranchLockableFiles(object):
572
"""A 'LockableFiles' implementation that talks to a smart server.
574
This is not a public interface class.
577
def __init__(self, bzrdir, _client):
579
self._client = _client
582
"""'get' a remote path as per the LockableFiles interface.
584
:param path: the file to 'get'. If this is 'branch.conf', we do not
585
just retrieve a file, instead we ask the smart server to generate
586
a configuration for us - which is retrieved as an INI file.
588
assert path == 'branch.conf'
589
path = self.bzrdir._path_for_remote_call(self._client)
590
response = self._client.call2('Branch.get_config_file', path)
591
assert response[0][0] == 'ok', \
592
'unexpected response code %s' % (response[0],)
593
return StringIO(response[1].read_body_bytes())
596
class RemoteBranchFormat(branch.BranchFormat):
598
def get_format_description(self):
599
return 'Remote BZR Branch'
601
def get_format_string(self):
602
return 'Remote BZR Branch'
604
def open(self, a_bzrdir):
605
assert isinstance(a_bzrdir, RemoteBzrDir)
606
return a_bzrdir.open_branch()
608
def initialize(self, a_bzrdir):
609
assert isinstance(a_bzrdir, RemoteBzrDir)
610
return a_bzrdir.create_branch()
613
class RemoteBranch(branch.Branch):
614
"""Branch stored on a server accessed by HPSS RPC.
616
At the moment most operations are mapped down to simple file operations.
619
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
621
"""Create a RemoteBranch instance.
623
:param real_branch: An optional local implementation of the branch
624
format, usually accessing the data via the VFS.
625
:param _client: Private parameter for testing.
627
#branch.Branch.__init__(self)
628
self._revision_history_cache = None
629
self.bzrdir = remote_bzrdir
630
if _client is not None:
631
self._client = _client
633
self._client = client.SmartClient(self.bzrdir._medium)
634
self.repository = remote_repository
635
if real_branch is not None:
636
self._real_branch = real_branch
637
# Give the remote repository the matching real repo.
638
real_repo = self._real_branch.repository
639
if isinstance(real_repo, RemoteRepository):
640
real_repo._ensure_real()
641
real_repo = real_repo._real_repository
642
self.repository._set_real_repository(real_repo)
643
# Give the branch the remote repository to let fast-pathing happen.
644
self._real_branch.repository = self.repository
646
self._real_branch = None
647
# Fill out expected attributes of branch for bzrlib api users.
648
self._format = RemoteBranchFormat()
649
self.base = self.bzrdir.root_transport.base
650
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
651
self._lock_mode = None
652
self._lock_token = None
654
self._leave_lock = False
656
def _ensure_real(self):
657
"""Ensure that there is a _real_branch set.
659
used before calls to self._real_branch.
661
if not self._real_branch:
662
assert vfs.vfs_enabled()
663
self.bzrdir._ensure_real()
664
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
665
# Give the remote repository the matching real repo.
666
real_repo = self._real_branch.repository
667
if isinstance(real_repo, RemoteRepository):
668
real_repo._ensure_real()
669
real_repo = real_repo._real_repository
670
self.repository._set_real_repository(real_repo)
671
# Give the branch the remote repository to let fast-pathing happen.
672
self._real_branch.repository = self.repository
673
# XXX: deal with _lock_mode == 'w'
674
if self._lock_mode == 'r':
675
self._real_branch.lock_read()
677
def get_physical_lock_status(self):
678
"""See Branch.get_physical_lock_status()."""
679
# should be an API call to the server, as branches must be lockable.
681
return self._real_branch.get_physical_lock_status()
684
if not self._lock_mode:
685
self._lock_mode = 'r'
687
if self._real_branch is not None:
688
self._real_branch.lock_read()
690
self._lock_count += 1
692
def _remote_lock_write(self, tokens):
694
branch_token = repo_token = ''
696
branch_token, repo_token = tokens
697
path = self.bzrdir._path_for_remote_call(self._client)
698
response = self._client.call('Branch.lock_write', path, branch_token,
700
if response[0] == 'ok':
701
ok, branch_token, repo_token = response
702
return branch_token, repo_token
703
elif response[0] == 'LockContention':
704
raise errors.LockContention('(remote lock)')
705
elif response[0] == 'TokenMismatch':
706
raise errors.TokenMismatch(tokens, '(remote tokens)')
707
elif response[0] == 'UnlockableTransport':
708
raise errors.UnlockableTransport(self.bzrdir.root_transport)
710
assert False, 'unexpected response code %r' % (response,)
712
def lock_write(self, tokens=None):
713
if not self._lock_mode:
714
remote_tokens = self._remote_lock_write(tokens)
715
self._lock_token, self._repo_lock_token = remote_tokens
716
assert self._lock_token, 'Remote server did not return a token!'
717
# TODO: We really, really, really don't want to call _ensure_real
718
# here, but it's the easiest way to ensure coherency between the
719
# state of the RemoteBranch and RemoteRepository objects and the
720
# physical locks. If we don't materialise the real objects here,
721
# then getting everything in the right state later is complex, so
722
# for now we just do it the lazy way.
723
# -- Andrew Bennetts, 2007-02-22.
725
if self._real_branch is not None:
726
self._real_branch.lock_write(tokens=remote_tokens)
727
if tokens is not None:
728
self._leave_lock = True
730
# XXX: this case seems to be unreachable; tokens cannot be None.
731
self._leave_lock = False
732
self._lock_mode = 'w'
734
elif self._lock_mode == 'r':
735
raise errors.ReadOnlyTransaction
737
if tokens is not None:
738
# Tokens were given to lock_write, and we're relocking, so check
739
# that the given tokens actually match the ones we already have.
740
held_tokens = (self._lock_token, self._repo_lock_token)
741
if tokens != held_tokens:
742
raise errors.TokenMismatch(str(tokens), str(held_tokens))
743
self._lock_count += 1
744
return self._lock_token, self._repo_lock_token
746
def _unlock(self, branch_token, repo_token):
747
path = self.bzrdir._path_for_remote_call(self._client)
748
response = self._client.call('Branch.unlock', path, branch_token,
750
if response == ('ok',):
752
elif response[0] == 'TokenMismatch':
753
raise errors.TokenMismatch(
754
str((branch_token, repo_token)), '(remote tokens)')
756
assert False, 'unexpected response code %s' % (response,)
759
self._lock_count -= 1
760
if not self._lock_count:
761
self._clear_cached_state()
762
mode = self._lock_mode
763
self._lock_mode = None
764
if self._real_branch is not None:
765
if not self._leave_lock:
766
# If this RemoteBranch will remove the physical lock for the
767
# repository, make sure the _real_branch doesn't do it
768
# first. (Because the _real_branch's repository is set to
769
# be the RemoteRepository.)
770
self._real_branch.repository.leave_lock_in_place()
771
self._real_branch.unlock()
774
assert self._lock_token, 'Locked, but no token!'
775
branch_token = self._lock_token
776
repo_token = self._repo_lock_token
777
self._lock_token = None
778
self._repo_lock_token = None
779
if not self._leave_lock:
780
self._unlock(branch_token, repo_token)
782
def break_lock(self):
784
return self._real_branch.break_lock()
786
def leave_lock_in_place(self):
787
self._leave_lock = True
789
def dont_leave_lock_in_place(self):
790
self._leave_lock = False
792
def last_revision_info(self):
793
"""See Branch.last_revision_info()."""
794
path = self.bzrdir._path_for_remote_call(self._client)
795
response = self._client.call('Branch.last_revision_info', path)
796
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
797
revno = int(response[1])
798
last_revision = response[2]
799
if last_revision == '':
800
last_revision = NULL_REVISION
801
return (revno, last_revision)
803
def _gen_revision_history(self):
804
"""See Branch._gen_revision_history()."""
805
path = self.bzrdir._path_for_remote_call(self._client)
806
response = self._client.call2('Branch.revision_history', path)
807
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
808
result = response[1].read_body_bytes().split('\x00')
814
def set_revision_history(self, rev_history):
815
# Send just the tip revision of the history; the server will generate
816
# the full history from that. If the revision doesn't exist in this
817
# branch, NoSuchRevision will be raised.
818
path = self.bzrdir._path_for_remote_call(self._client)
819
if rev_history == []:
822
rev_id = rev_history[-1]
823
response = self._client.call('Branch.set_last_revision',
824
path, self._lock_token, self._repo_lock_token, rev_id)
825
if response[0] == 'NoSuchRevision':
826
raise NoSuchRevision(self, rev_id)
828
assert response == ('ok',), (
829
'unexpected response code %r' % (response,))
830
self._cache_revision_history(rev_history)
832
def get_parent(self):
834
return self._real_branch.get_parent()
836
def set_parent(self, url):
838
return self._real_branch.set_parent(url)
840
def get_config(self):
841
return RemoteBranchConfig(self)
843
def sprout(self, to_bzrdir, revision_id=None):
844
# Like Branch.sprout, except that it sprouts a branch in the default
845
# format, because RemoteBranches can't be created at arbitrary URLs.
846
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
847
# to_bzrdir.create_branch...
849
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
850
self._real_branch.copy_content_into(result, revision_id=revision_id)
851
result.set_parent(self.bzrdir.root_transport.base)
855
def append_revision(self, *revision_ids):
857
return self._real_branch.append_revision(*revision_ids)
860
def pull(self, source, overwrite=False, stop_revision=None):
862
self._real_branch.pull(
863
source, overwrite=overwrite, stop_revision=stop_revision)
866
def push(self, target, overwrite=False, stop_revision=None):
868
return self._real_branch.push(
869
target, overwrite=overwrite, stop_revision=stop_revision)
872
return self._lock_count >= 1
874
def set_last_revision_info(self, revno, revision_id):
876
self._clear_cached_state()
877
return self._real_branch.set_last_revision_info(revno, revision_id)
879
def generate_revision_history(self, revision_id, last_rev=None,
882
return self._real_branch.generate_revision_history(
883
revision_id, last_rev=last_rev, other_branch=other_branch)
888
return self._real_branch.tags
890
def set_push_location(self, location):
892
return self._real_branch.set_push_location(location)
894
def update_revisions(self, other, stop_revision=None):
896
return self._real_branch.update_revisions(
897
other, stop_revision=stop_revision)
900
class RemoteWorkingTree(object):
902
def __init__(self, remote_bzrdir, real_workingtree):
903
self.real_workingtree = real_workingtree
904
self.bzrdir = remote_bzrdir
906
def __getattr__(self, name):
907
# XXX: temporary way to lazily delegate everything to the real
909
return getattr(self.real_workingtree, name)
912
class RemoteBranchConfig(BranchConfig):
915
self.branch._ensure_real()
916
return self.branch._real_branch.get_config().username()
918
def _get_branch_data_config(self):
919
self.branch._ensure_real()
920
if self._branch_data_config is None:
921
self._branch_data_config = TreeConfig(self.branch._real_branch)
922
return self._branch_data_config