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, lockdir, 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.lockable_files import LockableFiles
30
from bzrlib.revision import NULL_REVISION
31
from bzrlib.smart import client, vfs
32
from bzrlib.urlutils import unescape
34
# Note: RemoteBzrDirFormat is in bzrdir.py
36
class RemoteBzrDir(BzrDir):
37
"""Control directory on a remote server, accessed by HPSS."""
39
def __init__(self, transport, _client=None):
40
"""Construct a RemoteBzrDir.
42
:param _client: Private parameter for testing. Disables probing and the
45
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
46
# this object holds a delegated bzrdir that uses file-level operations
47
# to talk to the other side
48
# XXX: We should go into find_format, but not allow it to find
49
# RemoteBzrDirFormat and make sure it finds the real underlying format.
50
self._real_bzrdir = None
53
self._medium = transport.get_smart_client()
54
self._client = client.SmartClient(self._medium)
56
self._client = _client
61
path = self._path_for_remote_call(self._client)
62
#self._real_bzrdir._format.probe_transport(transport)
63
response = self._client.call('probe_dont_use', path)
64
if response == ('no',):
65
raise errors.NotBranchError(path=transport.base)
67
def _ensure_real(self):
68
"""Ensure that there is a _real_bzrdir set.
70
used before calls to self._real_bzrdir.
72
if not self._real_bzrdir:
73
default_format = BzrDirFormat.get_default_format()
74
self._real_bzrdir = default_format.open(self.root_transport,
77
def create_repository(self, shared=False):
78
self._real_bzrdir.create_repository(shared=shared)
79
return self.open_repository()
81
def create_branch(self):
82
real_branch = self._real_bzrdir.create_branch()
83
return RemoteBranch(self, self.find_repository(), real_branch)
85
def create_workingtree(self, revision_id=None):
86
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
87
return RemoteWorkingTree(self, real_workingtree)
89
def find_branch_format(self):
90
"""Find the branch 'format' for this bzrdir.
92
This might be a synthetic object for e.g. RemoteBranch and SVN.
94
b = self.open_branch()
97
def get_branch_reference(self):
98
"""See BzrDir.get_branch_reference()."""
99
path = self._path_for_remote_call(self._client)
100
response = self._client.call('BzrDir.open_branch', path)
101
if response[0] == 'ok':
102
if response[1] == '':
103
# branch at this location.
106
# a branch reference, use the existing BranchReference logic.
108
elif response == ('nobranch',):
109
raise errors.NotBranchError(path=self.root_transport.base)
111
assert False, 'unexpected response code %r' % (response,)
113
def open_branch(self, _unsupported=False):
114
assert _unsupported == False, 'unsupported flag support not implemented yet.'
115
reference_url = self.get_branch_reference()
116
if reference_url is None:
117
# branch at this location.
118
return RemoteBranch(self, self.find_repository())
120
# a branch reference, use the existing BranchReference logic.
121
format = BranchReferenceFormat()
122
return format.open(self, _found=True, location=reference_url)
124
def open_repository(self):
125
path = self._path_for_remote_call(self._client)
126
response = self._client.call('BzrDir.find_repository', path)
127
assert response[0] in ('ok', 'norepository'), \
128
'unexpected response code %s' % (response,)
129
if response[0] == 'norepository':
130
raise errors.NoRepositoryPresent(self)
131
assert len(response) == 4, 'incorrect response length %s' % (response,)
132
if response[1] == '':
133
format = RemoteRepositoryFormat()
134
format.rich_root_data = response[2] == 'True'
135
format.supports_tree_reference = response[3] == 'True'
136
return RemoteRepository(self, format)
138
raise errors.NoRepositoryPresent(self)
140
def open_workingtree(self, recommend_upgrade=True):
141
raise errors.NotLocalUrl(self.root_transport)
143
def _path_for_remote_call(self, client):
144
"""Return the path to be used for this bzrdir in a remote call."""
145
return client.remote_path_from_transport(self.root_transport)
147
def get_branch_transport(self, branch_format):
148
return self._real_bzrdir.get_branch_transport(branch_format)
150
def get_repository_transport(self, repository_format):
151
return self._real_bzrdir.get_repository_transport(repository_format)
153
def get_workingtree_transport(self, workingtree_format):
154
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
156
def can_convert_format(self):
157
"""Upgrading of remote bzrdirs is not supported yet."""
160
def needs_format_conversion(self, format=None):
161
"""Upgrading of remote bzrdirs is not supported yet."""
164
def clone(self, url, revision_id=None, force_new_repo=False):
166
return self._real_bzrdir.clone(url, revision_id=revision_id,
167
force_new_repo=force_new_repo)
170
class RemoteRepositoryFormat(repository.RepositoryFormat):
171
"""Format for repositories accessed over a SmartClient.
173
Instances of this repository are represented by RemoteRepository
176
The RemoteRepositoryFormat is parameterised during construction
177
to reflect the capabilities of the real, remote format. Specifically
178
the attributes rich_root_data and supports_tree_reference are set
179
on a per instance basis, and are not set (and should not be) at
183
_matchingbzrdir = RemoteBzrDirFormat
185
def initialize(self, a_bzrdir, shared=False):
186
assert isinstance(a_bzrdir, RemoteBzrDir), \
187
'%r is not a RemoteBzrDir' % (a_bzrdir,)
188
return a_bzrdir.create_repository(shared=shared)
190
def open(self, a_bzrdir):
191
assert isinstance(a_bzrdir, RemoteBzrDir)
192
return a_bzrdir.open_repository()
194
def get_format_description(self):
195
return 'bzr remote repository'
197
def __eq__(self, other):
198
return self.__class__ == other.__class__
200
def check_conversion_target(self, target_format):
201
if self.rich_root_data and not target_format.rich_root_data:
202
raise errors.BadConversionTarget(
203
'Does not support rich root data.', target_format)
204
if (self.supports_tree_reference and
205
not getattr(target_format, 'supports_tree_reference', False)):
206
raise errors.BadConversionTarget(
207
'Does not support nested trees', target_format)
210
class RemoteRepository(object):
211
"""Repository accessed over rpc.
213
For the moment everything is delegated to IO-like operations over
217
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
218
"""Create a RemoteRepository instance.
220
:param remote_bzrdir: The bzrdir hosting this repository.
221
:param format: The RemoteFormat object to use.
222
:param real_repository: If not None, a local implementation of the
223
repository logic for the repository, usually accessing the data
225
:param _client: Private testing parameter - override the smart client
226
to be used by the repository.
229
self._real_repository = real_repository
231
self._real_repository = None
232
self.bzrdir = remote_bzrdir
234
self._client = client.SmartClient(self.bzrdir._medium)
236
self._client = _client
237
self._format = format
238
self._lock_mode = None
239
self._lock_token = None
241
self._leave_lock = False
243
def _ensure_real(self):
244
"""Ensure that there is a _real_repository set.
246
used before calls to self._real_repository.
248
if not self._real_repository:
249
self.bzrdir._ensure_real()
250
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
251
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
253
def get_revision_graph(self, revision_id=None):
254
"""See Repository.get_revision_graph()."""
255
if revision_id is None:
257
elif revision_id == NULL_REVISION:
260
path = self.bzrdir._path_for_remote_call(self._client)
261
assert type(revision_id) is str
262
response = self._client.call2(
263
'Repository.get_revision_graph', path, revision_id)
264
assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
265
if response[0][0] == 'ok':
266
coded = response[1].read_body_bytes()
268
# no revisions in this repository!
270
lines = coded.split('\n')
274
d = list(line.split())
275
revision_graph[d[0]] = d[1:]
277
return revision_graph
279
response_body = response[1].read_body_bytes()
280
assert response_body == ''
281
raise NoSuchRevision(self, revision_id)
283
def has_revision(self, revision_id):
284
"""See Repository.has_revision()."""
285
if revision_id is None:
286
# The null revision is always present.
288
path = self.bzrdir._path_for_remote_call(self._client)
289
response = self._client.call('Repository.has_revision', path, revision_id)
290
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
291
return response[0] == 'ok'
293
def gather_stats(self, revid=None, committers=None):
294
"""See Repository.gather_stats()."""
295
path = self.bzrdir._path_for_remote_call(self._client)
296
if revid in (None, NULL_REVISION):
300
if committers is None or not committers:
301
fmt_committers = 'no'
303
fmt_committers = 'yes'
304
response = self._client.call2('Repository.gather_stats', path,
305
fmt_revid, fmt_committers)
306
assert response[0][0] == 'ok', \
307
'unexpected response code %s' % (response[0],)
309
body = response[1].read_body_bytes()
311
for line in body.split('\n'):
314
key, val_text = line.split(':')
315
if key in ('revisions', 'size', 'committers'):
316
result[key] = int(val_text)
317
elif key in ('firstrev', 'latestrev'):
318
values = val_text.split(' ')[1:]
319
result[key] = (float(values[0]), long(values[1]))
323
def get_physical_lock_status(self):
324
"""See Repository.get_physical_lock_status()."""
328
"""See Repository.is_shared()."""
329
path = self.bzrdir._path_for_remote_call(self._client)
330
response = self._client.call('Repository.is_shared', path)
331
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
332
return response[0] == 'yes'
335
# wrong eventually - want a local lock cache context
336
if not self._lock_mode:
337
self._lock_mode = 'r'
339
if self._real_repository is not None:
340
self._real_repository.lock_read()
342
self._lock_count += 1
344
def _remote_lock_write(self, token):
345
path = self.bzrdir._path_for_remote_call(self._client)
348
response = self._client.call('Repository.lock_write', path, token)
349
if response[0] == 'ok':
352
elif response[0] == 'LockContention':
353
raise errors.LockContention('(remote lock)')
354
elif response[0] == 'UnlockableTransport':
355
raise errors.UnlockableTransport(self.bzrdir.root_transport)
357
assert False, 'unexpected response code %s' % (response,)
359
def lock_write(self, token=None):
360
if not self._lock_mode:
361
self._lock_token = self._remote_lock_write(token)
362
assert self._lock_token, 'Remote server did not return a token!'
363
if self._real_repository is not None:
364
self._real_repository.lock_write(token=self._lock_token)
365
if token is not None:
366
self._leave_lock = True
368
self._leave_lock = False
369
self._lock_mode = 'w'
371
elif self._lock_mode == 'r':
372
raise errors.ReadOnlyError(self)
374
self._lock_count += 1
375
return self._lock_token
377
def leave_lock_in_place(self):
378
self._leave_lock = True
380
def dont_leave_lock_in_place(self):
381
self._leave_lock = False
383
def _set_real_repository(self, repository):
384
"""Set the _real_repository for this repository.
386
:param repository: The repository to fallback to for non-hpss
387
implemented operations.
389
assert not isinstance(repository, RemoteRepository)
390
self._real_repository = repository
391
if self._lock_mode == 'w':
392
# if we are already locked, the real repository must be able to
393
# acquire the lock with our token.
394
self._real_repository.lock_write(self._lock_token)
395
elif self._lock_mode == 'r':
396
self._real_repository.lock_read()
398
def _unlock(self, token):
399
path = self.bzrdir._path_for_remote_call(self._client)
400
response = self._client.call('Repository.unlock', path, token)
401
if response == ('ok',):
403
elif response[0] == 'TokenMismatch':
404
raise errors.TokenMismatch(token, '(remote token)')
406
assert False, 'unexpected response code %s' % (response,)
409
self._lock_count -= 1
410
if not self._lock_count:
411
mode = self._lock_mode
412
self._lock_mode = None
413
if self._real_repository is not None:
414
self._real_repository.unlock()
417
assert self._lock_token, 'Locked, but no token!'
418
token = self._lock_token
419
self._lock_token = None
420
if not self._leave_lock:
423
def break_lock(self):
424
# should hand off to the network
426
return self._real_repository.break_lock()
428
### These methods are just thin shims to the VFS object for now.
430
def revision_tree(self, revision_id):
432
return self._real_repository.revision_tree(revision_id)
434
def get_commit_builder(self, branch, parents, config, timestamp=None,
435
timezone=None, committer=None, revprops=None,
437
# FIXME: It ought to be possible to call this without immediately
438
# triggering _ensure_real. For now it's the easiest thing to do.
440
builder = self._real_repository.get_commit_builder(branch, parents,
441
config, timestamp=timestamp, timezone=timezone,
442
committer=committer, revprops=revprops, revision_id=revision_id)
443
# Make the builder use this RemoteRepository rather than the real one.
444
builder.repository = self
448
def add_inventory(self, revid, inv, parents):
450
return self._real_repository.add_inventory(revid, inv, parents)
453
def add_revision(self, rev_id, rev, inv=None, config=None):
455
return self._real_repository.add_revision(
456
rev_id, rev, inv=inv, config=config)
459
def get_inventory(self, revision_id):
461
return self._real_repository.get_inventory(revision_id)
464
def get_revision(self, revision_id):
466
return self._real_repository.get_revision(revision_id)
469
def weave_store(self):
471
return self._real_repository.weave_store
473
def get_transaction(self):
475
return self._real_repository.get_transaction()
478
def clone(self, a_bzrdir, revision_id=None):
480
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
482
def make_working_trees(self):
483
"""RemoteRepositories never create working trees by default."""
486
def fetch(self, source, revision_id=None, pb=None):
488
return self._real_repository.fetch(
489
source, revision_id=revision_id, pb=pb)
492
def control_weaves(self):
494
return self._real_repository.control_weaves
497
def get_ancestry(self, revision_id):
499
return self._real_repository.get_ancestry(revision_id)
502
def get_inventory_weave(self):
504
return self._real_repository.get_inventory_weave()
506
def fileids_altered_by_revision_ids(self, revision_ids):
508
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
511
def get_signature_text(self, revision_id):
513
return self._real_repository.get_signature_text(revision_id)
516
def get_revision_graph_with_ghosts(self, revision_ids=None):
518
return self._real_repository.get_revision_graph_with_ghosts(
519
revision_ids=revision_ids)
522
def get_inventory_xml(self, revision_id):
524
return self._real_repository.get_inventory_xml(revision_id)
526
def deserialise_inventory(self, revision_id, xml):
528
return self._real_repository.deserialise_inventory(revision_id, xml)
530
def reconcile(self, other=None, thorough=False):
532
return self._real_repository.reconcile(other=other, thorough=thorough)
534
def all_revision_ids(self):
536
return self._real_repository.all_revision_ids()
539
def get_deltas_for_revisions(self, revisions):
541
return self._real_repository.get_deltas_for_revisions(revisions)
544
def get_revision_delta(self, revision_id):
546
return self._real_repository.get_revision_delta(revision_id)
549
def revision_trees(self, revision_ids):
551
return self._real_repository.revision_trees(revision_ids)
554
def get_revision_reconcile(self, revision_id):
556
return self._real_repository.get_revision_reconcile(revision_id)
559
def check(self, revision_ids):
561
return self._real_repository.check(revision_ids)
563
def copy_content_into(self, destination, revision_id=None):
565
return self._real_repository.copy_content_into(
566
destination, revision_id=revision_id)
568
def set_make_working_trees(self, new_value):
569
raise NotImplementedError(self.set_make_working_trees)
572
def sign_revision(self, revision_id, gpg_strategy):
574
return self._real_repository.sign_revision(revision_id, gpg_strategy)
577
def get_revisions(self, revision_ids):
579
return self._real_repository.get_revisions(revision_ids)
581
def supports_rich_root(self):
583
return self._real_repository.supports_rich_root()
585
def iter_reverse_revision_history(self, revision_id):
587
return self._real_repository.iter_reverse_revision_history(revision_id)
590
def _serializer(self):
592
return self._real_repository._serializer
594
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
596
return self._real_repository.store_revision_signature(
597
gpg_strategy, plaintext, revision_id)
599
def has_signature_for_revision_id(self, revision_id):
601
return self._real_repository.has_signature_for_revision_id(revision_id)
604
class RemoteBranchLockableFiles(LockableFiles):
605
"""A 'LockableFiles' implementation that talks to a smart server.
607
This is not a public interface class.
610
def __init__(self, bzrdir, _client):
612
self._client = _client
613
self._need_find_modes = True
614
# XXX: This assumes that the branch control directory is .bzr/branch,
615
# which isn't necessarily true.
616
LockableFiles.__init__(
617
self, bzrdir.root_transport.clone('.bzr/branch'),
618
'lock', lockdir.LockDir)
620
def _find_modes(self):
621
# RemoteBranches don't let the client set the mode of control files.
622
self._dir_mode = None
623
self._file_mode = None
626
"""'get' a remote path as per the LockableFiles interface.
628
:param path: the file to 'get'. If this is 'branch.conf', we do not
629
just retrieve a file, instead we ask the smart server to generate
630
a configuration for us - which is retrieved as an INI file.
632
if path == 'branch.conf':
633
path = self.bzrdir._path_for_remote_call(self._client)
634
response = self._client.call2('Branch.get_config_file', path)
635
assert response[0][0] == 'ok', \
636
'unexpected response code %s' % (response[0],)
637
return StringIO(response[1].read_body_bytes())
640
return LockableFiles.get(self, path)
643
class RemoteBranchFormat(branch.BranchFormat):
645
def __eq__(self, other):
646
return (isinstance(other, RemoteBranchFormat) and
647
self.__dict__ == other.__dict__)
649
def get_format_description(self):
650
return 'Remote BZR Branch'
652
def get_format_string(self):
653
return 'Remote BZR Branch'
655
def open(self, a_bzrdir):
656
assert isinstance(a_bzrdir, RemoteBzrDir)
657
return a_bzrdir.open_branch()
659
def initialize(self, a_bzrdir):
660
assert isinstance(a_bzrdir, RemoteBzrDir)
661
return a_bzrdir.create_branch()
664
class RemoteBranch(branch.Branch):
665
"""Branch stored on a server accessed by HPSS RPC.
667
At the moment most operations are mapped down to simple file operations.
670
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
672
"""Create a RemoteBranch instance.
674
:param real_branch: An optional local implementation of the branch
675
format, usually accessing the data via the VFS.
676
:param _client: Private parameter for testing.
678
#branch.Branch.__init__(self)
679
self._revision_history_cache = None
680
self.bzrdir = remote_bzrdir
681
if _client is not None:
682
self._client = _client
684
self._client = client.SmartClient(self.bzrdir._medium)
685
self.repository = remote_repository
686
if real_branch is not None:
687
self._real_branch = real_branch
688
# Give the remote repository the matching real repo.
689
real_repo = self._real_branch.repository
690
if isinstance(real_repo, RemoteRepository):
691
real_repo._ensure_real()
692
real_repo = real_repo._real_repository
693
self.repository._set_real_repository(real_repo)
694
# Give the branch the remote repository to let fast-pathing happen.
695
self._real_branch.repository = self.repository
697
self._real_branch = None
698
# Fill out expected attributes of branch for bzrlib api users.
699
self._format = RemoteBranchFormat()
700
self.base = self.bzrdir.root_transport.base
701
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
702
self._lock_mode = None
703
self._lock_token = None
705
self._leave_lock = False
707
def _ensure_real(self):
708
"""Ensure that there is a _real_branch set.
710
used before calls to self._real_branch.
712
if not self._real_branch:
713
assert vfs.vfs_enabled()
714
self.bzrdir._ensure_real()
715
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
716
# Give the remote repository the matching real repo.
717
real_repo = self._real_branch.repository
718
if isinstance(real_repo, RemoteRepository):
719
real_repo._ensure_real()
720
real_repo = real_repo._real_repository
721
self.repository._set_real_repository(real_repo)
722
# Give the branch the remote repository to let fast-pathing happen.
723
self._real_branch.repository = self.repository
724
# XXX: deal with _lock_mode == 'w'
725
if self._lock_mode == 'r':
726
self._real_branch.lock_read()
728
def get_physical_lock_status(self):
729
"""See Branch.get_physical_lock_status()."""
730
# should be an API call to the server, as branches must be lockable.
732
return self._real_branch.get_physical_lock_status()
735
if not self._lock_mode:
736
self._lock_mode = 'r'
738
if self._real_branch is not None:
739
self._real_branch.lock_read()
741
self._lock_count += 1
743
def _remote_lock_write(self, token):
745
branch_token = repo_token = ''
748
repo_token = self.repository.lock_write()
749
self.repository.unlock()
750
path = self.bzrdir._path_for_remote_call(self._client)
751
response = self._client.call('Branch.lock_write', path, branch_token,
753
if response[0] == 'ok':
754
ok, branch_token, repo_token = response
755
return branch_token, repo_token
756
elif response[0] == 'LockContention':
757
raise errors.LockContention('(remote lock)')
758
elif response[0] == 'TokenMismatch':
759
raise errors.TokenMismatch(token, '(remote token)')
760
elif response[0] == 'UnlockableTransport':
761
raise errors.UnlockableTransport(self.bzrdir.root_transport)
762
elif response[0] == 'ReadOnlyError':
763
raise errors.ReadOnlyError(self)
765
assert False, 'unexpected response code %r' % (response,)
767
def lock_write(self, token=None):
768
if not self._lock_mode:
769
remote_tokens = self._remote_lock_write(token)
770
self._lock_token, self._repo_lock_token = remote_tokens
771
assert self._lock_token, 'Remote server did not return a token!'
772
# TODO: We really, really, really don't want to call _ensure_real
773
# here, but it's the easiest way to ensure coherency between the
774
# state of the RemoteBranch and RemoteRepository objects and the
775
# physical locks. If we don't materialise the real objects here,
776
# then getting everything in the right state later is complex, so
777
# for now we just do it the lazy way.
778
# -- Andrew Bennetts, 2007-02-22.
780
if self._real_branch is not None:
781
self._real_branch.repository.lock_write(
782
token=self._repo_lock_token)
784
self._real_branch.lock_write(token=self._lock_token)
786
self._real_branch.repository.unlock()
787
if token is not None:
788
self._leave_lock = True
790
# XXX: this case seems to be unreachable; token cannot be None.
791
self._leave_lock = False
792
self._lock_mode = 'w'
794
elif self._lock_mode == 'r':
795
raise errors.ReadOnlyTransaction
797
if token is not None:
798
# A token was given to lock_write, and we're relocking, so check
799
# that the given token actually matches the one we already have.
800
if token != self._lock_token:
801
raise errors.TokenMismatch(token, self._lock_token)
802
self._lock_count += 1
803
return self._lock_token
805
def _unlock(self, branch_token, repo_token):
806
path = self.bzrdir._path_for_remote_call(self._client)
807
response = self._client.call('Branch.unlock', path, branch_token,
809
if response == ('ok',):
811
elif response[0] == 'TokenMismatch':
812
raise errors.TokenMismatch(
813
str((branch_token, repo_token)), '(remote tokens)')
815
assert False, 'unexpected response code %s' % (response,)
818
self._lock_count -= 1
819
if not self._lock_count:
820
self._clear_cached_state()
821
mode = self._lock_mode
822
self._lock_mode = None
823
if self._real_branch is not None:
824
if not self._leave_lock:
825
# If this RemoteBranch will remove the physical lock for the
826
# repository, make sure the _real_branch doesn't do it
827
# first. (Because the _real_branch's repository is set to
828
# be the RemoteRepository.)
829
self._real_branch.repository.leave_lock_in_place()
830
self._real_branch.unlock()
833
assert self._lock_token, 'Locked, but no token!'
834
branch_token = self._lock_token
835
repo_token = self._repo_lock_token
836
self._lock_token = None
837
self._repo_lock_token = None
838
if not self._leave_lock:
839
self._unlock(branch_token, repo_token)
841
def break_lock(self):
843
return self._real_branch.break_lock()
845
def leave_lock_in_place(self):
846
self._leave_lock = True
848
def dont_leave_lock_in_place(self):
849
self._leave_lock = False
851
def last_revision_info(self):
852
"""See Branch.last_revision_info()."""
853
path = self.bzrdir._path_for_remote_call(self._client)
854
response = self._client.call('Branch.last_revision_info', path)
855
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
856
revno = int(response[1])
857
last_revision = response[2]
858
if last_revision == '':
859
last_revision = NULL_REVISION
860
return (revno, last_revision)
862
def _gen_revision_history(self):
863
"""See Branch._gen_revision_history()."""
864
path = self.bzrdir._path_for_remote_call(self._client)
865
response = self._client.call2('Branch.revision_history', path)
866
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
867
result = response[1].read_body_bytes().split('\x00')
873
def set_revision_history(self, rev_history):
874
# Send just the tip revision of the history; the server will generate
875
# the full history from that. If the revision doesn't exist in this
876
# branch, NoSuchRevision will be raised.
877
path = self.bzrdir._path_for_remote_call(self._client)
878
if rev_history == []:
881
rev_id = rev_history[-1]
882
response = self._client.call('Branch.set_last_revision',
883
path, self._lock_token, self._repo_lock_token, rev_id)
884
if response[0] == 'NoSuchRevision':
885
raise NoSuchRevision(self, rev_id)
887
assert response == ('ok',), (
888
'unexpected response code %r' % (response,))
889
self._cache_revision_history(rev_history)
891
def get_parent(self):
893
return self._real_branch.get_parent()
895
def set_parent(self, url):
897
return self._real_branch.set_parent(url)
899
def get_config(self):
900
return RemoteBranchConfig(self)
902
def sprout(self, to_bzrdir, revision_id=None):
903
# Like Branch.sprout, except that it sprouts a branch in the default
904
# format, because RemoteBranches can't be created at arbitrary URLs.
905
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
906
# to_bzrdir.create_branch...
908
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
909
self._real_branch.copy_content_into(result, revision_id=revision_id)
910
result.set_parent(self.bzrdir.root_transport.base)
914
def append_revision(self, *revision_ids):
916
return self._real_branch.append_revision(*revision_ids)
919
def pull(self, source, overwrite=False, stop_revision=None):
921
self._real_branch.pull(
922
source, overwrite=overwrite, stop_revision=stop_revision)
925
def push(self, target, overwrite=False, stop_revision=None):
927
return self._real_branch.push(
928
target, overwrite=overwrite, stop_revision=stop_revision)
931
return self._lock_count >= 1
933
def set_last_revision_info(self, revno, revision_id):
935
self._clear_cached_state()
936
return self._real_branch.set_last_revision_info(revno, revision_id)
938
def generate_revision_history(self, revision_id, last_rev=None,
941
return self._real_branch.generate_revision_history(
942
revision_id, last_rev=last_rev, other_branch=other_branch)
947
return self._real_branch.tags
949
def set_push_location(self, location):
951
return self._real_branch.set_push_location(location)
953
def update_revisions(self, other, stop_revision=None):
955
return self._real_branch.update_revisions(
956
other, stop_revision=stop_revision)
959
class RemoteBranchConfig(BranchConfig):
962
self.branch._ensure_real()
963
return self.branch._real_branch.get_config().username()
965
def _get_branch_data_config(self):
966
self.branch._ensure_real()
967
if self._branch_data_config is None:
968
self._branch_data_config = TreeConfig(self.branch._real_branch)
969
return self._branch_data_config