/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

  • Committer: Robert Collins
  • Date: 2007-03-29 07:24:14 UTC
  • mto: (2018.18.6 hpss-faster-copy)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: robertc@robertcollins.net-20070329072414-7dh0io231c2tormb
Branch implementations test test_set_get_parent should not depend on local urls : the test can be valid for any url namespace. Change it to be valid in that manner. (Robert Collins, Andrew Bennetts)

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, 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
 
33
 
 
34
# Note: RemoteBzrDirFormat is in bzrdir.py
 
35
 
 
36
class RemoteBzrDir(BzrDir):
 
37
    """Control directory on a remote server, accessed by HPSS."""
 
38
 
 
39
    def __init__(self, transport, _client=None):
 
40
        """Construct a RemoteBzrDir.
 
41
 
 
42
        :param _client: Private parameter for testing. Disables probing and the
 
43
            use of a real bzrdir.
 
44
        """
 
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
 
51
 
 
52
        if _client is None:
 
53
            self._medium = transport.get_smart_client()
 
54
            self._client = client.SmartClient(self._medium)
 
55
        else:
 
56
            self._client = _client
 
57
            self._medium = None
 
58
            return
 
59
 
 
60
        self._ensure_real()
 
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)
 
66
 
 
67
    def _ensure_real(self):
 
68
        """Ensure that there is a _real_bzrdir set.
 
69
 
 
70
        used before calls to self._real_bzrdir.
 
71
        """
 
72
        if not self._real_bzrdir:
 
73
            default_format = BzrDirFormat.get_default_format()
 
74
            self._real_bzrdir = default_format.open(self.root_transport,
 
75
                _found=True)
 
76
 
 
77
    def create_repository(self, shared=False):
 
78
        self._real_bzrdir.create_repository(shared=shared)
 
79
        return self.open_repository()
 
80
 
 
81
    def create_branch(self):
 
82
        real_branch = self._real_bzrdir.create_branch()
 
83
        return RemoteBranch(self, self.find_repository(), real_branch)
 
84
 
 
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)
 
88
 
 
89
    def find_branch_format(self):
 
90
        """Find the branch 'format' for this bzrdir.
 
91
 
 
92
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
93
        """
 
94
        b = self.open_branch()
 
95
        return b._format
 
96
 
 
97
    def open_branch(self, _unsupported=False):
 
98
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
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.
 
104
                return RemoteBranch(self, self.find_repository())
 
105
            else:
 
106
                # a branch reference, use the existing BranchReference logic.
 
107
                format = BranchReferenceFormat()
 
108
                return format.open(self, _found=True, location=response[1])
 
109
        elif response == ('nobranch',):
 
110
            raise errors.NotBranchError(path=self.root_transport.base)
 
111
        else:
 
112
            assert False, 'unexpected response code %r' % (response,)
 
113
                
 
114
    def open_repository(self):
 
115
        path = self._path_for_remote_call(self._client)
 
116
        response = self._client.call('BzrDir.find_repository', path)
 
117
        assert response[0] in ('ok', 'norepository'), \
 
118
            'unexpected response code %s' % (response,)
 
119
        if response[0] == 'norepository':
 
120
            raise errors.NoRepositoryPresent(self)
 
121
        assert len(response) == 4, 'incorrect response length %s' % (response,)
 
122
        if response[1] == '':
 
123
            format = RemoteRepositoryFormat()
 
124
            format.rich_root_data = response[2] == 'True'
 
125
            format.support_tree_reference = response[3] == 'True'
 
126
            return RemoteRepository(self, format)
 
127
        else:
 
128
            raise errors.NoRepositoryPresent(self)
 
129
 
 
130
    def open_workingtree(self):
 
131
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
132
 
 
133
    def _path_for_remote_call(self, client):
 
134
        """Return the path to be used for this bzrdir in a remote call."""
 
135
        return client.remote_path_from_transport(self.root_transport)
 
136
 
 
137
    def get_branch_transport(self, branch_format):
 
138
        return self._real_bzrdir.get_branch_transport(branch_format)
 
139
 
 
140
    def get_repository_transport(self, repository_format):
 
141
        return self._real_bzrdir.get_repository_transport(repository_format)
 
142
 
 
143
    def get_workingtree_transport(self, workingtree_format):
 
144
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
145
 
 
146
    def can_convert_format(self):
 
147
        """Upgrading of remote bzrdirs is not supported yet."""
 
148
        return False
 
149
 
 
150
    def needs_format_conversion(self, format=None):
 
151
        """Upgrading of remote bzrdirs is not supported yet."""
 
152
        return False
 
153
 
 
154
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
155
        self._ensure_real()
 
