/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

Add Repository.{dont_,}leave_lock_in_place.

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
        self._real_bzrdir = None
 
53
        
 
54
        self._ensure_real()
 
55
        smartclient = client.SmartClient(self.client)
 
56
        path = self._path_for_remote_call(smartclient)
 
57
        #self._real_bzrdir._format.probe_transport(transport)
 
58
        response = smartclient.call('probe_dont_use', path)
 
59
        if response == ('no',):
 
60
            raise errors.NotBranchError(path=transport.base)
 
61
 
 
62
    def _ensure_real(self):
 
63
        """Ensure that there is a _real_bzrdir set.
 
64
 
 
65
        used before calls to self._real_bzrdir.
 
66
        """
 
67
        if not self._real_bzrdir:
 
68
            default_format = BzrDirFormat.get_default_format()
 
69
            self._real_bzrdir = default_format.open(self.root_transport,
 
70
                _found=True)
 
71
 
 
72
    def create_repository(self, shared=False):
 
73
        return RemoteRepository(
 
74
            self, self._real_bzrdir.create_repository(shared=shared))
 
75
 
 
76
    def create_branch(self):
 
77
        real_branch = self._real_bzrdir.create_branch()
 
78
        return RemoteBranch(self, self.find_repository(), real_branch)
 
79
 
 
80
    def create_workingtree(self, revision_id=None):
 
81
        real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
 
82
        return RemoteWorkingTree(self, real_workingtree)
 
83
 
 
84
    def open_branch(self, _unsupported=False):
 
85
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
86
        smartclient = client.SmartClient(self.client)
 
87
        path = self._path_for_remote_call(smartclient)
 
88
        response = smartclient.call('BzrDir.open_branch', path)
 
89
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
90
        if response[0] != 'ok':
 
91
            # this should probably be a regular translate no ?
 
92
            raise errors.NotBranchError(path=self.root_transport.base)
 
93
        if response[1] == '':
 
94
            # branch at this location.
 
95
            return RemoteBranch(self, self.find_repository())
 
96
        else:
 
97
            # a branch reference, use the existing BranchReference logic.
 
98
            format = BranchReferenceFormat()
 
99
            return format.open(self, _found=True, location=response[1])
 
100
 
 
101
    def open_repository(self):
 
102
        smartclient = client.SmartClient(self.client)
 
103
        path = self._path_for_remote_call(smartclient)
 
104
        response = smartclient.call('BzrDir.find_repository', path)
 
105
        assert response[0] in ('ok', 'norepository'), \
 
106
            'unexpected response code %s' % (response,)
 
107
        if response[0] == 'norepository':
 
108
            raise errors.NoRepositoryPresent(self)
 
109
        if response[1] == '':
 
110
            return RemoteRepository(self)
 
111
        else:
 
112
            raise errors.NoRepositoryPresent(self)
 
113
 
 
114
    def open_workingtree(self):
 
115
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
116
 
 
117
    def _path_for_remote_call(self, client):
 
118
        """Return the path to be used for this bzrdir in a remote call."""
 
119
        return client.remote_path_from_transport(self.root_transport)
 
120
 
 
121
    def get_branch_transport(self, branch_format):
 
122
        return self._real_bzrdir.get_branch_transport(branch_format)
 
123
 
 
124
    def get_repository_transport(self, repository_format):
 
125
        return self._real_bzrdir.get_repository_transport(repository_format)
 
126
 
 
127
    def get_workingtree_transport(self, workingtree_format):
 
128
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
129
 
 
130
    def can_convert_format(self):
 
131
        """Upgrading of remote bzrdirs is not supported yet."""
 
132
        return False
 
133
 
 
134
    def needs_format_conversion(self, format=None):
 
135
        """Upgrading of remote bzrdirs is not supported yet."""
 
136
        return False
 
137
 
 
138
 
 
139
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
140
    """Format for repositories accessed over rpc.
 
141
 
 
142
    Instances of this repository are represented by RemoteRepository
 
143
    instances.
 
144
    """
 
145
 
 
146
    _matchingbzrdir = RemoteBzrDirFormat
 
147
 
 
148
    def initialize(self, a_bzrdir, shared=False):
 
149
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
150
        return a_bzrdir.create_repository(shared=shared)
 
151
    
 
152
    def open(self, a_bzrdir):
 
153
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
154
        return a_bzrdir.open_repository()
 
155
 
 
156
    def get_format_description(self):
 
157
        return 'bzr remote repository'
 
