/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: Martin Pool
  • Date: 2007-04-23 09:52:50 UTC
  • mto: (2018.18.24 hpss-faster-copy)
  • mto: This revision was merged to the branch mainline in revision 2462.
  • Revision ID: mbp@sourcefrog.net-20070423095250-xzaleukzs05x9lp0
Add new Repository.sprout,

Cleaner in intention and purpose than copy_content_into.  It doesn't copy the
extra settings of the repository (like working-trees and shared), which is
normally what you'll want.

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