156
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
157
            basis=basis, force_new_repo=force_new_repo)
 
158
 
 
159
    #def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
160
    #    self._ensure_real()
 
161
    #    return self._real_bzrdir.sprout(url, revision_id=revision_id,
 
162
    #        basis=basis, force_new_repo=force_new_repo)
 
163
 
 
164
 
 
165
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
166
    """Format for repositories accessed over a SmartClient.
 
167
 
 
168
    Instances of this repository are represented by RemoteRepository
 
169
    instances.
 
170
 
 
171
    The RemoteRepositoryFormat is parameterised during construction
 
172
    to reflect the capabilities of the real, remote format. Specifically
 
173
    the attributes rich_root_data and support_tree_reference are set
 
174
    on a per instance basis, and are not set (and should not be) at
 
175
    the class level.
 
176
    """
 
177
 
 
178
    _matchingbzrdir = RemoteBzrDirFormat
 
179
 
 
180
    def initialize(self, a_bzrdir, shared=False):
 
181
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
182
        return a_bzrdir.create_repository(shared=shared)
 
183
    
 
184
    def open(self, a_bzrdir):
 
185
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
186
        return a_bzrdir.open_repository()
 
187
 
 
188
    def get_format_description(self):
 
189
        return 'bzr remote repository'
 
190
 
 
191
    def __eq__(self, other):
 
192
        return self.__class__ == other.__class__
 
193
 
 
194
    def check_conversion_target(self, target_format):
 
195
        if self.rich_root_data and not target_format.rich_root_data:
 
196
            raise errors.BadConversionTarget(
 
197
                'Does not support rich root data.', target_format)
 
198
        if (self.support_tree_reference and
 
199
            not getattr(target_format, 'support_tree_reference', False)):
 
200
            raise errors.BadConversionTarget(
 
201
                'Does not support nested trees', target_format)
 
202
 
 
203
 
 
204
class RemoteRepository(object):
 
205
    """Repository accessed over rpc.
 
206
 
 
207
    For the moment everything is delegated to IO-like operations over
 
208
    the transport.
 
209
    """
 
210
 
 
211
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
 
212
        """Create a RemoteRepository instance.
 
213
        
 
214
        :param remote_bzrdir: The bzrdir hosting this repository.
 
215
        :param format: The RemoteFormat object to use.
 
216
        :param real_repository: If not None, a local implementation of the
 
217
            repository logic for the repository, usually accessing the data
 
218
            via the VFS.
 
219
        :param _client: Private testing parameter - override the smart client
 
220
            to be used by the repository.
 
221
        """
 
222
        if real_repository:
 
223
            self._real_repository = real_repository
 
224
        else:
 
225
            self._real_repository = None
 
226
        self.bzrdir = remote_bzrdir
 
227
        if _client is None:
 
228
            self._client = client.SmartClient(self.bzrdir._medium)
 
229
        else:
 
230
            self._client = _client
 
231
        self._format = format
 
232
        self._lock_mode = None
 
233
        self._lock_token = None
 
234
        self._lock_count = 0
 
235
        self._leave_lock = False
 
236
 
 
237
    def _ensure_real(self):
 
238
        """Ensure that there is a _real_repository set.
 
239
 
 
240
        used before calls to self._real_repository.
 
241
        """
 
242
        if not self._real_repository:
 
243
            self.bzrdir._ensure_real()
 
244
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
245
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
246
 
 
247
    def get_revision_graph(self, revision_id=None):
 
248
        """See Repository.get_revision_graph()."""
 
249
        if revision_id is None:
 
250
            revision_id = ''
 
251
        elif revision_id == NULL_REVISION:
 
252
            return {}
 
253
 
 
254
        path = self.bzrdir._path_for_remote_call(self._client)
 
255
        assert type(revision_id) is str
 
256
        response = self._client.call2(
 
257
            'Repository.get_revision_graph', path, revision_id)
 
258
        assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
 
259
        if response[0][0] == 'ok':
 
260
            coded = response[1].read_body_bytes()
 
261
            if coded == '':
 
262
                # no revisions in this repository!
 
263
                return {}
 
264
            lines = coded.split('\n')
 
265
            revision_graph = {}
 
266
            # FIXME
 
267
            for line in lines:
 
268
                d = list(line.split())
 
269
                revision_graph[d[0]] = d[1:]
 
270
                
 
271
            return revision_graph
 
272
        else:
 
273
            response_body = response[1].read_body_bytes()
 
274
            assert response_body == ''
 
275
            raise NoSuchRevision(self, revision_id)
 
276
 
 
277
    def has_revision(self, revision_id):
 