158
 
 
159
    def __eq__(self, other):
 
160
        return self.__class__ == other.__class__
 
161
 
 
162
    rich_root_data = False
 
163
 
 
164
 
 
165
class RemoteRepository(object):
 
166
    """Repository accessed over rpc.
 
167
 
 
168
    For the moment everything is delegated to IO-like operations over
 
169
    the transport.
 
170
    """
 
171
 
 
172
    def __init__(self, remote_bzrdir, real_repository=None, _client=None):
 
173
        """Create a RemoteRepository instance.
 
174
        
 
175
        :param remote_bzrdir: The bzrdir hosting this repository.
 
176
        :param real_repository: If not None, a local implementation of the
 
177
            repository logic for the repository, usually accessing the data
 
178
            via the VFS.
 
179
        :param _client: Private testing parameter - override the smart client
 
180
            to be used by the repository.
 
181
        """
 
182
        if real_repository:
 
183
            self._real_repository = real_repository
 
184
        else:
 
185
            self._real_repository = None
 
186
        self.bzrdir = remote_bzrdir
 
187
        if _client is None:
 
188
            self._client = client.SmartClient(self.bzrdir.client)
 
189
        else:
 
190
            self._client = _client
 
191
        self._format = RemoteRepositoryFormat()
 
192
        self._lock_mode = None
 
193
        self._lock_token = None
 
194
        self._lock_count = 0
 
195
 
 
196
    def _ensure_real(self):
 
197
        """Ensure that there is a _real_repository set.
 
198
 
 
199
        used before calls to self._real_repository.
 
200
        """
 
201
        if not self._real_repository:
 
202
            self.bzrdir._ensure_real()
 
203
            self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
204
 
 
205
    def get_revision_graph(self, revision_id=None):
 
206
        """See Repository.get_revision_graph()."""
 
207
        if revision_id is None:
 
208
            revision_id = ''
 
209
        elif revision_id == NULL_REVISION:
 
210
            return {}
 
211
 
 
212
        path = self.bzrdir._path_for_remote_call(self._client)
 
213
        response = self._client.call2('Repository.get_revision_graph', path, revision_id.encode('utf8'))
 
214
        assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
 
215
        if response[0][0] == 'ok':
 
216
            coded = response[1].read_body_bytes()
 
217
            lines = coded.decode('utf8').split('\n')
 
218
            revision_graph = {}
 
219
            # FIXME
 
220
            for line in lines:
 
221
                d = list(line.split())
 
222
                revision_graph[d[0]] = d[1:]
 
223
                
 
224
            return revision_graph
 
225
        else:
 
226
            assert response[1] != ''
 
227
            raise NoSuchRevision(self, revision_id)
 
228
 
 
229
    def has_revision(self, revision_id):
 
230
        """See Repository.has_revision()."""
 
231
        path = self.bzrdir._path_for_remote_call(self._client)
 
232
        response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
 
233
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
234
        return response[0] == 'ok'
 
235
 
 
236
    def gather_stats(self, revid, committers=None):
 
237
        """See Repository.gather_stats()."""
 
238
        path = self.bzrdir._path_for_remote_call(self._client)
 
239
        if revid in (None, NULL_REVISION):
 
240
            fmt_revid = ''
 
241
        else:
 
242
            fmt_revid = revid.encode('utf8')
 
243
        if committers is None:
 
244
            fmt_committers = 'no'
 
245
        else:
 
246
            fmt_committers = 'yes'
 
247
        response = self._client.call2('Repository.gather_stats', path,
 
248
                                      fmt_revid, fmt_committers)
 
249
        assert response[0][0] == 'ok', \
 
250
            'unexpected response code %s' % (response[0],)
 
251
 
 
252
        body = response[1].read_body_bytes()
 
253
        result = {}
 
254
        for line in body.split('\n'):
 
255
            if not line:
 
256
                continue
 
257
            key, val_text = line.split(':')
 
258
            if key in ('revisions', 'size', 'committers'):
 
259
                result[key] = int(val_text)
 
260
            elif key in ('firstrev', 'latestrev'):
 
261
                values = val_text.split(' ')[1:]
 
262
                result[key] = (float(values[0]), long(values[1]))
 
263
 
 
264
        return result
 
265
 
 
266
    def get_physical_lock_status(self):
 
267
        """See Repository.get_physical_lock_status()."""
 
268
        return False
 
269
 
 
270
    def is_shared(self):
 
271
        """See Repository.is_shared()."""
 
