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."""
142
class RemoteRepositoryFormat(repository.RepositoryFormat):
143
"""Format for repositories accessed over rpc.
145
Instances of this repository are represented by RemoteRepository
149
_matchingbzrdir = RemoteBzrDirFormat
151
def initialize(self, a_bzrdir, shared=False):
152
assert isinstance(a_bzrdir, RemoteBzrDir)
153
return a_bzrdir.create_repository(shared=shared)
155
def open(self, a_bzrdir):
156
assert isinstance(a_bzrdir, RemoteBzrDir)
157
return a_bzrdir.open_repository()
159
def get_format_description(self):
160
return 'bzr remote repository'
162
def __eq__(self, other):
163
return self.__class__ == other.__class__
165
rich_root_data = False
168
class RemoteRepository(object):
169
"""Repository accessed over rpc.
171
For the moment everything is delegated to IO-like operations over
175
def __init__(self, remote_bzrdir, real_repository=None, _client=None):
176
"""Create a RemoteRepository instance.
178
:param remote_bzrdir: The bzrdir hosting this repository.
179
:param real_repository: If not None, a local implementation of the
180
repository logic for the repository, usually accessing the data
182
:param _client: Private testing parameter - override the smart client
183
to be used by the repository.
186
self._real_repository = real_repository
188
self._real_repository = None
189
self.bzrdir = remote_bzrdir
191
self._client = client.SmartClient(self.bzrdir._medium)
193
self._client = _client
194
self._format = RemoteRepositoryFormat()
195
self._lock_mode = None
196
self._lock_token = None
198
self._leave_lock = False
200
def _ensure_real(self):
201
"""Ensure that there is a _real_repository set.
203
used before calls to self._real_repository.
205
if not self._real_repository:
206
self.bzrdir._ensure_real()
207
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
208
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
210
def get_revision_graph(self, revision_id=None):
211
"""See Repository.get_revision_graph()."""
212
if revision_id is None:
214
elif revision_id == NULL_REVISION:
217
path = self.bzrdir._path_for_remote_call(self._client)
218
assert type(revision_id) is str
219
response = self._client.call2(
220
'Repository.get_revision_graph', path, revision_id)
221
assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
222
if response[0][0] == 'ok':
223
coded = response[1].read_body_bytes()
224
lines = coded.split('\n')
228
d = list(line.split())
229
revision_graph[d[0]] = d[1:]
231
return revision_graph
233
response_body = response[1].read_body_bytes()
234
assert response_body == ''
235
raise NoSuchRevision(self, revision_id)
237
def has_revision(self, revision_id):
238
"""See Repository.has_revision()."""
239
if revision_id is None:
240
# The null revision is always present.
242
path = self.bzrdir._path_for_remote_call(self._client)
243
response = self._client.call('Repository.has_revision', path, revision_id)
244
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
245
return response[0] == 'ok'
247
def gather_stats(self, revid=None, committers=None):
248
"""See Repository.gather_stats()."""
249
path = self.bzrdir._path_for_remote_call(self._client)
250
if revid in (None, NULL_REVISION):
254
if committers is None or not committers:
255
fmt_committers = 'no'
257
fmt_committers = 'yes'
258
response = self._client.call2('Repository.gather_stats', path,
259
fmt_revid, fmt_committers)
260
assert response[0][0] == 'ok', \
261
'unexpected response code %s' % (response[0],)
263
body = response[1].read_body_bytes()
265
for line in body.split('\n'):
268
key, val_text = line.split(':')
269
if key in ('revisions', 'size', 'committers'):
270
result[key] = int(val_text)
271
elif key in ('firstrev', 'latestrev'):
272
values = val_text.split(' ')[1:]
273
result[key] = (float(values[0]), long(values[1]))
277
def get_physical_lock_status(self):
278
"""See Repository.get_physical_lock_status()."""
282
"""See Repository.is_shared()."""
283
path = self.bzrdir._path_for_remote_call(self._client)
284
response = self._client.call('Repository.is_shared', path)
285
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
286
return response[0] == 'yes'
289
# wrong eventually - want a local lock cache context
290
if not self._lock_mode:
291
self._lock_mode = 'r'
293
if self._real_repository is not None:
294
self._real_repository.lock_read()
296
self._lock_count += 1
298
def _remote_lock_write(self, token):
299
path = self.bzrdir._path_for_remote_call(self._client)
302
response = self._client.call('Repository.lock_write', path, token)
303
if response[0] == 'ok':
306
elif response[0] == 'LockContention':
307
raise errors.LockContention('(remote lock)')
309
assert False, 'unexpected response code %s' % (response,)
311
def lock_write(self, token=None):
312
if not self._lock_mode:
313
self._lock_token = self._remote_lock_write(token)
314
assert self._lock_token, 'Remote server did not return a token!'
315
if self._real_repository is not None:
316
self._real_repository.lock_write(token=self._lock_token)
317
if token is not None:
318
self._leave_lock = True
320
self._leave_lock = False
321
self._lock_mode = 'w'
323
elif self._lock_mode == 'r':
324
raise errors.ReadOnlyError(self)
326
self._lock_count += 1
327
return self._lock_token
329
def leave_lock_in_place(self):
330
self._leave_lock = True
332
def dont_leave_lock_in_place(self):
333
self._leave_lock = False
335
def _set_real_repository(self, repository):
336
"""Set the _real_repository for this repository.
338
:param repository: The repository to fallback to for non-hpss
339
implemented operations.
341
self._real_repository = repository
342
if self._lock_mode == 'w':
343
# if we are already locked, the real repository must be able to
344
# acquire the lock with our token.
345
self._real_repository.lock_write(self._lock_token)
346
elif self._lock_mode == 'r':
347
self._real_repository.lock_read()
349
def _unlock(self, token):
350
path = self.bzrdir._path_for_remote_call(self._client)
351
response = self._client.call('Repository.unlock', path, token)
352
if response == ('ok',):
354
elif response[0] == 'TokenMismatch':
355
raise errors.TokenMismatch(token, '(remote token)')
357
assert False, 'unexpected response code %s' % (response,)
360
self._lock_count -= 1
361
if not self._lock_count:
362
mode = self._lock_mode
363
self._lock_mode = None
364
if self._real_repository is not None:
365
self._real_repository.unlock()
368
assert self._lock_token, 'Locked, but no token!'
369
token = self._lock_token
370
self._lock_token = None
371
if not self._leave_lock:
374
def break_lock(self):
375
# should hand off to the network
377
return self._real_repository.break_lock()
379
### These methods are just thin shims to the VFS object for now.
381
def revision_tree(self, revision_id):
383
return self._real_repository.revision_tree(revision_id)
385
def get_commit_builder(self, branch, parents, config, timestamp=None,
386
timezone=None, committer=None, revprops=None,
388
# FIXME: It ought to be possible to call this without immediately
389
# triggering _ensure_real. For now it's the easiest thing to do.
391
builder = self._real_repository.get_commit_builder(branch, parents,
392
config, timestamp=timestamp, timezone=timezone,
393
committer=committer, revprops=revprops, revision_id=revision_id)
394
# Make the builder use this RemoteRepository rather than the real one.
395
builder.repository = self
399
def add_inventory(self, revid, inv, parents):
401
return self._real_repository.add_inventory(revid, inv, parents)
404
def add_revision(self, rev_id, rev, inv=None, config=None):
406
return self._real_repository.add_revision(
407
rev_id, rev, inv=inv, config=config)
410
def get_inventory(self, revision_id):
412
return self._real_repository.get_inventory(revision_id)
415
def get_revision(self, revision_id):
417
return self._real_repository.get_revision(revision_id)
420
def weave_store(self):
422
return self._real_repository.weave_store
424
def get_transaction(self):
426
return self._real_repository.get_transaction()
429
def clone(self, a_bzrdir, revision_id=None, basis=None):
431
return self._real_repository.clone(
432
a_bzrdir, revision_id=revision_id, basis=basis)
434
def make_working_trees(self):
437
def fetch(self, source, revision_id=None, pb=None):
439
return self._real_repository.fetch(
440
source, revision_id=revision_id, pb=pb)
443
def control_weaves(self):
445
return self._real_repository.control_weaves
448
def get_ancestry(self, revision_id):
450
return self._real_repository.get_ancestry(revision_id)
453
def get_inventory_weave(self):
455
return self._real_repository.get_inventory_weave()
457
def fileids_altered_by_revision_ids(self, revision_ids):
459
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
462
def get_signature_text(self, revision_id):
464
return self._real_repository.get_signature_text(revision_id)
467
def get_revision_graph_with_ghosts(self, revision_ids=None):
469
return self._real_repository.get_revision_graph_with_ghosts(
470
revision_ids=revision_ids)
473
def get_inventory_xml(self, revision_id):
475
return self._real_repository.get_inventory_xml(revision_id)
477
def deserialise_inventory(self, revision_id, xml):
479
return self._real_repository.deserialise_inventory(revision_id, xml)
481
def reconcile(self, other=None, thorough=False):
483
return self._real_repository.reconcile(other=other, thorough=thorough)
485
def all_revision_ids(self):
487
return self._real_repository.all_revision_ids()
490
def get_deltas_for_revisions(self, revisions):
492
return self._real_repository.get_deltas_for_revisions(revisions)
495
def get_revision_delta(self, revision_id):
497
return self._real_repository.get_revision_delta(revision_id)
500
def revision_trees(self, revision_ids):
502
return self._real_repository.revision_trees(revision_ids)
505
def get_revision_reconcile(self, revision_id):
507
return self._real_repository.get_revision_reconcile(revision_id)
510
def check(self, revision_ids):
512
return self._real_repository.check(revision_ids)
514
def copy_content_into(self, destination, revision_id=None, basis=None):
516
return self._real_repository.copy_content_into(
517
destination, revision_id=revision_id, basis=basis)
519
def set_make_working_trees(self, new_value):
520
raise NotImplementedError(self.set_make_working_trees)
523
def sign_revision(self, revision_id, gpg_strategy):
525
return self._real_repository.sign_revision(revision_id, gpg_strategy)
528
def get_revisions(self, revision_ids):
530
return self._real_repository.get_revisions(revision_ids)
532
def supports_rich_root(self):
534
return self._real_repository.supports_rich_root()
536
def iter_reverse_revision_history(self, revision_id):
538
return self._real_repository.iter_reverse_revision_history(revision_id)
541
class RemoteBranchLockableFiles(object):
542
"""A 'LockableFiles' implementation that talks to a smart server.
544
This is not a public interface class.
547
def __init__(self, bzrdir, _client):
549
self._client = _client
552
"""'get' a remote path as per the LockableFiles interface.
554
:param path: the file to 'get'. If this is 'branch.conf', we do not
555
just retrieve a file, instead we ask the smart server to generate
556
a configuration for us - which is retrieved as an INI file.
558
assert path == 'branch.conf'
559
path = self.bzrdir._path_for_remote_call(self._client)
560
response = self._client.call2('Branch.get_config_file', path)
561
assert response[0][0] == 'ok', \
562
'unexpected response code %s' % (response[0],)
563
return StringIO(response[1].read_body_bytes())
566
class RemoteBranchFormat(branch.BranchFormat):
568
def get_format_description(self):
569
return 'Remote BZR Branch'
571
def get_format_string(self):
572
return 'Remote BZR Branch'
574
def open(self, a_bzrdir):
575
assert isinstance(a_bzrdir, RemoteBzrDir)
576
return a_bzrdir.open_branch()
578
def initialize(self, a_bzrdir):
579
assert isinstance(a_bzrdir, RemoteBzrDir)
580
return a_bzrdir.create_branch()
583
class RemoteBranch(branch.Branch):
584
"""Branch stored on a server accessed by HPSS RPC.
586
At the moment most operations are mapped down to simple file operations.
589
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
591
"""Create a RemoteBranch instance.
593
:param real_branch: An optional local implementation of the branch
594
format, usually accessing the data via the VFS.
595
:param _client: Private parameter for testing.
597
self.bzrdir = remote_bzrdir
598
if _client is not None:
599
self._client = _client
601
self._client = client.SmartClient(self.bzrdir._medium)
602
self.repository = remote_repository
603
if real_branch is not None:
604
self._real_branch = real_branch
605
# Give the remote repository the matching real repo.
606
self.repository._set_real_repository(self._real_branch.repository)
607
# Give the branch the remote repository to let fast-pathing happen.
608
self._real_branch.repository = self.repository
610
self._real_branch = None
611
# Fill out expected attributes of branch for bzrlib api users.
612
self._format = RemoteBranchFormat()
613
self.base = self.bzrdir.root_transport.base
614
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
615
self._lock_mode = None
616
self._lock_token = None
618
self._leave_lock = False
620
def _ensure_real(self):
621
"""Ensure that there is a _real_branch set.
623
used before calls to self._real_branch.
625
if not self._real_branch:
626
assert vfs.vfs_enabled()
627
self.bzrdir._ensure_real()
628
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
629
# Give the remote repository the matching real repo.
630
self.repository._set_real_repository(self._real_branch.repository)
631
# Give the branch the remote repository to let fast-pathing happen.
632
self._real_branch.repository = self.repository
633
# XXX: deal with _lock_mode == 'w'
634
if self._lock_mode == 'r':
635
self._real_branch.lock_read()
637
def get_physical_lock_status(self):
638
"""See Branch.get_physical_lock_status()."""
639
# should be an API call to the server, as branches must be lockable.
641
return self._real_branch.get_physical_lock_status()
644
if not self._lock_mode:
645
self._lock_mode = 'r'
647
if self._real_branch is not None:
648
self._real_branch.lock_read()
650
self._lock_count += 1
652
def _remote_lock_write(self, tokens):
654
branch_token = repo_token = ''
656
branch_token, repo_token = tokens
657
path = self.bzrdir._path_for_remote_call(self._client)
658
response = self._client.call('Branch.lock_write', path, branch_token,
660
if response[0] == 'ok':
661
ok, branch_token, repo_token = response
662
return branch_token, repo_token
663
elif response[0] == 'LockContention':
664
raise errors.LockContention('(remote lock)')
665
elif response[0] == 'TokenMismatch':
666
raise errors.TokenMismatch(tokens, '(remote tokens)')
668
assert False, 'unexpected response code %r' % (response,)
670
def lock_write(self, tokens=None):
671
if not self._lock_mode:
672
remote_tokens = self._remote_lock_write(tokens)
673
self._lock_token, self._repo_lock_token = remote_tokens
674
assert self._lock_token, 'Remote server did not return a token!'
675
# TODO: We really, really, really don't want to call _ensure_real
676
# here, but it's the easiest way to ensure coherency between the
677
# state of the RemoteBranch and RemoteRepository objects and the
678
# physical locks. If we don't materialise the real objects here,
679
# then getting everything in the right state later is complex, so
680
# for now we just do it the lazy way.
681
# -- Andrew Bennetts, 2007-02-22.
683
if self._real_branch is not None:
684
self._real_branch.lock_write(tokens=remote_tokens)
685
if tokens is not None:
686
self._leave_lock = True
688
# XXX: this case seems to be unreachable; tokens cannot be None.
689
self._leave_lock = False
690
self._lock_mode = 'w'
692
elif self._lock_mode == 'r':
693
raise errors.ReadOnlyTransaction
695
if tokens is not None:
696
# Tokens were given to lock_write, and we're relocking, so check
697
# that the given tokens actually match the ones we already have.
698
held_tokens = (self._lock_token, self._repo_lock_token)
699
if tokens != held_tokens:
700
raise errors.TokenMismatch(str(tokens), str(held_tokens))
701
self._lock_count += 1
702
return self._lock_token, self._repo_lock_token
704
def _unlock(self, branch_token, repo_token):
705
path = self.bzrdir._path_for_remote_call(self._client)
706
response = self._client.call('Branch.unlock', path, branch_token,
708
if response == ('ok',):
710
elif response[0] == 'TokenMismatch':
711
raise errors.TokenMismatch(
712
str((branch_token, repo_token)), '(remote tokens)')
714
assert False, 'unexpected response code %s' % (response,)
717
self._lock_count -= 1
718
if not self._lock_count:
719
mode = self._lock_mode
720
self._lock_mode = None
721
if self._real_branch is not None:
722
if not self._leave_lock:
723
# If this RemoteBranch will remove the physical lock for the
724
# repository, make sure the _real_branch doesn't do it
725
# first. (Because the _real_branch's repository is set to
726
# be the RemoteRepository.)
727
self._real_branch.repository.leave_lock_in_place()
728
self._real_branch.unlock()
731
assert self._lock_token, 'Locked, but no token!'
732
branch_token = self._lock_token
733
repo_token = self._repo_lock_token
734
self._lock_token = None
735
self._repo_lock_token = None
736
if not self._leave_lock:
737
self._unlock(branch_token, repo_token)
739
def break_lock(self):
741
return self._real_branch.break_lock()
743
def leave_lock_in_place(self):
744
self._leave_lock = True
746
def dont_leave_lock_in_place(self):
747
self._leave_lock = False
749
def last_revision_info(self):
750
"""See Branch.last_revision_info()."""
751
path = self.bzrdir._path_for_remote_call(self._client)
752
response = self._client.call('Branch.last_revision_info', path)
753
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
754
revno = int(response[1])
755
last_revision = response[2]
756
if last_revision == '':
757
last_revision = NULL_REVISION
758
return (revno, last_revision)
760
def revision_history(self):
761
"""See Branch.revision_history()."""
762
# XXX: TODO: this does not cache the revision history for the duration
763
# of a lock, which is a bug - see the code for regular branches
765
path = self.bzrdir._path_for_remote_call(self._client)
766
response = self._client.call2('Branch.revision_history', path)
767
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
768
result = response[1].read_body_bytes().split('\x00')
774
def set_revision_history(self, rev_history):
775
# Send just the tip revision of the history; the server will generate
776
# the full history from that. If the revision doesn't exist in this
777
# branch, NoSuchRevision will be raised.
778
path = self.bzrdir._path_for_remote_call(self._client)
779
if rev_history == []:
782
rev_id = rev_history[-1]
783
response = self._client.call('Branch.set_last_revision',
784
path, self._lock_token, self._repo_lock_token, rev_id)
785
if response[0] == 'NoSuchRevision':
786
raise NoSuchRevision(self, rev_id)
788
assert response == ('ok',), (
789
'unexpected response code %r' % (response,))
791
def get_parent(self):
793
return self._real_branch.get_parent()
795
def set_parent(self, url):
797
return self._real_branch.set_parent(url)
799
def get_config(self):
800
return RemoteBranchConfig(self)
803
def append_revision(self, *revision_ids):
805
return self._real_branch.append_revision(*revision_ids)
808
def pull(self, source, overwrite=False, stop_revision=None):
810
self._real_branch.pull(
811
source, overwrite=overwrite, stop_revision=stop_revision)
814
def push(self, target, overwrite=False, stop_revision=None):
816
self._real_branch.push(
817
target, overwrite=overwrite, stop_revision=stop_revision)
820
return self._lock_count >= 1
822
def set_last_revision_info(self, revno, revision_id):
824
return self._real_branch.set_last_revision_info(revno, revision_id)
827
class RemoteWorkingTree(object):
829
def __init__(self, remote_bzrdir, real_workingtree):
830
self.real_workingtree = real_workingtree
831
self.bzrdir = remote_bzrdir
833
def __getattr__(self, name):
834
# XXX: temporary way to lazily delegate everything to the real
836
return getattr(self.real_workingtree, name)
839
class RemoteBranchConfig(BranchConfig):
842
self.branch._ensure_real()
843
return self.branch._real_branch.get_config().username()
845
def _get_branch_data_config(self):
846
self.branch._ensure_real()
847
if self._branch_data_config is None:
848
self._branch_data_config = TreeConfig(self.branch._real_branch)
849
return self._branch_data_config