278
        """See Repository.has_revision()."""
 
279
        if revision_id is None:
 
280
            # The null revision is always present.
 
281
            return True
 
282
        path = self.bzrdir._path_for_remote_call(self._client)
 
283
        response = self._client.call('Repository.has_revision', path, revision_id)
 
284
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
285
        return response[0] == 'ok'
 
286
 
 
287
    def gather_stats(self, revid=None, committers=None):
 
288
        """See Repository.gather_stats()."""
 
289
        path = self.bzrdir._path_for_remote_call(self._client)
 
290
        if revid in (None, NULL_REVISION):
 
291
            fmt_revid = ''
 
292
        else:
 
293
            fmt_revid = revid
 
294
        if committers is None or not committers:
 
295
            fmt_committers = 'no'
 
296
        else:
 
297
            fmt_committers = 'yes'
 
298
        response = self._client.call2('Repository.gather_stats', path,
 
299
                                      fmt_revid, fmt_committers)
 
300
        assert response[0][0] == 'ok', \
 
301
            'unexpected response code %s' % (response[0],)
 
302
 
 
303
        body = response[1].read_body_bytes()
 
304
        result = {}
 
305
        for line in body.split('\n'):
 
306
            if not line:
 
307
                continue
 
308
            key, val_text = line.split(':')
 
309
            if key in ('revisions', 'size', 'committers'):
 
310
                result[key] = int(val_text)
 
311
            elif key in ('firstrev', 'latestrev'):
 
312
                values = val_text.split(' ')[1:]
 
313
                result[key] = (float(values[0]), long(values[1]))
 
314
 
 
315
        return result
 
316
 
 
317
    def get_physical_lock_status(self):
 
318
        """See Repository.get_physical_lock_status()."""
 
319
        return False
 
320
 
 
321
    def is_shared(self):
 
322
        """See Repository.is_shared()."""
 
323
        path = self.bzrdir._path_for_remote_call(self._client)
 
324
        response = self._client.call('Repository.is_shared', path)
 
325
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
326
        return response[0] == 'yes'
 
327
 
 
328
    def lock_read(self):
 
329
        # wrong eventually - want a local lock cache context
 
330
        if not self._lock_mode:
 
331
            self._lock_mode = 'r'
 
332
            self._lock_count = 1
 
333
            if self._real_repository is not None:
 
334
                self._real_repository.lock_read()
 
335
        else:
 
336
            self._lock_count += 1
 
337
 
 
338
    def _remote_lock_write(self, token):
 
339
        path = self.bzrdir._path_for_remote_call(self._client)
 
340
        if token is None:
 
341
            token = ''
 
342
        response = self._client.call('Repository.lock_write', path, token)
 
343
        if response[0] == 'ok':
 
344
            ok, token = response
 
345
            return token
 
346
        elif response[0] == 'LockContention':
 
347
            raise errors.LockContention('(remote lock)')
 
348
        elif response[0] == 'UnlockableTransport':
 
349
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
350
        else:
 
351
            assert False, 'unexpected response code %s' % (response,)
 
352
 
 
353
    def lock_write(self, token=None):
 
354
        if not self._lock_mode:
 
355
            self._lock_token = self._remote_lock_write(token)
 
356
            assert self._lock_token, 'Remote server did not return a token!'
 
357
            if self._real_repository is not None:
 
358
                self._real_repository.lock_write(token=self._lock_token)
 
359
            if token is not None:
 
360
                self._leave_lock = True
 
361
            else:
 
362
                self._leave_lock = False
 
363
            self._lock_mode = 'w'
 
364
            self._lock_count = 1
 
365
        elif self._lock_mode == 'r':
 
366
            raise errors.ReadOnlyError(self)
 
367
        else:
 
368
            self._lock_count += 1
 
369
        return self._lock_token
 
370
 
 
371
    def leave_lock_in_place(self):
 
372
        self._leave_lock = True
 
373
 
 
374
    def dont_leave_lock_in_place(self):
 
375
        self._leave_lock = False
 
376
 
 
377
    def _set_real_repository(self, repository):
 
378
        """Set the _real_repository for this repository.
 
379
 
 
380
        :param repository: The repository to fallback to for non-hpss
 
381
            implemented operations.
 
382
        """
 
383
        assert not isinstance(repository, RemoteRepository)
 
384
        self._real_repository = repository
 
385
        if self._lock_mode == 'w':
 
386
            # if we are already locked, the real repository must be able to
 
387
            # acquire the lock with our token.
 
388
            self._real_repository.lock_write(self._lock_token)
 
389
        elif self._lock_mode == 'r':
 
390
            self._real_repository.lock_read()
 
