/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

Record merge against split out urlutils work.

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