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.revision import NULL_REVISION
27
from bzrlib.smart import client, vfs
28
from bzrlib.urlutils import unescape
30
# Note: RemoteBzrDirFormat is in bzrdir.py
32
class RemoteBzrDir(BzrDir):
33
"""Control directory on a remote server, accessed by HPSS."""
35
def __init__(self, transport, _client=None):
36
"""Construct a RemoteBzrDir.
38
:param _client: Private parameter for testing. Disables probing and the
41
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
42
if _client is not None:
46
self.client = transport.get_smart_client()
47
# this object holds a delegated bzrdir that uses file-level operations
48
# to talk to the other side
49
# XXX: We should go into find_format, but not allow it to find
50
# RemoteBzrDirFormat and make sure it finds the real underlying format.
52
# THIS IS A COMPLETE AND UTTER LIE.
53
# XXX: XXX: XXX: must be removed before merging to mainline
54
# SMART_SERVER_MERGE_BLOCKER
55
default_format = BzrDirFormat.get_default_format()
56
self._real_bzrdir = default_format.open(transport, _found=True)
57
smartclient = client.SmartClient(self.client)
58
path = self._path_for_remote_call(smartclient)
59
#self._real_bzrdir._format.probe_transport(transport)
60
response = smartclient.call('probe_dont_use', path)
61
if response == ('no',):
62
raise errors.NotBranchError(path=transport.base)
64
def create_repository(self, shared=False):
65
return RemoteRepository(
66
self, self._real_bzrdir.create_repository(shared=shared))
68
def create_branch(self):
69
real_branch = self._real_bzrdir.create_branch()
70
real_repository = real_branch.repository
71
remote_repository = RemoteRepository(self, real_repository)
72
return RemoteBranch(self, remote_repository, real_branch)
74
def create_workingtree(self, revision_id=None):
75
real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
76
return RemoteWorkingTree(self, real_workingtree)
78
def open_branch(self, _unsupported=False):
79
assert _unsupported == False, 'unsupported flag support not implemented yet.'
80
smartclient = client.SmartClient(self.client)
81
path = self._path_for_remote_call(smartclient)
82
response = smartclient.call('BzrDir.open_branch', path)
83
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
84
if response[0] != 'ok':
85
# this should probably be a regular translate no ?
86
raise errors.NotBranchError(path=self.root_transport.base)
88
# branch at this location.
90
# if the VFS is enabled, create a local object using the VFS.
91
real_branch = self._real_bzrdir.open_branch(unsupported=_unsupported)
92
# This branch accessed through the smart server, so wrap the
94
real_repository = real_branch.repository
95
assert isinstance(real_repository.bzrdir, RemoteBzrDir)
96
remote_repository = RemoteRepository(real_repository.bzrdir, real_repository)
97
return RemoteBranch(self, remote_repository, real_branch)
99
# otherwise just create a proxy for the branch.
100
return RemoteBranch(self, self.find_repository())
102
# a branch reference, use the existing BranchReference logic.
103
format = BranchReferenceFormat()
104
return format.open(self, _found=True, location=response[1])
106
def open_repository(self):
107
smartclient = client.SmartClient(self.client)
108
path = self._path_for_remote_call(smartclient)
109
response = smartclient.call('BzrDir.find_repository', path)
110
assert response[0] in ('ok', 'norepository'), \
111
'unexpected response code %s' % (response,)
112
if response[0] == 'norepository':
113
raise errors.NoRepositoryPresent(self)
114
if response[1] == '':
115
if vfs.vfs_enabled():
116
return RemoteRepository(self, self._real_bzrdir.open_repository())
118
return RemoteRepository(self)
120
raise errors.NoRepositoryPresent(self)
122
def open_workingtree(self):
123
return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
125
def _path_for_remote_call(self, client):
126
"""Return the path to be used for this bzrdir in a remote call."""
127
return client.remote_path_from_transport(self.root_transport)
129
def get_branch_transport(self, branch_format):
130
return self._real_bzrdir.get_branch_transport(branch_format)
132
def get_repository_transport(self, repository_format):
133
return self._real_bzrdir.get_repository_transport(repository_format)
135
def get_workingtree_transport(self, workingtree_format):
136
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
138
def can_convert_format(self):
139
"""Upgrading of remote bzrdirs is not supported yet."""
142
def needs_format_conversion(self, format=None):
143
"""Upgrading of remote bzrdirs is not supported yet."""
147
class RemoteRepositoryFormat(repository.RepositoryFormat):
148
"""Format for repositories accessed over rpc.
150
Instances of this repository are represented by RemoteRepository
154
_matchingbzrdir = RemoteBzrDirFormat
156
def initialize(self, a_bzrdir, shared=False):
157
assert isinstance(a_bzrdir, RemoteBzrDir)
158
return a_bzrdir.create_repository(shared=shared)
160
def open(self, a_bzrdir):
161
assert isinstance(a_bzrdir, RemoteBzrDir)
162
return a_bzrdir.open_repository()
164
def get_format_description(self):
165
return 'bzr remote repository'
167
def __eq__(self, other):
168
return self.__class__ == other.__class__
170
rich_root_data = False
173
class RemoteRepository(object):
174
"""Repository accessed over rpc.
176
For the moment everything is delegated to IO-like operations over
180
def __init__(self, remote_bzrdir, real_repository=None, _client=None):
181
"""Create a RemoteRepository instance.
183
:param remote_bzrdir: The bzrdir hosting this repository.
184
:param real_repository: If not None, a local implementation of the
185
repository logic for the repository, usually accessing the data
187
:param _client: Private testing parameter - override the smart client
188
to be used by the repository.
191
self._real_repository = real_repository
192
self.bzrdir = remote_bzrdir
194
self._client = client.SmartClient(self.bzrdir.client)
196
self._client = _client
197
self._format = RemoteRepositoryFormat()
199
def has_revision(self, revision_id):
200
"""See Repository.has_revision()."""
201
path = self.bzrdir._path_for_remote_call(self._client)
202
response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
203
assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
204
return response[0] == 'ok'
206
def gather_stats(self, revid, committers=None):
207
"""See Repository.gather_stats()."""
208
# SMART_SERVER_MERGE_BLOCKER
209
return self._real_repository.gather_stats(revid, committers)
211
def get_physical_lock_status(self):
212
"""See Repository.get_physical_lock_status()."""
216
"""See Repository.is_shared()."""
217
path = self.bzrdir._path_for_remote_call(self._client)
218
response = self._client.call('Repository.is_shared', path)
219
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
220
return response[0] == 'yes'
223
# wrong eventually - want a local lock cache context
224
return self._real_repository.lock_read()
226
def lock_write(self):
227
# definately wrong: want to check if there is a real repo
228
# and not thunk through if not
229
return self._real_repository.lock_write()
232
# should free cache context.
233
return self._real_repository.unlock()
235
def break_lock(self):
236
# should hand off to the network - or better yet, we should not
237
# allow stale network locks ?
238
return self._real_repository.break_lock()
241
class RemoteBranchLockableFiles(object):
242
"""A 'LockableFiles' implementation that talks to a smart server.
244
This is not a public interface class.
247
def __init__(self, bzrdir, _client):
249
self._client = _client
252
"""'get' a remote path as per the LockableFiles interface.
254
:param path: the file to 'get'. If this is 'branch.conf', we do not
255
just retrieve a file, instead we ask the smart server to generate
256
a configuration for us - which is retrieved as an INI file.
258
assert path == 'branch.conf'
259
path = self.bzrdir._path_for_remote_call(self._client)
260
response = self._client.call2('Branch.get_config_file', path)
261
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
262
return StringIO(response[1].read_body_bytes())
265
class RemoteBranchFormat(branch.BranchFormat):
267
def get_format_description(self):
268
return 'Remote BZR Branch'
270
def open(self, a_bzrdir):
271
assert isinstance(a_bzrdir, RemoteBzrDir)
272
return a_bzrdir.open_branch()
274
def initialize(self, a_bzrdir):
275
assert isinstance(a_bzrdir, RemoteBzrDir)
276
return a_bzrdir.create_branch()
279
class RemoteBranch(branch.Branch):
280
"""Branch stored on a server accessed by HPSS RPC.
282
At the moment most operations are mapped down to simple file operations.
285
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
287
"""Create a RemoteBranch instance.
289
:param real_branch: An optional local implementation of the branch
290
format, usually accessing the data via the VFS.
291
:param _client: Private parameter for testing.
293
self.bzrdir = remote_bzrdir
294
if _client is not None:
295
self._client = _client
297
self._client = client.SmartClient(self.bzrdir.client)
298
self.repository = remote_repository
299
if real_branch is not None:
300
self._real_branch = real_branch
301
# Fill out expected attributes of branch for bzrlib api users.
302
self._format = RemoteBranchFormat()
303
self.base = self.bzrdir.root_transport.base
304
self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
306
def get_physical_lock_status(self):
307
"""See Branch.get_physical_lock_status()."""
308
# should be an API call to the server, as branches must be lockable.
309
return self._real_branch.get_physical_lock_status()
312
return self._real_branch.lock_read()
314
def lock_write(self):
315
return self._real_branch.lock_write()
318
return self._real_branch.unlock()
320
def break_lock(self):
321
return self._real_branch.break_lock()
323
def last_revision_info(self):
324
"""See Branch.last_revision_info()."""
325
path = self.bzrdir._path_for_remote_call(self._client)
326
response = self._client.call('Branch.last_revision_info', path)
327
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
328
revno = int(response[1])
329
last_revision = response[2].decode('utf8')
330
if last_revision == '':
331
last_revision = NULL_REVISION
332
return (revno, last_revision)
334
def revision_history(self):
335
"""See Branch.revision_history()."""
336
# XXX: TODO: this does not cache the revision history for the duration
337
# of a lock, which is a bug - see the code for regular branches
339
path = self.bzrdir._path_for_remote_call(self._client)
340
response = self._client.call2('Branch.revision_history', path)
341
assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
342
result = response[1].read_body_bytes().decode('utf8').split('\x00')
347
def set_revision_history(self, rev_history):
348
return self._real_branch.set_revision_history(rev_history)
350
def get_parent(self):
351
return self._real_branch.get_parent()
353
def set_parent(self, url):
354
return self._real_branch.set_parent(url)
357
class RemoteWorkingTree(object):
359
def __init__(self, remote_bzrdir, real_workingtree):
360
self.real_workingtree = real_workingtree
361
self.bzrdir = remote_bzrdir
363
def __getattr__(self, name):
364
# XXX: temporary way to lazily delegate everything to the real
366
return getattr(self.real_workingtree, name)