391
 
 
392
    def _unlock(self, token):
 
393
        path = self.bzrdir._path_for_remote_call(self._client)
 
394
        response = self._client.call('Repository.unlock', path, token)
 
395
        if response == ('ok',):
 
396
            return
 
397
        elif response[0] == 'TokenMismatch':
 
398
            raise errors.TokenMismatch(token, '(remote token)')
 
399
        else:
 
400
            assert False, 'unexpected response code %s' % (response,)
 
401
 
 
402
    def unlock(self):
 
403
        self._lock_count -= 1
 
404
        if not self._lock_count:
 
405
            mode = self._lock_mode
 
406
            self._lock_mode = None
 
407
            if self._real_repository is not None:
 
408
                self._real_repository.unlock()
 
409
            if mode != 'w':
 
410
                return
 
411
            assert self._lock_token, 'Locked, but no token!'
 
412
            token = self._lock_token
 
413
            self._lock_token = None
 
414
            if not self._leave_lock:
 
415
                self._unlock(token)
 
416
 
 
417
    def break_lock(self):
 
418
        # should hand off to the network
 
419
        self._ensure_real()
 
420
        return self._real_repository.break_lock()
 
421
 
 
422
    ### These methods are just thin shims to the VFS object for now.
 
423
 
 
424
    def revision_tree(self, revision_id):
 
425
        self._ensure_real()
 
426
        return self._real_repository.revision_tree(revision_id)
 
427
 
 
428
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
429
                           timezone=None, committer=None, revprops=None,
 
430
                           revision_id=None):
 
431
        # FIXME: It ought to be possible to call this without immediately
 
432
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
433
        self._ensure_real()
 
434
        builder = self._real_repository.get_commit_builder(branch, parents,
 
435
                config, timestamp=timestamp, timezone=timezone,
 
436
                committer=committer, revprops=revprops, revision_id=revision_id)
 
437
        # Make the builder use this RemoteRepository rather than the real one.
 
438
        builder.repository = self
 
439
        return builder
 
440
 
 
441
    @needs_write_lock
 
442
    def add_inventory(self, revid, inv, parents):
 
443
        self._ensure_real()
 
444
        return self._real_repository.add_inventory(revid, inv, parents)
 
445
 
 
446
    @needs_write_lock
 
447
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
448
        self._ensure_real()
 
449
        return self._real_repository.add_revision(
 
450
            rev_id, rev, inv=inv, config=config)
 
451
 
 
452
    @needs_read_lock
 
453
    def get_inventory(self, revision_id):
 
454
        self._ensure_real()
 
455
        return self._real_repository.get_inventory(revision_id)
 
456
 
 
457
    @needs_read_lock
 
458
    def get_revision(self, revision_id):
 
459
        self._ensure_real()
 
460
        return self._real_repository.get_revision(revision_id)
 
461
 
 
462
    @property
 
463
    def weave_store(self):
 
464
        self._ensure_real()
 
465
        return self._real_repository.weave_store
 
466
 
 
467
    def get_transaction(self):
 
468
        self._ensure_real()
 
469
        return self._real_repository.get_transaction()
 
470
 
 
471
    @needs_read_lock
 
472
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
473
        self._ensure_real()
 
474
        return self._real_repository.clone(
 
475
            a_bzrdir, revision_id=revision_id, basis=basis)
 
476
 
 
477
    def make_working_trees(self):
 
478
        """RemoteRepositories never create working trees by default."""
 
479
        return False
 
480
 
 
481
    def fetch(self, source, revision_id=None, pb=None):
 
482
        self._ensure_real()
 
483
        return self._real_repository.fetch(
 
484
            source, revision_id=revision_id, pb=pb)
 
485
 
 
486
    @property
 
487
    def control_weaves(self):
 
488
        self._ensure_real()
 
489
        return self._real_repository.control_weaves
 
490
 
 
491
    @needs_read_lock
 
492
    def get_ancestry(self, revision_id):
 
493
        self._ensure_real()
 
494
        return self._real_repository.get_ancestry(revision_id)
 
495
 
 
496
    @needs_read_lock
 
497
    def get_inventory_weave(self):
 
498
        self._ensure_real()
 
499
        return self._real_repository.get_inventory_weave()
 
500
 
 
501
    def fileids_altered_by_revision_ids(self, revision_ids):
 
502
        self._ensure_real()
 
503
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
504
 
 
505
    @needs_read_lock
 
506
    def get_signature_text(self, revision_id):
 
507
        self._ensure_real()
 
508
        return self._real_repository.get_signature_text(revision_id)
 
509
 
 
510
    @needs_read_lock
 
511
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
512
        self._ensure_real()
 
