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()
234
lines = coded.split('\n')
238
d = list(line.split())
239
revision_graph[d[0]] = d[1:]
241
return revision_graph
243
response_body = response[1].read_body_bytes()
244
assert response_body == ''
245
raise NoSuchRevision(self, revision_id)
247
def has_revision(self, revision_id):
248
"""See Repository.has_revision()."""
249
if revision_id is None:
250
# The null revision is always present.
252
path = self.bzrdir._path_for_remote_call(self._client)
253
response = self._client.call('Repository.has_revision', path, revision_id)
254
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
255
return response[0] == 'ok'
257
def gather_stats(self, revid=None, committers=None):
258
"""See Repository.gather_stats()."""
259
path = self.bzrdir._path_for_remote_call(self._client)
260
if revid in (None, NULL_REVISION):
264
if committers is None or not committers:
265
fmt_committers = 'no'
267
fmt_committers = 'yes'
268
response = self._client.call2('Repository.gather_stats', path,
269
fmt_revid, fmt_committers)
270
assert response[0][0] == 'ok', \
271
'unexpected response code %s' % (response[0],)
273
body = response[1].read_body_bytes()
275
for line in body.split('\n'):
278
key, val_text = line.split(':')
279
if key in ('revisions', 'size', 'committers'):
280
result[key] = int(val_text)
281
elif key in ('firstrev', 'latestrev'):
282
values = val_text.split(' ')[1:]
283
result[key] = (float(values[0]), long(values[1]))
287
def get_physical_lock_status(self):
288
"""See Repository.get_physical_lock_status()."""
292
"""See Repository.is_shared()."""
293
path = self.bzrdir._path_for_remote_call(self._client)
294
response = self._client.call('Repository.is_shared', path)
295
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
296
return response[0] == 'yes'
299
# wrong eventually - want a local lock cache context
300
if not self._lock_mode:
301
self._lock_mode = 'r'
303
if self._real_repository is not None:
304
self._real_repository.lock_read()
306
self._lock_count += 1
308
def _remote_lock_write(self, token):
309
path = self.bzrdir._path_for_remote_call(self._client)
312
response = self._client.call('Repository.lock_write', path, token)
313
if response[0] == 'ok':
316
elif response[0] == 'LockContention':
317
raise errors.LockContention('(remote lock)')
318
elif response[0] == 'UnlockableTransport':
319
raise errors.UnlockableTransport(self.bzrdir.root_transport)
321
assert False, 'unexpected response code %s' % (response,)
323
def lock_write(self, token=None):
324
if not self._lock_mode:
325
self._lock_token = self._remote_lock_write(token)
326
assert self._lock_token, 'Remote server did not return a token!'
327
if self._real_repository is not None:
328
self._real_repository.lock_write(token=self._lock_token)
329
if token is not None:
330
self._leave_lock = True
332
self._leave_lock = False
333
self._lock_mode = 'w'
335
elif self._lock_mode == 'r':
336
raise errors.ReadOnlyError(self)
338
self._lock_count += 1
339
return self._lock_token
341
def leave_lock_in_place(self):
342
self._leave_lock = True
344
def dont_leave_lock_in_place(self):
345
self._leave_lock = False
347
def _set_real_repository(self, repository):
348
"""Set the _real_repository for this repository.
350
:param repository: The repository to fallback to for non-hpss
351
implemented operations.
353
assert not isinstance(repository, RemoteRepository)
354
self._real_repository = repository
355
if self._lock_mode == 'w':
356
# if we are already locked, the real repository must be able to
357
# acquire the lock with our token.
358
self._real_repository.lock_write(self._lock_token)
359
elif self._lock_mode == 'r':
360
self._real_repository.lock_read()
362
def _unlock(self, token):
363
path = self.bzrdir._path_for_remote_call(self._client)
364
response = self._client.call('Repository.unlock', path, token)
365
if response == ('ok',):
367
elif response[0] == 'TokenMismatch':
368
raise errors.TokenMismatch(token, '(remote token)')
370
assert False, 'unexpected response code %s' % (response,)
373
self._lock_count -= 1
374
if not self._lock_count:
375
mode = self._lock_mode
376
self._lock_mode = None
377
if self._real_repository is not None:
378
self._real_repository.unlock()
381
assert self._lock_token, 'Locked, but no token!'
382
token = self._lock_token
383
self._lock_token = None
384
if not self._leave_lock:
387
def break_lock(self):
388
# should hand off to the network
390
return self._real_repository.break_lock()
392
### These methods are just thin shims to the VFS object for now.
394
def revision_tree(self, revision_id):
396
return self._real_repository.revision_tree(revision_id)
398
def get_commit_builder(self, branch, parents, config, timestamp=None,
399
timezone=None, committer=None, revprops=None,
401
# FIXME: It ought to be possible to call this without immediately
402
# triggering _ensure_real. For now it's the easiest thing to do.
404
builder = self._real_repository.get_commit_builder(branch, parents,
405
config, timestamp=timestamp, timezone=timezone,
406
committer=committer, revprops=revprops, revision_id=revision_id)
407
# Make the builder use this RemoteRepository rather than the real one.
408
builder.repository = self
412
def add_inventory(self, revid, inv, parents):
414
return self._real_repository.add_inventory(revid, inv, parents)
417
def add_revision(self, rev_id, rev, inv=None, config=None):
419
return self._real_repository.add_revision(
420
rev_id, rev, inv=inv, config=config)
423
def get_inventory(self, revision_id):
425
return self._real_repository.get_inventory(revision_id)
428
def get_revision(self, revision_id):
430
return self._real_repository.get_revision(revision_id)
433
def weave_store(self):
435
return self._real_repository.weave_store
437
def get_transaction(self):
439
return self._real_repository.get_transaction()
442
def clone(self, a_bzrdir, revision_id=None, basis=None):
444
return self._real_repository.clone(
445
a_bzrdir, revision_id=revision_id, basis=basis)
447
def make_working_trees(self):
450
def fetch(self, source, revision_id=None, pb=None):
452
return self._real_repository.fetch(
453
source, revision_id=revision_id, pb=pb)
456
def control_weaves(self):
458
return self._real_repository.control_weaves
461
def get_ancestry(self, revision_id):
463
return self._real_repository.get_ancestry(revision_id)
466
def get_inventory_weave(self):
468
return self._real_repository.get_inventory_weave()
470
def fileids_altered_by_revision_ids(self, revision_ids):
472
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
475
def get_signature_text(self, revision_id):
477
return self._real_repository.get_signature_text(revision_id)
480
def get_revision_graph_with_ghosts(self, revision_ids=None):
482
return self._real_repository.get_revision_graph_with_ghosts(
483
revision_ids=revision_ids)
486
def get_inventory_xml(self, revision_id):
488
return self._real_repository.get_inventory_xml(revision_id)
490
def deserialise_inventory(self, revision_id, xml):
492
return self._real_repository.deserialise_inventory(revision_id, xml)
494
def reconcile(self, other=None, thorough=False):
496
return self._real_repository.reconcile(other=other, thorough=thorough)
498
def all_revision_ids(self):
500
return self._real_repository.all_revision_ids()
503
def get_deltas_for_revisions(self, revisions):
505
return self._real_repository.get_deltas_for_revisions(revisions)
508
def get_revision_delta(self, revision_id):
510
return self._real_repository.get_revision_delta(revision_id)
513
def revision_trees(self, revision_ids):
515
return self._real_repository.revision_trees(revision_ids)
518
def get_revision_reconcile(self, revision_id):
520
return self._real_repository.get_revision_reconcile(revision_id)
523
def check(self, revision_ids):
525
return self._real_repository.check(revision_ids)
527
def copy_content_into(self, destination, revision_id=None, basis=None):
529
return self._real_repository.copy_content_into(
530
destination, revision_id=revision_id, basis=basis)
532
def set_make_working_trees(self, new_value):
533
raise NotImplementedError(self.set_make_working_trees)
536
def sign_revision(self, revision_id, gpg_strategy):
538
return self._real_repository.sign_revision(revision_id, gpg_strategy)
541
def get_revisions(self, revision_ids):
543
return self._real_repository.get_revisions(revision_ids)
545
def supports_rich_root(self):
547
return self._real_repository.supports_rich_root()
549
def iter_reverse_revision_history(self, revision_id):
551
return self._real_repository.iter_reverse_revision_history(revision_id)
554
def _serializer(self):
556
return self._real_repository._serializer
558
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
560
return self._real_repository.store_revision_signature(
561
gpg_strategy, plaintext, revision_id)
563
def has_signature_for_revision_id(self, revision_id):
565
return self._real_repository.has_signature_for_revision_id(revision_id)
568
class RemoteBranchLockableFiles(object):
569
"""A 'LockableFiles' implementation that talks to a smart server.
571
This is not a public interface class.
574
def __init__(self, bzrdir, _client):
576
self._client = _client
579
"""'get' a remote path as per the LockableFiles interface.
581
:param path: the file to 'get'. If this is 'branch.conf', we do not
582
just retrieve a file, instead we ask the smart server to generate
583
a configuration for us - which is retrieved as an INI file.
585
assert path == 'branch.conf'
586
path = self.bzrdir._path_for_remote_call(self._client)
587
response = self._client.call2('Branch.get_config_file', path)
588
assert response[0][0] == 'ok', \
589
'unexpected response code %s' % (response[0],)
590
return StringIO(response[1].read_body_bytes())
593
class RemoteBranchFormat(branch.BranchFormat):
595
def get_format_description(self):
596
return 'Remote BZR Branch'
598
def get_format_string(self):
599
return 'Remote BZR Branch'
601
def open(self, a_bzrdir):
602
assert isinstance(a_bzrdir, RemoteBzrDir)
603
return a_bzrdir.open_branch()
605
def initialize(self, a_bzrdir):
606
assert isinstance(a_bzrdir, RemoteBzrDir)
607
return a_bzrdir.create_branch()
610
class RemoteBranch(branch.Branch):
611
"""Branch stored on a server accessed by HPSS RPC.
613
At the moment most operations are mapped down to simple file operations.
616
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
618
"""Create a RemoteBranch instance.
620
:param real_branch: An optional local implementation of the branch
621
format, usually accessing the data via the VFS.
622
:param _client: Private parameter for testing.
624
self.bzrdir = remote_bzrdir
625
if _client is not None:
626
self._client = _client
628
self._client = client.SmartClient(self.bzrdir._medium)
629
self.repository = remote_repository
630
if real_branch is not None:
631
self._real_branch = real_branch
632
# Give the remote repository the matching real repo.
633
real_repo = self._real_branch.repository
634
if isinstance(real_repo, RemoteRepository):
635
real_repo._ensure_real()
636
real_repo = real_repo._real_repository
637
self.repository._set_real_repository(real_repo)
638
# Give the branch the remote repository to let fast-pathing happen.
639
self._real_branch.repository = self.repository
641
self._real_branch = None
642
# Fill out expected attributes of branch for bzrlib api users.
643
self._format = RemoteBranchFormat()
644
self.base = self.bzrdir.root_transport.base
645
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
646
self._lock_mode = None
647
self._lock_token = None
649
self._leave_lock = False
651
def _ensure_real(self):
652
"""Ensure that there is a _real_branch set.
654
used before calls to self._real_branch.
656
if not self._real_branch:
657
assert vfs.vfs_enabled()
658
self.bzrdir._ensure_real()
659
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
660
# Give the remote repository the matching real repo.
661
real_repo = self._real_branch.repository
662
if isinstance(real_repo, RemoteRepository):
663
real_repo._ensure_real()
664
real_repo = real_repo._real_repository
665
self.repository._set_real_repository(real_repo)
666
# Give the branch the remote repository to let fast-pathing happen.
667
self._real_branch.repository = self.repository
668
# XXX: deal with _lock_mode == 'w'
669
if self._lock_mode == 'r':
670
self._real_branch.lock_read()
672
def get_physical_lock_status(self):
673
"""See Branch.get_physical_lock_status()."""
674
# should be an API call to the server, as branches must be lockable.
676
return self._real_branch.get_physical_lock_status()
679
if not self._lock_mode:
680
self._lock_mode = 'r'
682
if self._real_branch is not None:
683
self._real_branch.lock_read()
685
self._lock_count += 1
687
def _remote_lock_write(self, tokens):
689
branch_token = repo_token = ''
691
branch_token, repo_token = tokens
692
path = self.bzrdir._path_for_remote_call(self._client)
693
response = self._client.call('Branch.lock_write', path, branch_token,
695
if response[0] == 'ok':
696
ok, branch_token, repo_token = response
697
return branch_token, repo_token
698
elif response[0] == 'LockContention':
699
raise errors.LockContention('(remote lock)')
700
elif response[0] == 'TokenMismatch':
701
raise errors.TokenMismatch(tokens, '(remote tokens)')
702
elif response[0] == 'UnlockableTransport':
703
raise errors.UnlockableTransport(self.bzrdir.root_transport)
705
assert False, 'unexpected response code %r' % (response,)
707
def lock_write(self, tokens=None):
708
if not self._lock_mode:
709
remote_tokens = self._remote_lock_write(tokens)
710
self._lock_token, self._repo_lock_token = remote_tokens
711
assert self._lock_token, 'Remote server did not return a token!'
712
# TODO: We really, really, really don't want to call _ensure_real
713
# here, but it's the easiest way to ensure coherency between the
714
# state of the RemoteBranch and RemoteRepository objects and the
715
# physical locks. If we don't materialise the real objects here,
716
# then getting everything in the right state later is complex, so
717
# for now we just do it the lazy way.
718
# -- Andrew Bennetts, 2007-02-22.
720
if self._real_branch is not None:
721
self._real_branch.lock_write(tokens=remote_tokens)
722
if tokens is not None:
723
self._leave_lock = True
725
# XXX: this case seems to be unreachable; tokens cannot be None.
726
self._leave_lock = False
727
self._lock_mode = 'w'
729
elif self._lock_mode == 'r':
730
raise errors.ReadOnlyTransaction
732
if tokens is not None:
733
# Tokens were given to lock_write, and we're relocking, so check
734
# that the given tokens actually match the ones we already have.
735
held_tokens = (self._lock_token, self._repo_lock_token)
736
if tokens != held_tokens:
737
raise errors.TokenMismatch(str(tokens), str(held_tokens))
738
self._lock_count += 1
739
return self._lock_token, self._repo_lock_token
741
def _unlock(self, branch_token, repo_token):
742
path = self.bzrdir._path_for_remote_call(self._client)
743
response = self._client.call('Branch.unlock', path, branch_token,
745
if response == ('ok',):
747
elif response[0] == 'TokenMismatch':
748
raise errors.TokenMismatch(
749
str((branch_token, repo_token)), '(remote tokens)')
751
assert False, 'unexpected response code %s' % (response,)
754
self._lock_count -= 1
755
if not self._lock_count:
756
mode = self._lock_mode
757
self._lock_mode = None
758
if self._real_branch is not None:
759
if not self._leave_lock:
760
# If this RemoteBranch will remove the physical lock for the
761
# repository, make sure the _real_branch doesn't do it
762
# first. (Because the _real_branch's repository is set to
763
# be the RemoteRepository.)
764
self._real_branch.repository.leave_lock_in_place()
765
self._real_branch.unlock()
768
assert self._lock_token, 'Locked, but no token!'
769
branch_token = self._lock_token
770
repo_token = self._repo_lock_token
771
self._lock_token = None
772
self._repo_lock_token = None
773
if not self._leave_lock:
774
self._unlock(branch_token, repo_token)
776
def break_lock(self):
778
return self._real_branch.break_lock()
780
def leave_lock_in_place(self):
781
self._leave_lock = True
783
def dont_leave_lock_in_place(self):
784
self._leave_lock = False
786
def last_revision_info(self):
787
"""See Branch.last_revision_info()."""
788
path = self.bzrdir._path_for_remote_call(self._client)
789
response = self._client.call('Branch.last_revision_info', path)
790
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
791
revno = int(response[1])
792
last_revision = response[2]
793
if last_revision == '':
794
last_revision = NULL_REVISION
795
return (revno, last_revision)
797
def revision_history(self):
798
"""See Branch.revision_history()."""
799
# XXX: TODO: this does not cache the revision history for the duration
800
# of a lock, which is a bug - see the code for regular branches
802
path = self.bzrdir._path_for_remote_call(self._client)
803
response = self._client.call2('Branch.revision_history', path)
804
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
805
result = response[1].read_body_bytes().split('\x00')
811
def set_revision_history(self, rev_history):
812
# Send just the tip revision of the history; the server will generate
813
# the full history from that. If the revision doesn't exist in this
814
# branch, NoSuchRevision will be raised.
815
path = self.bzrdir._path_for_remote_call(self._client)
816
if rev_history == []:
819
rev_id = rev_history[-1]
820
response = self._client.call('Branch.set_last_revision',
821
path, self._lock_token, self._repo_lock_token, rev_id)
822
if response[0] == 'NoSuchRevision':
823
raise NoSuchRevision(self, rev_id)
825
assert response == ('ok',), (
826
'unexpected response code %r' % (response,))
828
def get_parent(self):
830
return self._real_branch.get_parent()
832
def set_parent(self, url):
834
return self._real_branch.set_parent(url)
836
def get_config(self):
837
return RemoteBranchConfig(self)
839
def sprout(self, to_bzrdir, revision_id=None):
840
# Like Branch.sprout, except that it sprouts a branch in the default
841
# format, because RemoteBranches can't be created at arbitrary URLs.
842
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
843
# to_bzrdir.create_branch...
845
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
846
self._real_branch.copy_content_into(result, revision_id=revision_id)
847
result.set_parent(self.bzrdir.root_transport.base)
851
def append_revision(self, *revision_ids):
853
return self._real_branch.append_revision(*revision_ids)
856
def pull(self, source, overwrite=False, stop_revision=None):
858
self._real_branch.pull(
859
source, overwrite=overwrite, stop_revision=stop_revision)
862
def push(self, target, overwrite=False, stop_revision=None):
864
return self._real_branch.push(
865
target, overwrite=overwrite, stop_revision=stop_revision)
868
return self._lock_count >= 1
870
def set_last_revision_info(self, revno, revision_id):
872
return self._real_branch.set_last_revision_info(revno, revision_id)
874
def generate_revision_history(self, revision_id, last_rev=None,
877
return self._real_branch.generate_revision_history(
878
revision_id, last_rev=last_rev, other_branch=other_branch)
883
return self._real_branch.tags
885
def set_push_location(self, location):
887
return self._real_branch.set_push_location(location)
889
def update_revisions(self, other, stop_revision=None):
891
return self._real_branch.update_revisions(
892
other, stop_revision=stop_revision)
895
class RemoteWorkingTree(object):
897
def __init__(self, remote_bzrdir, real_workingtree):
898
self.real_workingtree = real_workingtree
899
self.bzrdir = remote_bzrdir
901
def __getattr__(self, name):
902
# XXX: temporary way to lazily delegate everything to the real
904
return getattr(self.real_workingtree, name)
907
class RemoteBranchConfig(BranchConfig):
910
self.branch._ensure_real()
911
return self.branch._real_branch.get_config().username()
913
def _get_branch_data_config(self):
914
self.branch._ensure_real()
915
if self._branch_data_config is None:
916
self._branch_data_config = TreeConfig(self.branch._real_branch)
917
return self._branch_data_config