/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

Merge RemoteRepository.gather_stats.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
# TODO: At some point, handle upgrades by just passing the whole request
 
18
# across to run on the server.
 
19
 
 
20
from cStringIO import StringIO
 
21
from urlparse import urlparse
 
22
 
 
23
from bzrlib import branch, errors, repository
 
24
from bzrlib.branch import BranchReferenceFormat
 
25
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
 
26
from bzrlib.errors import NoSuchRevision
 
27
from bzrlib.revision import NULL_REVISION
 
28
from bzrlib.smart import client, vfs
 
29
from bzrlib.urlutils import unescape
 
30
 
 
31
# Note: RemoteBzrDirFormat is in bzrdir.py
 
32
 
 
33
class RemoteBzrDir(BzrDir):
 
34
    """Control directory on a remote server, accessed by HPSS."""
 
35
 
 
36
    def __init__(self, transport, _client=None):
 
37
        """Construct a RemoteBzrDir.
 
38
 
 
39
        :param _client: Private parameter for testing. Disables probing and the
 
40
            use of a real bzrdir.
 
41
        """
 
42
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
43
        if _client is not None:
 
44
            self.client = _client
 
45
            return
 
46
 
 
47
        self.client = transport.get_smart_client()
 
48
        # this object holds a delegated bzrdir that uses file-level operations
 
49
        # to talk to the other side
 
50
        # XXX: We should go into find_format, but not allow it to find
 
51
        # RemoteBzrDirFormat and make sure it finds the real underlying format.
 
52
        
 
53
        # THIS IS A COMPLETE AND UTTER LIE.
 
54
        # XXX: XXX: XXX: must be removed before merging to mainline
 
55
        # SMART_SERVER_MERGE_BLOCKER
 
56
        default_format = BzrDirFormat.get_default_format()
 
57
        self._real_bzrdir = default_format.open(transport, _found=True)
 
58
        smartclient = client.SmartClient(self.client)
 
59
        path = self._path_for_remote_call(smartclient)
 
60
        #self._real_bzrdir._format.probe_transport(transport)
 
61
        response = smartclient.call('probe_dont_use', path)
 
62
        if response == ('no',):
 
63
            raise errors.NotBranchError(path=transport.base)
 
64
 
 
65
    def create_repository(self, shared=False):
 
66
        return RemoteRepository(
 
67
            self, self._real_bzrdir.create_repository(shared=shared))
 
68
 
 
69
    def create_branch(self):
 
70
        real_branch = self._real_bzrdir.create_branch()
 
71
        real_repository = real_branch.repository
 
72
        remote_repository = RemoteRepository(self, real_repository)
 
73
        return RemoteBranch(self, remote_repository, real_branch)
 
74
 
 
75
    def create_workingtree(self, revision_id=None):
 
76
        real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
 
77
        return RemoteWorkingTree(self, real_workingtree)
 
78
 
 
79
    def open_branch(self, _unsupported=False):
 
80
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
81
        smartclient = client.SmartClient(self.client)
 
82
        path = self._path_for_remote_call(smartclient)
 
83
        response = smartclient.call('BzrDir.open_branch', path)
 
84
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
85
        if response[0] != 'ok':
 
86
            # this should probably be a regular translate no ?
 
87
            raise errors.NotBranchError(path=self.root_transport.base)
 
88
        if response[1] == '':
 
89
            # branch at this location.
 
90
            if vfs.vfs_enabled():
 
91
                # if the VFS is enabled, create a local object using the VFS.
 
92
                real_branch = self._real_bzrdir.open_branch(unsupported=_unsupported)
 
93
                # This branch accessed through the smart server, so wrap the
 
94
                # file-level objects.
 
95
                real_repository = real_branch.repository
 
96
                remote_repository = self.find_repository()
 
97
                remote_repository._real_repository = real_repository
 
98
                return RemoteBranch(self, remote_repository, real_branch)
 
99
            else:
 
100
                # otherwise just create a proxy for the branch.
 
101
                return RemoteBranch(self, self.find_repository())
 
102
        else:
 
103
            # a branch reference, use the existing BranchReference logic.
 
104
            format = BranchReferenceFormat()
 