513
        return self._real_repository.get_revision_graph_with_ghosts(
 
514
            revision_ids=revision_ids)
 
515
 
 
516
    @needs_read_lock
 
517
    def get_inventory_xml(self, revision_id):
 
518
        self._ensure_real()
 
519
        return self._real_repository.get_inventory_xml(revision_id)
 
520
 
 
521
    def deserialise_inventory(self, revision_id, xml):
 
522
        self._ensure_real()
 
523
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
524
 
 
525
    def reconcile(self, other=None, thorough=False):
 
526
        self._ensure_real()
 
527
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
528
        
 
529
    def all_revision_ids(self):
 
530
        self._ensure_real()
 
531
        return self._real_repository.all_revision_ids()
 
532
    
 
533
    @needs_read_lock
 
534
    def get_deltas_for_revisions(self, revisions):
 
535
        self._ensure_real()
 
536
        return self._real_repository.get_deltas_for_revisions(revisions)
 
537
 
 
538
    @needs_read_lock
 
539
    def get_revision_delta(self, revision_id):
 
540
        self._ensure_real()
 
541
        return self._real_repository.get_revision_delta(revision_id)
 
542
 
 
543
    @needs_read_lock
 
544
    def revision_trees(self, revision_ids):
 
545
        self._ensure_real()
 
546
        return self._real_repository.revision_trees(revision_ids)
 
547
 
 
548
    @needs_read_lock
 
549
    def get_revision_reconcile(self, revision_id):
 
550
        self._ensure_real()
 
551
        return self._real_repository.get_revision_reconcile(revision_id)
 
552
 
 
553
    @needs_read_lock
 
554
    def check(self, revision_ids):
 
555
        self._ensure_real()
 
556
        return self._real_repository.check(revision_ids)
 
557
 
 
558
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
559
        self._ensure_real()
 
560
        return self._real_repository.copy_content_into(
 
561
            destination, revision_id=revision_id, basis=basis)
 
562
 
 
563
    def set_make_working_trees(self, new_value):
 
564
        raise NotImplementedError(self.set_make_working_trees)
 
565
 
 
566
    @needs_write_lock
 
567
    def sign_revision(self, revision_id, gpg_strategy):
 
568
        self._ensure_real()
 
569
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
570
 
 
571
    @needs_read_lock
 
572
    def get_revisions(self, revision_ids):
 
573
        self._ensure_real()
 
574
        return self._real_repository.get_revisions(revision_ids)
 
575
 
 
576
    def supports_rich_root(self):
 
577
        self._ensure_real()
 
578
        return self._real_repository.supports_rich_root()
 
579
 
 
580
    def iter_reverse_revision_history(self, revision_id):
 
581
        self._ensure_real()
 
582
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
583
 
 
584
    @property
 
585
    def _serializer(self):
 
586
        self._ensure_real()
 
587
        return self._real_repository._serializer
 
588
 
 
589
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
590
        self._ensure_real()
 
591
        return self._real_repository.store_revision_signature(
 
592
            gpg_strategy, plaintext, revision_id)
 
593
 
 
594
    def has_signature_for_revision_id(self, revision_id):
 
595
        self._ensure_real()
 
596
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
597
 
 
598
 
 
599
class RemoteBranchLockableFiles(LockableFiles):
 
600
    """A 'LockableFiles' implementation that talks to a smart server.
 
601
    
 
602
    This is not a public interface class.
 
603
    """
 
604
 
 
605
    def __init__(self, bzrdir, _client):
 
606
        self.bzrdir = bzrdir
 
607
        self._client = _client
 
608
        LockableFiles.__init__(self, bzrdir.root_transport, 'lock',
 
609
                lockdir.LockDir)
 
610
 
 
611
    def get(self, path):
 
612
        """'get' a remote path as per the LockableFiles interface.
 
613
 
 
614
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
615
             just retrieve a file, instead we ask the smart server to generate
 
616
             a configuration for us - which is retrieved as an INI file.
 
617
        """
 
618
        if path != 'branch.conf':
 
619
            raise errors.NoSuchFile(path)
 
620
        assert path == 'branch.conf'
 
621
        path = self.bzrdir._path_for_remote_call(self._client)
 
622
        response = self._client.call2('Branch.get_config_file', path)
 
623
        assert response[0][0] == 'ok', \
 
624
            'unexpected response code %s' % (response[0],)
 
625
        return StringIO(response[1].read_body_bytes())
 
626
 
 
627
 
 
628
class RemoteBranchFormat(branch.BranchFormat):
 
629
 
 
630
    def __eq__(self, other):
 
631
        return (isinstance(other, RemoteBranchFormat) and 
 
632
            self.__dict__ == other.__dict__)
 
