1
# Copyright (C) 2006 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 urlparse import urlparse
22
from bzrlib import branch, errors, repository
23
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
24
from bzrlib.branch import BranchReferenceFormat
25
from bzrlib.smart import client, vfs
26
from bzrlib.urlutils import unescape
28
# Note: RemoteBzrDirFormat is in bzrdir.py
30
class RemoteBzrDir(BzrDir):
31
"""Control directory on a remote server, accessed by HPSS."""
33
def __init__(self, transport):
34
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
35
self.client = transport.get_smart_client()
36
# this object holds a delegated bzrdir that uses file-level operations
37
# to talk to the other side
38
# XXX: We should go into find_format, but not allow it to find
39
# RemoteBzrDirFormat and make sure it finds the real underlying format.
41
# THIS IS A COMPLETE AND UTTER LIE.
42
# XXX: XXX: XXX: must be removed before merging to mainline
43
# SMART_SERVER_MERGE_BLOCKER
44
default_format = BzrDirFormat.get_default_format()
45
self._real_bzrdir = default_format.open(transport, _found=True)
46
smartclient = client.SmartClient(self.client)
47
path = self._path_for_remote_call(smartclient)
48
#self._real_bzrdir._format.probe_transport(transport)
49
response = smartclient.call('probe_dont_use', path)
50
if response == ('no',):
51
raise errors.NotBranchError(path=transport.base)
54
def create_repository(self, shared=False):
55
return RemoteRepository(
56
self, self._real_bzrdir.create_repository(shared=shared))
58
def create_branch(self):
59
real_branch = self._real_bzrdir.create_branch()
60
real_repository = real_branch.repository
61
remote_repository = RemoteRepository(self, real_repository)
62
return RemoteBranch(self, remote_repository, real_branch)
64
def create_workingtree(self, revision_id=None):
65
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
66
return RemoteWorkingTree(self, real_workingtree)
68
def open_branch(self, _unsupported=False):
69
assert _unsupported == False, 'unsupported flag support not implemented yet.'
70
smartclient = client.SmartClient(self.client)
71
path = self._path_for_remote_call(smartclient)
72
response = smartclient.call('BzrDir.open_branch', path)
73
assert response[0] == 'ok', 'unexpected response code %s' % response[0]
74
if response[0] != 'ok':
75
# this should probably be a regular translate no ?
76
raise errors.NotBranchError(path=self.root_transport.base)
78
# branch at this location.
80
# if the VFS is enabled, create a local object using the VFS.
81
real_branch = self._real_bzrdir.open_branch(unsupported=_unsupported)
82
# This branch accessed through the smart server, so wrap the
84
real_repository = real_branch.repository
85
remote_repository = RemoteRepository(self, real_repository)
86
return RemoteBranch(self, remote_repository, real_branch)
88
# otherwise just create a proxy for the branch.
89
return RemoteBranch(self, self.find_repository())
91
# a branch reference, use the existing BranchReference logic.
92
format = BranchReferenceFormat()
93
return format.open(self, _found=True, location=response[1])
95
def open_repository(self):
96
smartclient = client.SmartClient(self.client)
97
path = self._path_for_remote_call(smartclient)
98
response = smartclient.call('BzrDir.find_repository', path)
99
assert response[0] in ('ok', 'norepository'), \
100
'unexpected response code %s' % response[0]
101
if response[0] == 'norepository':
102
raise errors.NoRepositoryPresent(self)
103
if response[1] == '':
104
if vfs.vfs_enabled():
105
return RemoteRepository(self, self._real_bzrdir.open_repository())
107
return RemoteRepository(self)
109
raise errors.NoRepositoryPresent(self)
111
def open_workingtree(self):
112
return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
114
def _path_for_remote_call(self, client):
115
"""Return the path to be used for this bzrdir in a remote call."""
116
return client.remote_path_from_transport(self.root_transport)
118
def get_branch_transport(self, branch_format):
119
return self._real_bzrdir.get_branch_transport(branch_format)
121
def get_repository_transport(self, repository_format):
122
return self._real_bzrdir.get_repository_transport(repository_format)
124
def get_workingtree_transport(self, workingtree_format):
125
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
127
def can_convert_format(self):
128
"""Upgrading of remote bzrdirs is not supported yet."""
131
def needs_format_conversion(self, format=None):
132
"""Upgrading of remote bzrdirs is not supported yet."""
136
class RemoteRepositoryFormat(repository.RepositoryFormat):
137
"""Format for repositories accessed over rpc.
139
Instances of this repository are represented by RemoteRepository
143
_matchingbzrdir = RemoteBzrDirFormat
145
def initialize(self, a_bzrdir, shared=False):
146
assert isinstance(a_bzrdir, RemoteBzrDir)
147
return a_bzrdir.create_repository(shared=shared)
149
def open(self, a_bzrdir):
150
assert isinstance(a_bzrdir, RemoteBzrDir)
151
return a_bzrdir.open_repository()
153
def get_format_description(self):
154
return 'bzr remote repository'
156
def __eq__(self, other):
157
return self.__class__ == other.__class__
159
rich_root_data = False
162
class RemoteRepository(object):
163
"""Repository accessed over rpc.
165
For the moment everything is delegated to IO-like operations over
169
def __init__(self, remote_bzrdir, real_repository=None):
170
"""Create a RemoteRepository instance.
172
:param remote_bzrdir: The bzrdir hosting this repository.
173
:param real_repository: If not None, a local implementation of the
174
repository logic for the repository, usually accessing the data
178
self._real_repository = real_repository
179
self.bzrdir = remote_bzrdir
180
self._client = client.SmartClient(self.bzrdir.client)
181
self._format = RemoteRepositoryFormat()
183
def has_revision(self, revision_id):
184
"""See Repository.has_revision()."""
185
path = self.bzrdir._path_for_remote_call(self._client)
186
response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
187
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % response[0]
188
return response[0] == 'ok'
191
class RemoteBranchFormat(branch.BranchFormat):
193
def open(self, a_bzrdir):
194
assert isinstance(a_bzrdir, RemoteBzrDir)
195
return a_bzrdir.open_branch()
197
def initialize(self, a_bzrdir):
198
assert isinstance(a_bzrdir, RemoteBzrDir)
199
return a_bzrdir.create_branch()
202
class RemoteBranch(branch.Branch):
203
"""Branch stored on a server accessed by HPSS RPC.
205
At the moment most operations are mapped down to simple file operations.
208
def __init__(self, remote_bzrdir, remote_repository, real_branch=None):
209
"""Create a RemoteBranch instance.
211
:param real_branch: An optional local implementation of the branch
212
format, usually accessing the data via the VFS.
214
self.bzrdir = remote_bzrdir
215
self._client = client.SmartClient(self.bzrdir.client)
216
self.repository = remote_repository
217
if real_branch is not None:
218
self._real_branch = real_branch
219
self._format = RemoteBranchFormat()
222
return self._real_branch.lock_read()
224
def lock_write(self):
225
return self._real_branch.lock_write()
228
return self._real_branch.unlock()
230
def break_lock(self):
231
return self._real_branch.break_lock()
233
def revision_history(self):
234
"""See Branch.revision_history()."""
235
# XXX: TODO: this does not cache the revision history for the duration
236
# of a lock, which is a bug - see the code for regular branches
238
path = self.bzrdir._path_for_remote_call(self._client)
239
response = self._client.call2('Branch.revision_history', path)
240
assert response[0][0] == 'ok', 'unexpected response code %s' % response[0]
241
result = response[1].read_body_bytes().decode('utf8').split('\x00')
246
def set_revision_history(self, rev_history):
247
return self._real_branch.set_revision_history(rev_history)
249
def get_parent(self):
250
return self._real_branch.get_parent()
252
def set_parent(self, url):
253
return self._real_branch.set_parent(url)
256
class RemoteWorkingTree(object):
258
def __init__(self, remote_bzrdir, real_workingtree):
259
self.real_workingtree = real_workingtree
260
self.bzrdir = remote_bzrdir
262
def __getattr__(self, name):
263
# XXX: temporary way to lazily delegate everything to the real
265
return getattr(self.real_workingtree, name)