105
            return format.open(self, _found=True, location=response[1])
 
106
 
 
107
    def open_repository(self):
 
108
        smartclient = client.SmartClient(self.client)
 
109
        path = self._path_for_remote_call(smartclient)
 
110
        response = smartclient.call('BzrDir.find_repository', path)
 
111
        assert response[0] in ('ok', 'norepository'), \
 
112
            'unexpected response code %s' % (response,)
 
113
        if response[0] == 'norepository':
 
114
            raise errors.NoRepositoryPresent(self)
 
115
        if response[1] == '':
 
116
            if vfs.vfs_enabled():
 
117
                return RemoteRepository(self, self._real_bzrdir.open_repository())
 
118
            else:
 
119
                return RemoteRepository(self)
 
120
        else:
 
121
            raise errors.NoRepositoryPresent(self)
 
122
 
 
123
    def open_workingtree(self):
 
124
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
125
 
 
126
    def _path_for_remote_call(self, client):
 
127
        """Return the path to be used for this bzrdir in a remote call."""
 
128
        return client.remote_path_from_transport(self.root_transport)
 
129
 
 
130
    def get_branch_transport(self, branch_format):
 
131
        return self._real_bzrdir.get_branch_transport(branch_format)
 
132
 
 
133
    def get_repository_transport(self, repository_format):
 
134
        return self._real_bzrdir.get_repository_transport(repository_format)
 
135
 
 
136
    def get_workingtree_transport(self, workingtree_format):
 
137
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
138
 
 
139
    def can_convert_format(self):
 
140
        """Upgrading of remote bzrdirs is not supported yet."""
 
141
        return False
 
142
 
 
143
    def needs_format_conversion(self, format=None):
 
144
        """Upgrading of remote bzrdirs is not supported yet."""
 
145
        return False
 
146
 
 
147
 
 
148
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
149
    """Format for repositories accessed over rpc.
 
150
 
 
151
    Instances of this repository are represented by RemoteRepository
 
152
    instances.
 
153
    """
 
154
 
 
155
    _matchingbzrdir = RemoteBzrDirFormat
 
156
 
 
157
    def initialize(self, a_bzrdir, shared=False):
 
158
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
159
        return a_bzrdir.create_repository(shared=shared)
 
160
    
 
161
    def open(self, a_bzrdir):
 
162
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
163
        return a_bzrdir.open_repository()
 
164
 
 
165
    def get_format_description(self):
 
166
        return 'bzr remote repository'
 
167
 
 
168
    def __eq__(self, other):
 
169
        return self.__class__ == other.__class__
 
170
 
 
171
    rich_root_data = False
 
172
 
 
173
 
 
174
class RemoteRepository(object):
 
175
    """Repository accessed over rpc.
 
176
 
 
177
    For the moment everything is delegated to IO-like operations over
 
178
    the transport.
 
179
    """
 
180
 
 
181
    def __init__(self, remote_bzrdir, real_repository=None, _client=None):
 
182
        """Create a RemoteRepository instance.
 
183
        
 
184
        :param remote_bzrdir: The bzrdir hosting this repository.
 
185
        :param real_repository: If not None, a local implementation of the
 
186
            repository logic for the repository, usually accessing the data
 
187
            via the VFS.
 
188
        :param _client: Private testing parameter - override the smart client
 
189
            to be used by the repository.
 
190
        """
 
191
        if real_repository:
 
192
            self._real_repository = real_repository
 
193
        self.bzrdir = remote_bzrdir
 
194
        if _client is None:
 
195
            self._client = client.SmartClient(self.bzrdir.client)
 
196
        else:
 
197
            self._client = _client
 
198
        self._format = RemoteRepositoryFormat()
 
199
 
 
200
    def get_revision_graph(self, revision_id=None):
 
201
        """See Repository.get_revision_graph()."""
 
202
        if revision_id is None:
 
203
            revision_id = ''
 
204
        elif revision_id == NULL_REVISION:
 
205
            return {}
 
206
 
 
207
        path = self.bzrdir._path_for_remote_call(self._client)
 
208
        response = self._client.call2('Repository.get_revision_graph', path, revision_id.encode('utf8'))
 