633
 
 
634
    def get_format_description(self):
 
635
        return 'Remote BZR Branch'
 
636
 
 
637
    def get_format_string(self):
 
638
        return 'Remote BZR Branch'
 
639
 
 
640
    def open(self, a_bzrdir):
 
641
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
642
        return a_bzrdir.open_branch()
 
643
 
 
644
    def initialize(self, a_bzrdir):
 
645
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
646
        return a_bzrdir.create_branch()
 
647
 
 
648
 
 
649
class RemoteBranch(branch.Branch):
 
650
    """Branch stored on a server accessed by HPSS RPC.
 
651
 
 
652
    At the moment most operations are mapped down to simple file operations.
 
653
    """
 
654
 
 
655
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
656
        _client=None):
 
657
        """Create a RemoteBranch instance.
 
658
 
 
659
        :param real_branch: An optional local implementation of the branch
 
660
            format, usually accessing the data via the VFS.
 
661
        :param _client: Private parameter for testing.
 
662
        """
 
663
        #branch.Branch.__init__(self)
 
664
        self._revision_history_cache = None
 
665
        self.bzrdir = remote_bzrdir
 
666
        if _client is not None:
 
667
            self._client = _client
 
668
        else:
 
669
            self._client = client.SmartClient(self.bzrdir._medium)
 
670
        self.repository = remote_repository
 
671
        if real_branch is not None:
 
672
            self._real_branch = real_branch
 
673
            # Give the remote repository the matching real repo.
 
674
            real_repo = self._real_branch.repository
 
675
            if isinstance(real_repo, RemoteRepository):
 
676
                real_repo._ensure_real()
 
677
                real_repo = real_repo._real_repository
 
678
            self.repository._set_real_repository(real_repo)
 
679
            # Give the branch the remote repository to let fast-pathing happen.
 
680
            self._real_branch.repository = self.repository
 
681
        else:
 
682
            self._real_branch = None
 
683
        # Fill out expected attributes of branch for bzrlib api users.
 
684
        self._format = RemoteBranchFormat()
 
685
        self.base = self.bzrdir.root_transport.base
 
686
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
687
        self._lock_mode = None
 
688
        self._lock_token = None
 
689
        self._lock_count = 0
 
690
        self._leave_lock = False
 
691
 
 
692
    def _ensure_real(self):
 
693
        """Ensure that there is a _real_branch set.
 
694
 
 
695
        used before calls to self._real_branch.
 
696
        """
 
697
        if not self._real_branch:
 
698
            assert vfs.vfs_enabled()
 
699
            self.bzrdir._ensure_real()
 
700
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
701
            # Give the remote repository the matching real repo.
 
702
            real_repo = self._real_branch.repository
 
703
            if isinstance(real_repo, RemoteRepository):
 
704
                real_repo._ensure_real()
 
705
                real_repo = real_repo._real_repository
 
706
            self.repository._set_real_repository(real_repo)
 
707
            # Give the branch the remote repository to let fast-pathing happen.
 
708
            self._real_branch.repository = self.repository
 
709
            # XXX: deal with _lock_mode == 'w'
 
710
            if self._lock_mode == 'r':
 
711
                self._real_branch.lock_read()
 
712
 
 
713
    def get_physical_lock_status(self):
 
714
        """See Branch.get_physical_lock_status()."""
 
715
        # should be an API call to the server, as branches must be lockable.
 
716
        self._ensure_real()
 
717
        return self._real_branch.get_physical_lock_status()
 
718
 
 
719
    def lock_read(self):
 
720
        if not self._lock_mode:
 
721
            self._lock_mode = 'r'
 
722
            self._lock_count = 1
 
723
            if self._real_branch is not None:
 
724
                self._real_branch.lock_read()
 
725
        else:
 
726
            self._lock_count += 1
 
727
 
 
728
    def _remote_lock_write(self, tokens):
 
729
        if tokens is None:
 
730
            branch_token = repo_token = ''
 
731
        else:
 
732
            branch_token, repo_token = tokens
 
733
        path = self.bzrdir._path_for_remote_call(self._client)
 
734
        response = self._client.call('Branch.lock_write', path, branch_token,
 
735
                                     repo_token)
 
736
        if response[0] == 'ok':
 
737
            ok, branch_token, repo_token = response
 
738
            return branch_token, repo_token
 
739
        elif response[0] == 'LockContention':
 
740
            raise errors.LockContention('(remote lock)')
 
741
        elif response[0] == 'TokenMismatch':
 
742
            raise errors.TokenMismatch(tokens, '(remote tokens)')
 
