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)')
319
assert False, 'unexpected response code %s' % (response,)
321
def lock_write(self, token=None):
322
if not self._lock_mode:
323
self._lock_token = self._remote_lock_write(token)
324
assert self._lock_token, 'Remote server did not return a token!'
325
if self._real_repository is not None:
326
self._real_repository.lock_write(token=self._lock_token)
327
if token is not None:
328
self._leave_lock = True
330
self._leave_lock = False
331
self._lock_mode = 'w'
333
elif self._lock_mode == 'r':
334
raise errors.ReadOnlyError(self)
336
self._lock_count += 1
337
return self._lock_token
339
def leave_lock_in_place(self):
340
self._leave_lock = True
342
def dont_leave_lock_in_place(self):
343
self._leave_lock = False
345
def _set_real_repository(self, repository):
346
"""Set the _real_repository for this repository.
348
:param repository: The repository to fallback to for non-hpss
349
implemented operations.
351
self._real_repository = repository
352
if self._lock_mode == 'w':
353
# if we are already locked, the real repository must be able to
354
# acquire the lock with our token.
355
self._real_repository.lock_write(self._lock_token)
356
elif self._lock_mode == 'r':
357
self._real_repository.lock_read()
359
def _unlock(self, token):
360
path = self.bzrdir._path_for_remote_call(self._client)
361
response = self._client.call('Repository.unlock', path, token)
362
if response == ('ok',):
364
elif response[0] == 'TokenMismatch':
365
raise errors.TokenMismatch(token, '(remote token)')
367
assert False, 'unexpected response code %s' % (response,)
370
self._lock_count -= 1
371
if not self._lock_count:
372
mode = self._lock_mode
373
self._lock_mode = None
374
if self._real_repository is not None:
375
self._real_repository.unlock()
378
assert self._lock_token, 'Locked, but no token!'
379
token = self._lock_token
380
self._lock_token = None
381
if not self._leave_lock:
384
def break_lock(self):
385
# should hand off to the network
387
return self._real_repository.break_lock()
389
### These methods are just thin shims to the VFS object for now.
391
def revision_tree(self, revision_id):
393
return self._real_repository.revision_tree(revision_id)
395
def get_commit_builder(self, branch, parents, config, timestamp=None,
396
timezone=None, committer=None, revprops=None,
398
# FIXME: It ought to be possible to call this without immediately
399
# triggering _ensure_real. For now it's the easiest thing to do.
401
builder = self._real_repository.get_commit_builder(branch, parents,
402
config, timestamp=timestamp, timezone=timezone,
403
committer=committer, revprops=revprops, revision_id=revision_id)
404
# Make the builder use this RemoteRepository rather than the real one.
405
builder.repository = self
409
def add_inventory(self, revid, inv, parents):
411
return self._real_repository.add_inventory(revid, inv, parents)
414
def add_revision(self, rev_id, rev, inv=None, config=None):
416
return self._real_repository.add_revision(
417
rev_id, rev, inv=inv, config=config)
420
def get_inventory(self, revision_id):
422
return self._real_repository.get_inventory(revision_id)
425
def get_revision(self, revision_id):
427
return self._real_repository.get_revision(revision_id)
430
def weave_store(self):
432
return self._real_repository.weave_store
434
def get_transaction(self):
436
return self._real_repository.get_transaction()
439
def clone(self, a_bzrdir, revision_id=None, basis=None):
441
return self._real_repository.clone(
442
a_bzrdir, revision_id=revision_id, basis=basis)
444
def make_working_trees(self):
447
def fetch(self, source, revision_id=None, pb=None):
449
return self._real_repository.fetch(
450
source, revision_id=revision_id, pb=pb)
453
def control_weaves(self):
455
return self._real_repository.control_weaves
458
def get_ancestry(self, revision_id):
460
return self._real_repository.get_ancestry(revision_id)
463
def get_inventory_weave(self):
465
return self._real_repository.get_inventory_weave()
467
def fileids_altered_by_revision_ids(self, revision_ids):
469
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
472
def get_signature_text(self, revision_id):
474
return self._real_repository.get_signature_text(revision_id)
477
def get_revision_graph_with_ghosts(self, revision_ids=None):
479
return self._real_repository.get_revision_graph_with_ghosts(
480
revision_ids=revision_ids)
483
def get_inventory_xml(self, revision_id):
485
return self._real_repository.get_inventory_xml(revision_id)
487
def deserialise_inventory(self, revision_id, xml):
489
return self._real_repository.deserialise_inventory(revision_id, xml)
491
def reconcile(self, other=None, thorough=False):
493
return self._real_repository.reconcile(other=other, thorough=thorough)
495
def all_revision_ids(self):
497
return self._real_repository.all_revision_ids()
500
def get_deltas_for_revisions(self, revisions):
502
return self._real_repository.get_deltas_for_revisions(revisions)
505
def get_revision_delta(self, revision_id):
507
return self._real_repository.get_revision_delta(revision_id)
510
def revision_trees(self, revision_ids):
512
return self._real_repository.revision_trees(revision_ids)
515
def get_revision_reconcile(self, revision_id):
517
return self._real_repository.get_revision_reconcile(revision_id)
520
def check(self, revision_ids):
522
return self._real_repository.check(revision_ids)
524
def copy_content_into(self, destination, revision_id=None, basis=None):
526
return self._real_repository.copy_content_into(
527
destination, revision_id=revision_id, basis=basis)
529
def set_make_working_trees(self, new_value):
530
raise NotImplementedError(self.set_make_working_trees)
533
def sign_revision(self, revision_id, gpg_strategy):
535
return self._real_repository.sign_revision(revision_id, gpg_strategy)
538
def get_revisions(self, revision_ids):
540
return self._real_repository.get_revisions(revision_ids)
542
def supports_rich_root(self):
544
return self._real_repository.supports_rich_root()
546
def iter_reverse_revision_history(self, revision_id):
548
return self._real_repository.iter_reverse_revision_history(revision_id)
551
class RemoteBranchLockableFiles(object):
552
"""A 'LockableFiles' implementation that talks to a smart server.
554
This is not a public interface class.
557
def __init__(self, bzrdir, _client):
559
self._client = _client
562
"""'get' a remote path as per the LockableFiles interface.
564
:param path: the file to 'get'. If this is 'branch.conf', we do not
565
just retrieve a file, instead we ask the smart server to generate
566
a configuration for us - which is retrieved as an INI file.
568
assert path == 'branch.conf'
569
path = self.bzrdir._path_for_remote_call(self._client)
570
response = self._client.call2('Branch.get_config_file', path)
571
assert response[0][0] == 'ok', \
572
'unexpected response code %s' % (response[0],)
573
return StringIO(response[1].read_body_bytes())
576
class RemoteBranchFormat(branch.BranchFormat):
578
def get_format_description(self):
579
return 'Remote BZR Branch'
581
def get_format_string(self):
582
return 'Remote BZR Branch'
584
def open(self, a_bzrdir):
585
assert isinstance(a_bzrdir, RemoteBzrDir)
586
return a_bzrdir.open_branch()
588
def initialize(self, a_bzrdir):
589
assert isinstance(a_bzrdir, RemoteBzrDir)
590
return a_bzrdir.create_branch()
593
class RemoteBranch(branch.Branch):
594
"""Branch stored on a server accessed by HPSS RPC.
596
At the moment most operations are mapped down to simple file operations.
599
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
601
"""Create a RemoteBranch instance.
603
:param real_branch: An optional local implementation of the branch
604
format, usually accessing the data via the VFS.
605
:param _client: Private parameter for testing.
607
self.bzrdir = remote_bzrdir
608
if _client is not None:
609
self._client = _client
611
self._client = client.SmartClient(self.bzrdir._medium)
612
self.repository = remote_repository
613
if real_branch is not None:
614
self._real_branch = real_branch
615
# Give the remote repository the matching real repo.
616
self.repository._set_real_repository(self._real_branch.repository)
617
# Give the branch the remote repository to let fast-pathing happen.
618
self._real_branch.repository = self.repository
620
self._real_branch = None
621
# Fill out expected attributes of branch for bzrlib api users.
622
self._format = RemoteBranchFormat()
623
self.base = self.bzrdir.root_transport.base
624
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
625
self._lock_mode = None
626
self._lock_token = None
628
self._leave_lock = False
630
def _ensure_real(self):
631
"""Ensure that there is a _real_branch set.
633
used before calls to self._real_branch.
635
if not self._real_branch:
636
assert vfs.vfs_enabled()
637
self.bzrdir._ensure_real()
638
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
639
# Give the remote repository the matching real repo.
640
self.repository._set_real_repository(self._real_branch.repository)
641
# Give the branch the remote repository to let fast-pathing happen.
642
self._real_branch.repository = self.repository
643
# XXX: deal with _lock_mode == 'w'
644
if self._lock_mode == 'r':
645
self._real_branch.lock_read()
647
def get_physical_lock_status(self):
648
"""See Branch.get_physical_lock_status()."""
649
# should be an API call to the server, as branches must be lockable.
651
return self._real_branch.get_physical_lock_status()
654
if not self._lock_mode:
655
self._lock_mode = 'r'
657
if self._real_branch is not None:
658
self._real_branch.lock_read()
660
self._lock_count += 1
662
def _remote_lock_write(self, tokens):
664
branch_token = repo_token = ''
666
branch_token, repo_token = tokens
667
path = self.bzrdir._path_for_remote_call(self._client)
668
response = self._client.call('Branch.lock_write', path, branch_token,
670
if response[0] == 'ok':
671
ok, branch_token, repo_token = response
672
return branch_token, repo_token
673
elif response[0] == 'LockContention':
674
raise errors.LockContention('(remote lock)')
675
elif response[0] == 'TokenMismatch':
676
raise errors.TokenMismatch(tokens, '(remote tokens)')
678
assert False, 'unexpected response code %r' % (response,)
680
def lock_write(self, tokens=None):
681
if not self._lock_mode:
682
remote_tokens = self._remote_lock_write(tokens)
683
self._lock_token, self._repo_lock_token = remote_tokens
684
assert self._lock_token, 'Remote server did not return a token!'
685
# TODO: We really, really, really don't want to call _ensure_real
686
# here, but it's the easiest way to ensure coherency between the
687
# state of the RemoteBranch and RemoteRepository objects and the
688
# physical locks. If we don't materialise the real objects here,
689
# then getting everything in the right state later is complex, so
690
# for now we just do it the lazy way.
691
# -- Andrew Bennetts, 2007-02-22.
693
if self._real_branch is not None:
694
self._real_branch.lock_write(tokens=remote_tokens)
695
if tokens is not None:
696
self._leave_lock = True
698
# XXX: this case seems to be unreachable; tokens cannot be None.
699
self._leave_lock = False
700
self._lock_mode = 'w'
702
elif self._lock_mode == 'r':
703
raise errors.ReadOnlyTransaction
705
if tokens is not None:
706
# Tokens were given to lock_write, and we're relocking, so check
707
# that the given tokens actually match the ones we already have.
708
held_tokens = (self._lock_token, self._repo_lock_token)
709
if tokens != held_tokens:
710
raise errors.TokenMismatch(str(tokens), str(held_tokens))
711
self._lock_count += 1
712
return self._lock_token, self._repo_lock_token
714
def _unlock(self, branch_token, repo_token):
715
path = self.bzrdir._path_for_remote_call(self._client)
716
response = self._client.call('Branch.unlock', path, branch_token,
718
if response == ('ok',):
720
elif response[0] == 'TokenMismatch':
721
raise errors.TokenMismatch(
722
str((branch_token, repo_token)), '(remote tokens)')
724
assert False, 'unexpected response code %s' % (response,)
727
self._lock_count -= 1
728
if not self._lock_count:
729
mode = self._lock_mode
730
self._lock_mode = None
731
if self._real_branch is not None:
732
if not self._leave_lock:
733
# If this RemoteBranch will remove the physical lock for the
734
# repository, make sure the _real_branch doesn't do it
735
# first. (Because the _real_branch's repository is set to
736
# be the RemoteRepository.)
737
self._real_branch.repository.leave_lock_in_place()
738
self._real_branch.unlock()
741
assert self._lock_token, 'Locked, but no token!'
742
branch_token = self._lock_token
743
repo_token = self._repo_lock_token
744
self._lock_token = None
745
self._repo_lock_token = None
746
if not self._leave_lock:
747
self._unlock(branch_token, repo_token)
749
def break_lock(self):
751
return self._real_branch.break_lock()
753
def leave_lock_in_place(self):
754
self._leave_lock = True
756
def dont_leave_lock_in_place(self):
757
self._leave_lock = False
759
def last_revision_info(self):
760
"""See Branch.last_revision_info()."""
761
path = self.bzrdir._path_for_remote_call(self._client)
762
response = self._client.call('Branch.last_revision_info', path)
763
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
764
revno = int(response[1])
765
last_revision = response[2]
766
if last_revision == '':
767
last_revision = NULL_REVISION
768
return (revno, last_revision)
770
def revision_history(self):
771
"""See Branch.revision_history()."""
772
# XXX: TODO: this does not cache the revision history for the duration
773
# of a lock, which is a bug - see the code for regular branches
775
path = self.bzrdir._path_for_remote_call(self._client)
776
response = self._client.call2('Branch.revision_history', path)
777
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
778
result = response[1].read_body_bytes().split('\x00')
784
def set_revision_history(self, rev_history):
785
# Send just the tip revision of the history; the server will generate
786
# the full history from that. If the revision doesn't exist in this
787
# branch, NoSuchRevision will be raised.
788
path = self.bzrdir._path_for_remote_call(self._client)
789
if rev_history == []:
792
rev_id = rev_history[-1]
793
response = self._client.call('Branch.set_last_revision',
794
path, self._lock_token, self._repo_lock_token, rev_id)
795
if response[0] == 'NoSuchRevision':
796
raise NoSuchRevision(self, rev_id)
798
assert response == ('ok',), (
799
'unexpected response code %r' % (response,))
801
def get_parent(self):
803
return self._real_branch.get_parent()
805
def set_parent(self, url):
807
return self._real_branch.set_parent(url)
809
def get_config(self):
810
return RemoteBranchConfig(self)
812
def sprout(self, to_bzrdir, revision_id=None):
813
# Like Branch.sprout, except that it sprouts a branch in the default
814
# format, because RemoteBranches can't be created at arbitrary URLs.
815
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
816
# to_bzrdir.create_branch...
818
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
819
self._real_branch.copy_content_into(result, revision_id=revision_id)
820
result.set_parent(self.bzrdir.root_transport.base)
824
def append_revision(self, *revision_ids):
826
return self._real_branch.append_revision(*revision_ids)
829
def pull(self, source, overwrite=False, stop_revision=None):
831
self._real_branch.pull(
832
source, overwrite=overwrite, stop_revision=stop_revision)
835
def push(self, target, overwrite=False, stop_revision=None):
837
self._real_branch.push(
838
target, overwrite=overwrite, stop_revision=stop_revision)
841
return self._lock_count >= 1
843
def set_last_revision_info(self, revno, revision_id):
845
return self._real_branch.set_last_revision_info(revno, revision_id)
848
class RemoteWorkingTree(object):
850
def __init__(self, remote_bzrdir, real_workingtree):
851
self.real_workingtree = real_workingtree
852
self.bzrdir = remote_bzrdir
854
def __getattr__(self, name):
855
# XXX: temporary way to lazily delegate everything to the real
857
return getattr(self.real_workingtree, name)
860
class RemoteBranchConfig(BranchConfig):
863
self.branch._ensure_real()
864
return self.branch._real_branch.get_config().username()
866
def _get_branch_data_config(self):
867
self.branch._ensure_real()
868
if self._branch_data_config is None:
869
self._branch_data_config = TreeConfig(self.branch._real_branch)
870
return self._branch_data_config