272
        path = self.bzrdir._path_for_remote_call(self._client)
 
273
        response = self._client.call('Repository.is_shared', path)
 
274
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
275
        return response[0] == 'yes'
 
276
 
 
277
    def lock_read(self):
 
278
        # wrong eventually - want a local lock cache context
 
279
        if not self._lock_mode:
 
280
            self._lock_mode = 'r'
 
281
            self._lock_count = 1
 
282
        else:
 
283
            self._lock_count += 1
 
284
        self._ensure_real()
 
285
        return self._real_repository.lock_read()
 
286
 
 
287
    def lock_write(self, token=None):
 
288
        # definately wrong: want to check if there is a real repo
 
289
        # and not thunk through if not
 
290
        if not self._lock_mode:
 
291
            self._ensure_real()
 
292
            self._lock_token = self._real_repository.lock_write(token=token)
 
293
            self._lock_mode = 'w'
 
294
            self._lock_count = 1
 
295
        elif self._lock_mode == 'r':
 
296
            raise errors.ReadOnlyTransaction
 
297
        else:
 
298
            self._lock_count += 1
 
299
        return self._lock_token
 
300
 
 
301
    def leave_lock_in_place(self):
 
302
        self._real_repository.leave_lock_in_place()
 
303
 
 
304
    def dont_leave_lock_in_place(self):
 
305
        self._real_repository.dont_leave_lock_in_place()
 
306
 
 
307
    def _set_real_repository(self, repository):
 
308
        """Set the _real_repository for this repository.
 
309
 
 
310
        :param repository: The repository to fallback to for non-hpss
 
311
            implemented operations.
 
312
        """
 
313
        self._real_repository = repository
 
314
        if self._lock_mode == 'w':
 
315
            # if we are already locked, the real repository must be able to
 
316
            # acquire the lock with our token.
 
317
            self._real_repository.lock_write(self._lock_token)
 
318
 
 
319
    def unlock(self):
 
320
        # should free cache context.
 
321
        self._lock_count -= 1
 
322
        if not self._lock_count:
 
323
            self._lock_mode = None
 
324
            self._lock_token = None
 
325
            return self._real_repository.unlock()
 
326
 
 
327
    def break_lock(self):
 
328
        # should hand off to the network - or better yet, we should not
 
329
        # allow stale network locks ?
 
330
        self._ensure_real()
 
331
        return self._real_repository.break_lock()
 
332
 
 
333
 
 
334
class RemoteBranchLockableFiles(object):
 
335
    """A 'LockableFiles' implementation that talks to a smart server.
 
336
    
 
337
    This is not a public interface class.
 
338
    """
 
339
 
 
340
    def __init__(self, bzrdir, _client):
 
341
        self.bzrdir = bzrdir
 
342
        self._client = _client
 
343
 
 
344
    def get(self, path):
 
345
        """'get' a remote path as per the LockableFiles interface.
 
346
 
 
347
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
348
             just retrieve a file, instead we ask the smart server to generate
 
349
             a configuration for us - which is retrieved as an INI file.
 
350
        """
 
351
        assert path == 'branch.conf'
 
352
        path = self.bzrdir._path_for_remote_call(self._client)
 
353
        response = self._client.call2('Branch.get_config_file', path)
 
354
        assert response[0][0] == 'ok', \
 
355
            'unexpected response code %s' % (response[0],)
 
356
        return StringIO(response[1].read_body_bytes())
 
357
 
 
358
 
 
359
class RemoteBranchFormat(branch.BranchFormat):
 
360
 
 
361
    def get_format_description(self):
 
362
        return 'Remote BZR Branch'
 
363
 
 
364
    def open(self, a_bzrdir):
 
365
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
366
        return a_bzrdir.open_branch()
 
367
 
 
368
    def initialize(self, a_bzrdir):
 
369
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
370
        return a_bzrdir.create_branch()
 
371
 
 
372
 
 
373
class RemoteBranch(branch.Branch):
 
374
    """Branch stored on a server accessed by HPSS RPC.
 
375
 
 
376
    At the moment most operations are mapped down to simple file operations.
 
377
    """
 
378
 
 
379
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
380
        _client=None):
 
381
        """Create a RemoteBranch instance.
 
382
 
 
383
        :param real_branch: An optional local implementation of the branch
 
384
            format, usually accessing the data via the VFS.
 
385
        :param _client: Private parameter for testing.
 
386
        """
 
387
        self.bzrdir = remote_bzrdir
 
388
        if _client is not None:
 