743
        elif response[0] == 'UnlockableTransport':
 
744
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
745
        elif response[0] == 'ReadOnlyError':
 
746
            raise errors.ReadOnlyError(self)
 
747
        else:
 
748
            assert False, 'unexpected response code %r' % (response,)
 
749
            
 
750
    def lock_write(self, tokens=None):
 
751
        if not self._lock_mode:
 
752
            remote_tokens = self._remote_lock_write(tokens)
 
753
            self._lock_token, self._repo_lock_token = remote_tokens
 
754
            assert self._lock_token, 'Remote server did not return a token!'
 
755
            # TODO: We really, really, really don't want to call _ensure_real
 
756
            # here, but it's the easiest way to ensure coherency between the
 
757
            # state of the RemoteBranch and RemoteRepository objects and the
 
758
            # physical locks.  If we don't materialise the real objects here,
 
759
            # then getting everything in the right state later is complex, so
 
760
            # for now we just do it the lazy way.
 
761
            #   -- Andrew Bennetts, 2007-02-22.
 
762
            self._ensure_real()
 
763
            if self._real_branch is not None:
 
764
                self._real_branch.lock_write(tokens=remote_tokens)
 
765
            if tokens is not None:
 
766
                self._leave_lock = True
 
767
            else:
 
768
                # XXX: this case seems to be unreachable; tokens cannot be None.
 
769
                self._leave_lock = False
 
770
            self._lock_mode = 'w'
 
771
            self._lock_count = 1
 
772
        elif self._lock_mode == 'r':
 
773
            raise errors.ReadOnlyTransaction
 
774
        else:
 
775
            if tokens is not None:
 
776
                # Tokens were given to lock_write, and we're relocking, so check
 
777
                # that the given tokens actually match the ones we already have.
 
778
                held_tokens = (self._lock_token, self._repo_lock_token)
 
779
                if tokens != held_tokens:
 
780
                    raise errors.TokenMismatch(str(tokens), str(held_tokens))
 
781
            self._lock_count += 1
 
782
        return self._lock_token, self._repo_lock_token
 
783
 
 
784
    def _unlock(self, branch_token, repo_token):
 
785
        path = self.bzrdir._path_for_remote_call(self._client)
 
786
        response = self._client.call('Branch.unlock', path, branch_token,
 
787
                                     repo_token)
 
788
        if response == ('ok',):
 
789
            return
 
790
        elif response[0] == 'TokenMismatch':
 
791
            raise errors.TokenMismatch(
 
792
                str((branch_token, repo_token)), '(remote tokens)')
 
793
        else:
 
794
            assert False, 'unexpected response code %s' % (response,)
 
795
 
 
796
    def unlock(self):
 
797
        self._lock_count -= 1
 
798
        if not self._lock_count:
 
799
            self._clear_cached_state()
 
800
            mode = self._lock_mode
 
801
            self._lock_mode = None
 
802
            if self._real_branch is not None:
 
803
                if not self._leave_lock:
 
804
                    # If this RemoteBranch will remove the physical lock for the
 
805
                    # repository, make sure the _real_branch doesn't do it
 
806
                    # first.  (Because the _real_branch's repository is set to
 
807
                    # be the RemoteRepository.)
 
808
                    self._real_branch.repository.leave_lock_in_place()
 
809
                self._real_branch.unlock()
 
810
            if mode != 'w':
 
811
                return
 
812
            assert self._lock_token, 'Locked, but no token!'
 
813
            branch_token = self._lock_token
 
814
            repo_token = self._repo_lock_token
 
815
            self._lock_token = None
 
816
            self._repo_lock_token = None
 
817
            if not self._leave_lock:
 
818
                self._unlock(branch_token, repo_token)
 
819
 
 
820
    def break_lock(self):
 
821
        self._ensure_real()
 
822
        return self._real_branch.break_lock()
 
823
 
 
824
    def leave_lock_in_place(self):
 
825
        self._leave_lock = True
 
826
 
 
827
    def dont_leave_lock_in_place(self):
 
828
        self._leave_lock = False
 
829
 
 
830
    def last_revision_info(self):
 
831
        """See Branch.last_revision_info()."""
 
832
        path = self.bzrdir._path_for_remote_call(self._client)
 
833
        response = self._client.call('Branch.last_revision_info', path)
 
834
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
835
        revno = int(response[1])
 
836
        last_revision = response[2]
 
837
        if last_revision == '':
 
838
            last_revision = NULL_REVISION
 
839
        return (revno, last_revision)
 
840
 
 
841
    def _gen_revision_history(self):
 
842
        """See Branch._gen_revision_history()."""
 
843
        path = self.bzrdir._path_for_remote_call(self._client)
 