209
        assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
 
210
        if response[0][0] == 'ok':
 
211
            coded = response[1].read_body_bytes()
 
212
            lines = coded.decode('utf8').split('\n')
 
213
            revision_graph = {}
 
214
            # FIXME
 
215
            for line in lines:
 
216
                d = list(line.split())
 
217
                revision_graph[d[0]] = d[1:]
 
218
                
 
219
            return revision_graph
 
220
        else:
 
221
            assert response[1] != ''
 
222
            raise NoSuchRevision(self, revision_id)
 
223
 
 
224
    def has_revision(self, revision_id):
 
225
        """See Repository.has_revision()."""
 
226
        path = self.bzrdir._path_for_remote_call(self._client)
 
227
        response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
 
228
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
229
        return response[0] == 'ok'
 
230
 
 
231
    def gather_stats(self, revid, committers=None):
 
232
        """See Repository.gather_stats()."""
 
233
        path = self.bzrdir._path_for_remote_call(self._client)
 
234
        if revid in (None, NULL_REVISION):
 
235
            fmt_revid = ''
 
236
        else:
 
237
            fmt_revid = revid.encode('utf8')
 
238
        if committers is None:
 
239
            fmt_committers = 'no'
 
240
        else:
 
241
            fmt_committers = 'yes'
 
242
        response = self._client.call2('Repository.gather_stats', path,
 
243
                                      fmt_revid, fmt_committers)
 
244
        assert response[0][0] == 'ok', \
 
245
            'unexpected response code %s' % (response[0],)
 
246
 
 
247
        body = response[1].read_body_bytes()
 
248
        result = {}
 
249
        for line in body.split('\n'):
 
250
            if not line:
 
251
                continue
 
252
            key, val_text = line.split(':')
 
253
            if key in ('revisions', 'size', 'committers'):
 
254
                result[key] = int(val_text)
 
255
            elif key in ('firstrev', 'latestrev'):
 
256
                values = val_text.split(' ')[1:]
 
257
                result[key] = (float(values[0]), long(values[1]))
 
258
 
 
259
        return result
 
260
 
 
261
    def get_physical_lock_status(self):
 
262
        """See Repository.get_physical_lock_status()."""
 
263
        return False
 
264
 
 
265
    def is_shared(self):
 
266
        """See Repository.is_shared()."""
 
267
        path = self.bzrdir._path_for_remote_call(self._client)
 
268
        response = self._client.call('Repository.is_shared', path)
 
269
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
270
        return response[0] == 'yes'
 
271
 
 
272
    def lock_read(self):
 
273
        # wrong eventually - want a local lock cache context
 
274
        return self._real_repository.lock_read()
 
275
 
 
276
    def lock_write(self):
 
277
        # definately wrong: want to check if there is a real repo
 
278
        # and not thunk through if not
 
279
        return self._real_repository.lock_write()
 
280
 
 
281
    def unlock(self):
 
282
        # should free cache context.
 
283
        return self._real_repository.unlock()
 
284
 
 
285
    def break_lock(self):
 
286
        # should hand off to the network - or better yet, we should not
 
287
        # allow stale network locks ?
 
288
        return self._real_repository.break_lock()
 
289
 
 
290
 
 
291
class RemoteBranchLockableFiles(object):
 
292
    """A 'LockableFiles' implementation that talks to a smart server.
 
293
    
 
294
    This is not a public interface class.
 
295
    """
 
296
 
 
297
    def __init__(self, bzrdir, _client):
 
298
        self.bzrdir = bzrdir
 
299
        self._client = _client
 
300
 
 
301
    def get(self, path):
 
302
        """'get' a remote path as per the LockableFiles interface.
 
303
 
 
304
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
305
             just retrieve a file, instead we ask the smart server to generate
 
306
             a configuration for us - which is retrieved as an INI file.
 
307
        """
 
308
        assert path == 'branch.conf'
 
309
        path = self.bzrdir._path_for_remote_call(self._client)
 
310
        response = self._client.call2('Branch.get_config_file', path)
 
311
        assert response[0][0] == 'ok', \
 
