/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-18 05:38:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070418053831-u9bbsbljuzymrz6z
Various changes in response to John's review.

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