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
self._real_repository = repository
354
if self._lock_mode == 'w':
355
# if we are already locked, the real repository must be able to
356
# acquire the lock with our token.
357
self._real_repository.lock_write(self._lock_token)
358
elif self._lock_mode == 'r':
359
self._real_repository.lock_read()
361
def _unlock(self, token):
362
path = self.bzrdir._path_for_remote_call(self._client)
363
response = self._client.call('Repository.unlock', path, token)
364
if response == ('ok',):
366
elif response[0] == 'TokenMismatch':
367
raise errors.TokenMismatch(token, '(remote token)')
369
assert False, 'unexpected response code %s' % (response,)
372
self._lock_count -= 1
373
if not self._lock_count:
374
mode = self._lock_mode
375
self._lock_mode = None
376
if self._real_repository is not None:
377
self._real_repository.unlock()
380
assert self._lock_token, 'Locked, but no token!'
381
token = self._lock_token
382
self._lock_token = None
383
if not self._leave_lock:
386
def break_lock(self):
387
# should hand off to the network
389
return self._real_repository.break_lock()
391
### These methods are just thin shims to the VFS object for now.
393
def revision_tree(self, revision_id):
395
return self._real_repository.revision_tree(revision_id)
397
def get_commit_builder(self, branch, parents, config, timestamp=None,
398
timezone=None, committer=None, revprops=None,
400
# FIXME: It ought to be possible to call this without immediately
401
# triggering _ensure_real. For now it's the easiest thing to do.
403
builder = self._real_repository.get_commit_builder(branch, parents,
404
config, timestamp=timestamp, timezone=timezone,
405
committer=committer, revprops=revprops, revision_id=revision_id)
406
# Make the builder use this RemoteRepository rather than the real one.
407
builder.repository = self
411
def add_inventory(self, revid, inv, parents):
413
return self._real_repository.add_inventory(revid, inv, parents)
416
def add_revision(self, rev_id, rev, inv=None, config=None):
418
return self._real_repository.add_revision(
419
rev_id, rev, inv=inv, config=config)
422
def get_inventory(self, revision_id):
424
return self._real_repository.get_inventory(revision_id)
427
def get_revision(self, revision_id):
429
return self._real_repository.get_revision(revision_id)
432
def weave_store(self):
434
return self._real_repository.weave_store
436
def get_transaction(self):
438
return self._real_repository.get_transaction()
441
def clone(self, a_bzrdir, revision_id=None, basis=None):
443
return self._real_repository.clone(
444
a_bzrdir, revision_id=revision_id, basis=basis)
446
def make_working_trees(self):
449
def fetch(self, source, revision_id=None, pb=None):
451
return self._real_repository.fetch(
452
source, revision_id=revision_id, pb=pb)
455
def control_weaves(self):
457
return self._real_repository.control_weaves
460
def get_ancestry(self, revision_id):
462
return self._real_repository.get_ancestry(revision_id)
465
def get_inventory_weave(self):
467
return self._real_repository.get_inventory_weave()
469
def fileids_altered_by_revision_ids(self, revision_ids):
471
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
474
def get_signature_text(self, revision_id):
476
return self._real_repository.get_signature_text(revision_id)
479
def get_revision_graph_with_ghosts(self, revision_ids=None):
481
return self._real_repository.get_revision_graph_with_ghosts(
482
revision_ids=revision_ids)
485
def get_inventory_xml(self, revision_id):
487
return self._real_repository.get_inventory_xml(revision_id)
489
def deserialise_inventory(self, revision_id, xml):
491
return self._real_repository.deserialise_inventory(revision_id, xml)
493
def reconcile(self, other=None, thorough=False):
495
return self._real_repository.reconcile(other=other, thorough=thorough)
497
def all_revision_ids(self):
499
return self._real_repository.all_revision_ids()
502
def get_deltas_for_revisions(self, revisions):
504
return self._real_repository.get_deltas_for_revisions(revisions)
507
def get_revision_delta(self, revision_id):
509
return self._real_repository.get_revision_delta(revision_id)
512
def revision_trees(self, revision_ids):
514
return self._real_repository.revision_trees(revision_ids)
517
def get_revision_reconcile(self, revision_id):
519
return self._real_repository.get_revision_reconcile(revision_id)
522
def check(self, revision_ids):
524
return self._real_repository.check(revision_ids)
526
def copy_content_into(self, destination, revision_id=None, basis=None):
528
return self._real_repository.copy_content_into(
529
destination, revision_id=revision_id, basis=basis)
531
def set_make_working_trees(self, new_value):
532
raise NotImplementedError(self.set_make_working_trees)
535
def sign_revision(self, revision_id, gpg_strategy):
537
return self._real_repository.sign_revision(revision_id, gpg_strategy)
540
def get_revisions(self, revision_ids):
542
return self._real_repository.get_revisions(revision_ids)
544
def supports_rich_root(self):
546
return self._real_repository.supports_rich_root()
548
def iter_reverse_revision_history(self, revision_id):
550
return self._real_repository.iter_reverse_revision_history(revision_id)
553
class RemoteBranchLockableFiles(object):
554
"""A 'LockableFiles' implementation that talks to a smart server.
556
This is not a public interface class.
559
def __init__(self, bzrdir, _client):
561
self._client = _client
564
"""'get' a remote path as per the LockableFiles interface.
566
:param path: the file to 'get'. If this is 'branch.conf', we do not
567
just retrieve a file, instead we ask the smart server to generate
568
a configuration for us - which is retrieved as an INI file.
570
assert path == 'branch.conf'
571
path = self.bzrdir._path_for_remote_call(self._client)
572
response = self._client.call2('Branch.get_config_file', path)
573
assert response[0][0] == 'ok', \
574
'unexpected response code %s' % (response[0],)
575
return StringIO(response[1].read_body_bytes())
578
class RemoteBranchFormat(branch.BranchFormat):
580
def get_format_description(self):
581
return 'Remote BZR Branch'
583
def get_format_string(self):
584
return 'Remote BZR Branch'
586
def open(self, a_bzrdir):
587
assert isinstance(a_bzrdir, RemoteBzrDir)
588
return a_bzrdir.open_branch()
590
def initialize(self, a_bzrdir):
591
assert isinstance(a_bzrdir, RemoteBzrDir)
592
return a_bzrdir.create_branch()
595
class RemoteBranch(branch.Branch):
596
"""Branch stored on a server accessed by HPSS RPC.
598
At the moment most operations are mapped down to simple file operations.
601
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
603
"""Create a RemoteBranch instance.
605
:param real_branch: An optional local implementation of the branch
606
format, usually accessing the data via the VFS.
607
:param _client: Private parameter for testing.
609
self.bzrdir = remote_bzrdir
610
if _client is not None:
611
self._client = _client
613
self._client = client.SmartClient(self.bzrdir._medium)
614
self.repository = remote_repository
615
if real_branch is not None:
616
self._real_branch = real_branch
617
# Give the remote repository the matching real repo.
618
self.repository._set_real_repository(self._real_branch.repository)
619
# Give the branch the remote repository to let fast-pathing happen.
620
self._real_branch.repository = self.repository
622
self._real_branch = None
623
# Fill out expected attributes of branch for bzrlib api users.
624
self._format = RemoteBranchFormat()
625
self.base = self.bzrdir.root_transport.base
626
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
627
self._lock_mode = None
628
self._lock_token = None
630
self._leave_lock = False
632
def _ensure_real(self):
633
"""Ensure that there is a _real_branch set.
635
used before calls to self._real_branch.
637
if not self._real_branch:
638
assert vfs.vfs_enabled()
639
self.bzrdir._ensure_real()
640
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
641
# Give the remote repository the matching real repo.
642
self.repository._set_real_repository(self._real_branch.repository)
643
# Give the branch the remote repository to let fast-pathing happen.
644
self._real_branch.repository = self.repository
645
# XXX: deal with _lock_mode == 'w'
646
if self._lock_mode == 'r':
647
self._real_branch.lock_read()
649
def get_physical_lock_status(self):
650
"""See Branch.get_physical_lock_status()."""
651
# should be an API call to the server, as branches must be lockable.
653
return self._real_branch.get_physical_lock_status()
656
if not self._lock_mode:
657
self._lock_mode = 'r'
659
if self._real_branch is not None:
660
self._real_branch.lock_read()
662
self._lock_count += 1
664
def _remote_lock_write(self, tokens):
666
branch_token = repo_token = ''
668
branch_token, repo_token = tokens
669
path = self.bzrdir._path_for_remote_call(self._client)
670
response = self._client.call('Branch.lock_write', path, branch_token,
672
if response[0] == 'ok':
673
ok, branch_token, repo_token = response
674
return branch_token, repo_token
675
elif response[0] == 'LockContention':
676
raise errors.LockContention('(remote lock)')
677
elif response[0] == 'TokenMismatch':
678
raise errors.TokenMismatch(tokens, '(remote tokens)')
679
elif response[0] == 'UnlockableTransport':
680
raise errors.UnlockableTransport(self.bzrdir.root_transport)
682
assert False, 'unexpected response code %r' % (response,)
684
def lock_write(self, tokens=None):
685
if not self._lock_mode:
686
remote_tokens = self._remote_lock_write(tokens)
687
self._lock_token, self._repo_lock_token = remote_tokens
688
assert self._lock_token, 'Remote server did not return a token!'
689
# TODO: We really, really, really don't want to call _ensure_real
690
# here, but it's the easiest way to ensure coherency between the
691
# state of the RemoteBranch and RemoteRepository objects and the
692
# physical locks. If we don't materialise the real objects here,
693
# then getting everything in the right state later is complex, so
694
# for now we just do it the lazy way.
695
# -- Andrew Bennetts, 2007-02-22.
697
if self._real_branch is not None:
698
self._real_branch.lock_write(tokens=remote_tokens)
699
if tokens is not None:
700
self._leave_lock = True
702
# XXX: this case seems to be unreachable; tokens cannot be None.
703
self._leave_lock = False
704
self._lock_mode = 'w'
706
elif self._lock_mode == 'r':
707
raise errors.ReadOnlyTransaction
709
if tokens is not None:
710
# Tokens were given to lock_write, and we're relocking, so check
711
# that the given tokens actually match the ones we already have.
712
held_tokens = (self._lock_token, self._repo_lock_token)
713
if tokens != held_tokens:
714
raise errors.TokenMismatch(str(tokens), str(held_tokens))
715
self._lock_count += 1
716
return self._lock_token, self._repo_lock_token
718
def _unlock(self, branch_token, repo_token):
719
path = self.bzrdir._path_for_remote_call(self._client)
720
response = self._client.call('Branch.unlock', path, branch_token,
722
if response == ('ok',):
724
elif response[0] == 'TokenMismatch':
725
raise errors.TokenMismatch(
726
str((branch_token, repo_token)), '(remote tokens)')
728
assert False, 'unexpected response code %s' % (response,)
731
self._lock_count -= 1
732
if not self._lock_count:
733
mode = self._lock_mode
734
self._lock_mode = None
735
if self._real_branch is not None:
736
if not self._leave_lock:
737
# If this RemoteBranch will remove the physical lock for the
738
# repository, make sure the _real_branch doesn't do it
739
# first. (Because the _real_branch's repository is set to
740
# be the RemoteRepository.)
741
self._real_branch.repository.leave_lock_in_place()
742
self._real_branch.unlock()
745
assert self._lock_token, 'Locked, but no token!'
746
branch_token = self._lock_token
747
repo_token = self._repo_lock_token
748
self._lock_token = None
749
self._repo_lock_token = None
750
if not self._leave_lock:
751
self._unlock(branch_token, repo_token)
753
def break_lock(self):
755
return self._real_branch.break_lock()
757
def leave_lock_in_place(self):
758
self._leave_lock = True
760
def dont_leave_lock_in_place(self):
761
self._leave_lock = False
763
def last_revision_info(self):
764
"""See Branch.last_revision_info()."""
765
path = self.bzrdir._path_for_remote_call(self._client)
766
response = self._client.call('Branch.last_revision_info', path)
767
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
768
revno = int(response[1])
769
last_revision = response[2]
770
if last_revision == '':
771
last_revision = NULL_REVISION
772
return (revno, last_revision)
774
def revision_history(self):
775
"""See Branch.revision_history()."""
776
# XXX: TODO: this does not cache the revision history for the duration
777
# of a lock, which is a bug - see the code for regular branches
779
path = self.bzrdir._path_for_remote_call(self._client)
780
response = self._client.call2('Branch.revision_history', path)
781
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
782
result = response[1].read_body_bytes().split('\x00')
788
def set_revision_history(self, rev_history):
789
# Send just the tip revision of the history; the server will generate
790
# the full history from that. If the revision doesn't exist in this
791
# branch, NoSuchRevision will be raised.
792
path = self.bzrdir._path_for_remote_call(self._client)
793
if rev_history == []:
796
rev_id = rev_history[-1]
797
response = self._client.call('Branch.set_last_revision',
798
path, self._lock_token, self._repo_lock_token, rev_id)
799
if response[0] == 'NoSuchRevision':
800
raise NoSuchRevision(self, rev_id)
802
assert response == ('ok',), (
803
'unexpected response code %r' % (response,))
805
def get_parent(self):
807
return self._real_branch.get_parent()
809
def set_parent(self, url):
811
return self._real_branch.set_parent(url)
813
def get_config(self):
814
return RemoteBranchConfig(self)
816
def sprout(self, to_bzrdir, revision_id=None):
817
# Like Branch.sprout, except that it sprouts a branch in the default
818
# format, because RemoteBranches can't be created at arbitrary URLs.
819
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
820
# to_bzrdir.create_branch...
822
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
823
self._real_branch.copy_content_into(result, revision_id=revision_id)
824
result.set_parent(self.bzrdir.root_transport.base)
828
def append_revision(self, *revision_ids):
830
return self._real_branch.append_revision(*revision_ids)
833
def pull(self, source, overwrite=False, stop_revision=None):
835
self._real_branch.pull(
836
source, overwrite=overwrite, stop_revision=stop_revision)
839
def push(self, target, overwrite=False, stop_revision=None):
841
self._real_branch.push(
842
target, overwrite=overwrite, stop_revision=stop_revision)
845
return self._lock_count >= 1
847
def set_last_revision_info(self, revno, revision_id):
849
return self._real_branch.set_last_revision_info(revno, revision_id)
851
def generate_revision_history(self, revision_id, last_rev=None,
854
return self._real_branch.generate_revision_history(
855
revision_id, last_rev=last_rev, other_branch=other_branch)
858
class RemoteWorkingTree(object):
860
def __init__(self, remote_bzrdir, real_workingtree):
861
self.real_workingtree = real_workingtree
862
self.bzrdir = remote_bzrdir
864
def __getattr__(self, name):
865
# XXX: temporary way to lazily delegate everything to the real
867
return getattr(self.real_workingtree, name)
870
class RemoteBranchConfig(BranchConfig):
873
self.branch._ensure_real()
874
return self.branch._real_branch.get_config().username()
876
def _get_branch_data_config(self):
877
self.branch._ensure_real()
878
if self._branch_data_config is None:
879
self._branch_data_config = TreeConfig(self.branch._real_branch)
880
return self._branch_data_config