/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

Fix another bug in urlutils.join.

Show diffs side-by-side

added added

removed removed

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