312
            'unexpected response code %s' % (response[0],)
 
313
        return StringIO(response[1].read_body_bytes())
 
314
 
 
315
 
 
316
class RemoteBranchFormat(branch.BranchFormat):
 
317
 
 
318
    def get_format_description(self):
 
319
        return 'Remote BZR Branch'
 
320
 
 
321
    def open(self, a_bzrdir):
 
322
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
323
        return a_bzrdir.open_branch()
 
324
 
 
325
    def initialize(self, a_bzrdir):
 
326
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
327
        return a_bzrdir.create_branch()
 
328
 
 
329
 
 
330
class RemoteBranch(branch.Branch):
 
331
    """Branch stored on a server accessed by HPSS RPC.
 
332
 
 
333
    At the moment most operations are mapped down to simple file operations.
 
334
    """
 
335
 
 
336
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
337
        _client=None):
 
338
        """Create a RemoteBranch instance.
 
339
 
 
340
        :param real_branch: An optional local implementation of the branch
 
341
            format, usually accessing the data via the VFS.
 
342
        :param _client: Private parameter for testing.
 
343
        """
 
344
        self.bzrdir = remote_bzrdir
 
345
        if _client is not None:
 
346
            self._client = _client
 
347
        else:
 
348
            self._client = client.SmartClient(self.bzrdir.client)
 
349
        self.repository = remote_repository
 
350
        if real_branch is not None:
 
351
            self._real_branch = real_branch
 
352
        # Fill out expected attributes of branch for bzrlib api users.
 
353
        self._format = RemoteBranchFormat()
 
354
        self.base = self.bzrdir.root_transport.base
 
355
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
356
 
 
357
    def get_physical_lock_status(self):
 
358
        """See Branch.get_physical_lock_status()."""
 
359
        # should be an API call to the server, as branches must be lockable.
 
360
        return self._real_branch.get_physical_lock_status()
 
361
 
 
362
    def lock_read(self):
 
363
        return self._real_branch.lock_read()
 
364
 
 
365
    def lock_write(self):
 
366
        return self._real_branch.lock_write()
 
367
 
 
368
    def unlock(self):
 
369
        return self._real_branch.unlock()
 
370
 
 
371
    def break_lock(self):
 
372
        return self._real_branch.break_lock()
 
373
 
 
374
    def last_revision_info(self):
 
375
        """See Branch.last_revision_info()."""
 
376
        path = self.bzrdir._path_for_remote_call(self._client)
 
377
        response = self._client.call('Branch.last_revision_info', path)
 
378
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
379
        revno = int(response[1])
 
380
        last_revision = response[2].decode('utf8')
 
381
        if last_revision == '':
 
382
            last_revision = NULL_REVISION
 
383
        return (revno, last_revision)
 
384
 
 
385
    def revision_history(self):
 
386
        """See Branch.revision_history()."""
 
387
        # XXX: TODO: this does not cache the revision history for the duration
 
388
        # of a lock, which is a bug - see the code for regular branches
 
389
        # for details.
 
390
        path = self.bzrdir._path_for_remote_call(self._client)
 
391
        response = self._client.call2('Branch.revision_history', path)
 
392
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
393
        result = response[1].read_body_bytes().decode('utf8').split('\x00')
 
394
        if result == ['']:
 
395
            return []
 
396
        return result
 
397
 
 
398
    def set_revision_history(self, rev_history):
 
399
        return self._real_branch.set_revision_history(rev_history)
 
400
 
 
401
    def get_parent(self):
 
402
        return self._real_branch.get_parent()
 
403
        
 
404
    def set_parent(self, url):
 
405
        return self._real_branch.set_parent(url)
 
406
        
 
407
 
 
408
class RemoteWorkingTree(object):
 
409
 
 
410
    def __init__(self, remote_bzrdir, real_workingtree):
 
411
        self.real_workingtree = real_workingtree
 
412
        self.bzrdir = remote_bzrdir
 
413
 
 
414
    def __getattr__(self, name):
 
415
        # XXX: temporary way to lazily delegate everything to the real
 
416
        # workingtree
 
417
        return getattr(self.real_workingtree, name)
 
418
 
 
419