389
            self._client = _client
 
390
        else:
 
391
            self._client = client.SmartClient(self.bzrdir.client)
 
392
        self.repository = remote_repository
 
393
        if real_branch is not None:
 
394
            self._real_branch = real_branch
 
395
        else:
 
396
            self._real_branch = None
 
397
        # Fill out expected attributes of branch for bzrlib api users.
 
398
        self._format = RemoteBranchFormat()
 
399
        self.base = self.bzrdir.root_transport.base
 
400
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
401
 
 
402
    def _ensure_real(self):
 
403
        """Ensure that there is a _real_branch set.
 
404
 
 
405
        used before calls to self._real_branch.
 
406
        """
 
407
        if not self._real_branch:
 
408
            assert vfs.vfs_enabled()
 
409
            self.bzrdir._ensure_real()
 
410
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
411
            # Give the remote repository the matching real repo.
 
412
            self.repository._set_real_repository(self._real_branch.repository)
 
413
            # Give the branch the remote repository to let fast-pathing happen.
 
414
            self._real_branch.repository = self.repository
 
415
 
 
416
    def get_physical_lock_status(self):
 
417
        """See Branch.get_physical_lock_status()."""
 
418
        # should be an API call to the server, as branches must be lockable.
 
419
        self._ensure_real()
 
420
        return self._real_branch.get_physical_lock_status()
 
421
 
 
422
    def lock_read(self):
 
423
        self._ensure_real()
 
424
        return self._real_branch.lock_read()
 
425
 
 
426
    def lock_write(self, token=None):
 
427
        self._ensure_real()
 
428
        return self._real_branch.lock_write(token=token)
 
429
 
 
430
    def unlock(self):
 
431
        self._ensure_real()
 
432
        return self._real_branch.unlock()
 
433
 
 
434
    def break_lock(self):
 
435
        self._ensure_real()
 
436
        return self._real_branch.break_lock()
 
437
 
 
438
    def last_revision_info(self):
 
439
        """See Branch.last_revision_info()."""
 
440
        path = self.bzrdir._path_for_remote_call(self._client)
 
441
        response = self._client.call('Branch.last_revision_info', path)
 
442
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
443
        revno = int(response[1])
 
444
        last_revision = response[2].decode('utf8')
 
445
        if last_revision == '':
 
446
            last_revision = NULL_REVISION
 
447
        return (revno, last_revision)
 
448
 
 
449
    def revision_history(self):
 
450
        """See Branch.revision_history()."""
 
451
        # XXX: TODO: this does not cache the revision history for the duration
 
452
        # of a lock, which is a bug - see the code for regular branches
 
453
        # for details.
 
454
        path = self.bzrdir._path_for_remote_call(self._client)
 
455
        response = self._client.call2('Branch.revision_history', path)
 
456
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
457
        result = response[1].read_body_bytes().decode('utf8').split('\x00')
 
458
        if result == ['']:
 
459
            return []
 
460
        return result
 
461
 
 
462
    def set_revision_history(self, rev_history):
 
463
        # Send just the tip revision of the history; the server will generate
 
464
        # the full history from that.  If the revision doesn't exist in this
 
465
        # branch, NoSuchRevision will be raised.
 
466
        path = self.bzrdir._path_for_remote_call(self._client)
 
467
        if rev_history == []:
 
468
            rev_id = ''
 
469
        else:
 
470
            rev_id = rev_history[-1]
 
471
        response = self._client.call('Branch.set_last_revision', path, rev_id)
 
472
        if response[0] == 'NoSuchRevision':
 
473
            raise NoSuchRevision(self, rev_id)
 
474
        else:
 
475
            assert response == ('ok',), (
 
476
                'unexpected response code %r' % (response,))
 
477
 
 
478
    def get_parent(self):
 
479
        self._ensure_real()
 
480
        return self._real_branch.get_parent()
 
481
        
 
482
    def set_parent(self, url):
 
483
        self._ensure_real()
 
484
        return self._real_branch.set_parent(url)
 
485
        
 
486
 
 
487
class RemoteWorkingTree(object):
 
488
 
 
489
    def __init__(self, remote_bzrdir, real_workingtree):
 
490
        self.real_workingtree = real_workingtree
 
491
        self.bzrdir = remote_bzrdir
 
492
 
 
493
    def __getattr__(self, name):
 
494
        # XXX: temporary way to lazily delegate everything to the real
 
495
        # workingtree
 
496
        return getattr(self.real_workingtree, name)
 
497
 
 
498