/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: Andrew Bennetts
  • Date: 2007-04-17 04:30:51 UTC
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070417043051-yt3bxfhm85byfntc
Add some missing _ensure_real calls, and a missing import.

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