844
        response = self._client.call2('Branch.revision_history', path)
 
845
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
846
        result = response[1].read_body_bytes().split('\x00')
 
847
        if result == ['']:
 
848
            return []
 
849
        return result
 
850
 
 
851
    @needs_write_lock
 
852
    def set_revision_history(self, rev_history):
 
853
        # Send just the tip revision of the history; the server will generate
 
854
        # the full history from that.  If the revision doesn't exist in this
 
855
        # branch, NoSuchRevision will be raised.
 
856
        path = self.bzrdir._path_for_remote_call(self._client)
 
857
        if rev_history == []:
 
858
            rev_id = ''
 
859
        else:
 
860
            rev_id = rev_history[-1]
 
861
        response = self._client.call('Branch.set_last_revision',
 
862
            path, self._lock_token, self._repo_lock_token, rev_id)
 
863
        if response[0] == 'NoSuchRevision':
 
864
            raise NoSuchRevision(self, rev_id)
 
865
        else:
 
866
            assert response == ('ok',), (
 
867
                'unexpected response code %r' % (response,))
 
868
        self._cache_revision_history(rev_history)
 
869
 
 
870
    def get_parent(self):
 
871
        self._ensure_real()
 
872
        return self._real_branch.get_parent()
 
873
        
 
874
    def set_parent(self, url):
 
875
        self._ensure_real()
 
876
        return self._real_branch.set_parent(url)
 
877
        
 
878
    def get_config(self):
 
879
        return RemoteBranchConfig(self)
 
880
 
 
881
    def sprout(self, to_bzrdir, revision_id=None):
 
882
        # Like Branch.sprout, except that it sprouts a branch in the default
 
883
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
884
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
885
        # to_bzrdir.create_branch...
 
886
        self._ensure_real()
 
887
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
888
        self._real_branch.copy_content_into(result, revision_id=revision_id)
 
889
        result.set_parent(self.bzrdir.root_transport.base)
 
890
        return result
 
891
 
 
892
    @needs_write_lock
 
893
    def append_revision(self, *revision_ids):
 
894
        self._ensure_real()
 
895
        return self._real_branch.append_revision(*revision_ids)
 
896
 
 
897
    @needs_write_lock
 
898
    def pull(self, source, overwrite=False, stop_revision=None):
 
899
        self._ensure_real()
 
900
        self._real_branch.pull(
 
901
            source, overwrite=overwrite, stop_revision=stop_revision)
 
902
 
 
903
    @needs_read_lock
 
904
    def push(self, target, overwrite=False, stop_revision=None):
 
905
        self._ensure_real()
 
906
        return self._real_branch.push(
 
907
            target, overwrite=overwrite, stop_revision=stop_revision)
 
908
 
 
909
    def is_locked(self):
 
910
        return self._lock_count >= 1
 
911
 
 
912
    def set_last_revision_info(self, revno, revision_id):
 
913
        self._ensure_real()
 
914
        self._clear_cached_state()
 
915
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
916
 
 
917
    def generate_revision_history(self, revision_id, last_rev=None,
 
918
                                  other_branch=None):
 
919
        self._ensure_real()
 
920
        return self._real_branch.generate_revision_history(
 
921
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
922
 
 
923
    @property
 
924
    def tags(self):
 
925
        self._ensure_real()
 
926
        return self._real_branch.tags
 
927
 
 
928
    def set_push_location(self, location):
 
929
        self._ensure_real()
 
930
        return self._real_branch.set_push_location(location)
 
931
 
 
932
    def update_revisions(self, other, stop_revision=None):
 
933
        self._ensure_real()
 
934
        return self._real_branch.update_revisions(
 
935
            other, stop_revision=stop_revision)
 
936
 
 
937
 
 
938
class RemoteWorkingTree(object):
 
939
 
 
940
    def __init__(self, remote_bzrdir, real_workingtree):
 
941
        self.real_workingtree = real_workingtree
 
942
        self.bzrdir = remote_bzrdir
 
943
 
 
944
    def __getattr__(self, name):
 
945
        # XXX: temporary way to lazily delegate everything to the real
 
946
        # workingtree
 
947
        return getattr(self.real_workingtree, name)
 
948
 
 
949
 
 
950
class RemoteBranchConfig(BranchConfig):
 
951
 
 
952
    def username(self):
 
953
        self.branch._ensure_real()
 
954
        return self.branch._real_branch.get_config().username()
 
955
 
 
956
    def _get_branch_data_config(self):
 
957
        self.branch._ensure_real()
 
958
        if self._branch_data_config is None:
 
959
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
960
        return self._branch_data_config
 
961