~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Pool
  • Date: 2007-10-12 08:00:07 UTC
  • mto: This revision was merged to the branch mainline in revision 2913.
  • Revision ID: mbp@sourcefrog.net-20071012080007-vf80woayyom8s8e1
Rename update_to_one_parent_via_delta to more wieldy update_basis_by_delta

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
19
19
 
20
 
import bz2
21
20
from cStringIO import StringIO
22
21
 
23
22
from bzrlib import (
24
23
    branch,
25
 
    debug,
26
24
    errors,
27
 
    graph,
28
25
    lockdir,
29
26
    repository,
30
 
    revision,
31
 
    symbol_versioning,
32
27
)
33
 
from bzrlib.branch import BranchReferenceFormat
 
28
from bzrlib.branch import Branch, BranchReferenceFormat
34
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
30
from bzrlib.config import BranchConfig, TreeConfig
36
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
 
from bzrlib.errors import (
38
 
    NoSuchRevision,
39
 
    SmartProtocolError,
40
 
    )
 
32
from bzrlib.errors import NoSuchRevision
41
33
from bzrlib.lockable_files import LockableFiles
42
 
from bzrlib.pack import ContainerPushParser
 
34
from bzrlib.revision import NULL_REVISION
43
35
from bzrlib.smart import client, vfs
44
36
from bzrlib.symbol_versioning import (
45
 
    deprecated_in,
46
37
    deprecated_method,
 
38
    zero_ninetyone,
47
39
    )
48
 
from bzrlib.revision import ensure_null, NULL_REVISION
49
 
from bzrlib.trace import mutter, note, warning
50
 
 
 
40
from bzrlib.trace import note
51
41
 
52
42
# Note: RemoteBzrDirFormat is in bzrdir.py
53
43
 
66
56
        self._real_bzrdir = None
67
57
 
68
58
        if _client is None:
69
 
            medium = transport.get_smart_medium()
70
 
            self._client = client._SmartClient(medium)
 
59
            self._shared_medium = transport.get_shared_medium()
 
60
            self._client = client._SmartClient(self._shared_medium)
71
61
        else:
72
62
            self._client = _client
 
63
            self._shared_medium = None
73
64
            return
74
65
 
75
66
        path = self._path_for_remote_call(self._client)
88
79
            self._real_bzrdir = BzrDir.open_from_transport(
89
80
                self.root_transport, _server_formats=False)
90
81
 
91
 
    def cloning_metadir(self, stacked=False):
92
 
        self._ensure_real()
93
 
        return self._real_bzrdir.cloning_metadir(stacked)
94
 
 
95
 
    def _translate_error(self, err, **context):
96
 
        _translate_error(err, bzrdir=self, **context)
97
 
        
98
82
    def create_repository(self, shared=False):
99
83
        self._ensure_real()
100
84
        self._real_bzrdir.create_repository(shared=shared)
101
85
        return self.open_repository()
102
86
 
103
 
    def destroy_repository(self):
104
 
        """See BzrDir.destroy_repository"""
105
 
        self._ensure_real()
106
 
        self._real_bzrdir.destroy_repository()
107
 
 
108
87
    def create_branch(self):
109
88
        self._ensure_real()
110
89
        real_branch = self._real_bzrdir.create_branch()
115
94
        self._ensure_real()
116
95
        self._real_bzrdir.destroy_branch()
117
96
 
118
 
    def create_workingtree(self, revision_id=None, from_branch=None):
 
97
    def create_workingtree(self, revision_id=None):
119
98
        raise errors.NotLocalUrl(self.transport.base)
120
99
 
121
100
    def find_branch_format(self):
129
108
    def get_branch_reference(self):
130
109
        """See BzrDir.get_branch_reference()."""
131
110
        path = self._path_for_remote_call(self._client)
132
 
        try:
133
 
            response = self._client.call('BzrDir.open_branch', path)
134
 
        except errors.ErrorFromSmartServer, err:
135
 
            self._translate_error(err)
 
111
        response = self._client.call('BzrDir.open_branch', path)
136
112
        if response[0] == 'ok':
137
113
            if response[1] == '':
138
114
                # branch at this location.
140
116
            else:
141
117
                # a branch reference, use the existing BranchReference logic.
142
118
                return response[1]
 
119
        elif response == ('nobranch',):
 
120
            raise errors.NotBranchError(path=self.root_transport.base)
143
121
        else:
144
122
            raise errors.UnexpectedSmartServerResponse(response)
145
123
 
146
 
    def _get_tree_branch(self):
147
 
        """See BzrDir._get_tree_branch()."""
148
 
        return None, self.open_branch()
149
 
 
150
124
    def open_branch(self, _unsupported=False):
151
 
        if _unsupported:
152
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
125
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
153
126
        reference_url = self.get_branch_reference()
154
127
        if reference_url is None:
155
128
            # branch at this location.
161
134
                
162
135
    def open_repository(self):
163
136
        path = self._path_for_remote_call(self._client)
164
 
        verb = 'BzrDir.find_repositoryV2'
165
 
        try:
166
 
            try:
167
 
                response = self._client.call(verb, path)
168
 
            except errors.UnknownSmartMethod:
169
 
                verb = 'BzrDir.find_repository'
170
 
                response = self._client.call(verb, path)
171
 
        except errors.ErrorFromSmartServer, err:
172
 
            self._translate_error(err)
173
 
        if response[0] != 'ok':
174
 
            raise errors.UnexpectedSmartServerResponse(response)
175
 
        if verb == 'BzrDir.find_repository':
176
 
            # servers that don't support the V2 method don't support external
177
 
            # references either.
178
 
            response = response + ('no', )
179
 
        if not (len(response) == 5):
180
 
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
137
        response = self._client.call('BzrDir.find_repository', path)
 
138
        assert response[0] in ('ok', 'norepository'), \
 
139
            'unexpected response code %s' % (response,)
 
140
        if response[0] == 'norepository':
 
141
            raise errors.NoRepositoryPresent(self)
 
142
        assert len(response) == 4, 'incorrect response length %s' % (response,)
181
143
        if response[1] == '':
182
144
            format = RemoteRepositoryFormat()
183
145
            format.rich_root_data = (response[2] == 'yes')
184
146
            format.supports_tree_reference = (response[3] == 'yes')
185
 
            # No wire format to check this yet.
186
 
            format.supports_external_lookups = (response[4] == 'yes')
187
 
            # Used to support creating a real format instance when needed.
188
 
            format._creating_bzrdir = self
189
147
            return RemoteRepository(self, format)
190
148
        else:
191
149
            raise errors.NoRepositoryPresent(self)
221
179
        """Upgrading of remote bzrdirs is not supported yet."""
222
180
        return False
223
181
 
224
 
    def clone(self, url, revision_id=None, force_new_repo=False,
225
 
              preserve_stacking=False):
 
182
    def clone(self, url, revision_id=None, force_new_repo=False):
226
183
        self._ensure_real()
227
184
        return self._real_bzrdir.clone(url, revision_id=revision_id,
228
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
229
 
 
230
 
    def get_config(self):
231
 
        self._ensure_real()
232
 
        return self._real_bzrdir.get_config()
 
185
            force_new_repo=force_new_repo)
233
186
 
234
187
 
235
188
class RemoteRepositoryFormat(repository.RepositoryFormat):
238
191
    Instances of this repository are represented by RemoteRepository
239
192
    instances.
240
193
 
241
 
    The RemoteRepositoryFormat is parameterized during construction
 
194
    The RemoteRepositoryFormat is parameterised during construction
242
195
    to reflect the capabilities of the real, remote format. Specifically
243
196
    the attributes rich_root_data and supports_tree_reference are set
244
197
    on a per instance basis, and are not set (and should not be) at
245
198
    the class level.
246
199
    """
247
200
 
248
 
    _matchingbzrdir = RemoteBzrDirFormat()
 
201
    _matchingbzrdir = RemoteBzrDirFormat
249
202
 
250
203
    def initialize(self, a_bzrdir, shared=False):
251
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
252
 
            prior_repo = self._creating_bzrdir.open_repository()
253
 
            prior_repo._ensure_real()
254
 
            return prior_repo._real_repository._format.initialize(
255
 
                a_bzrdir, shared=shared)
 
204
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
205
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
256
206
        return a_bzrdir.create_repository(shared=shared)
257
207
    
258
208
    def open(self, a_bzrdir):
259
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
260
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
209
        assert isinstance(a_bzrdir, RemoteBzrDir)
261
210
        return a_bzrdir.open_repository()
262
211
 
263
212
    def get_format_description(self):
300
249
            self._real_repository = None
301
250
        self.bzrdir = remote_bzrdir
302
251
        if _client is None:
303
 
            self._client = remote_bzrdir._client
 
252
            self._client = client._SmartClient(self.bzrdir._shared_medium)
304
253
        else:
305
254
            self._client = _client
306
255
        self._format = format
308
257
        self._lock_token = None
309
258
        self._lock_count = 0
310
259
        self._leave_lock = False
311
 
        # A cache of looked up revision parent data; reset at unlock time.
312
 
        self._parents_map = None
313
 
        if 'hpss' in debug.debug_flags:
314
 
            self._requested_parents = None
315
 
        # For tests:
316
 
        # These depend on the actual remote format, so force them off for
317
 
        # maximum compatibility. XXX: In future these should depend on the
318
 
        # remote repository instance, but this is irrelevant until we perform
319
 
        # reconcile via an RPC call.
320
 
        self._reconcile_does_inventory_gc = False
321
 
        self._reconcile_fixes_text_parents = False
322
 
        self._reconcile_backsup_inventory = False
 
260
        # for tests
 
261
        self._reconcile_does_inventory_gc = True
323
262
        self.base = self.bzrdir.transport.base
324
 
        # Additional places to query for data.
325
 
        self._fallback_repositories = []
326
263
 
327
264
    def __str__(self):
328
265
        return "%s(%s)" % (self.__class__.__name__, self.base)
361
298
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
362
299
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
363
300
 
364
 
    def _translate_error(self, err, **context):
365
 
        self.bzrdir._translate_error(err, repository=self, **context)
366
 
 
367
 
    def find_text_key_references(self):
368
 
        """Find the text key references within the repository.
369
 
 
370
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
371
 
        revision_ids. Each altered file-ids has the exact revision_ids that
372
 
        altered it listed explicitly.
373
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
374
 
            to whether they were referred to by the inventory of the
375
 
            revision_id that they contain. The inventory texts from all present
376
 
            revision ids are assessed to generate this report.
377
 
        """
378
 
        self._ensure_real()
379
 
        return self._real_repository.find_text_key_references()
380
 
 
381
 
    def _generate_text_key_index(self):
382
 
        """Generate a new text key index for the repository.
383
 
 
384
 
        This is an expensive function that will take considerable time to run.
385
 
 
386
 
        :return: A dict mapping (file_id, revision_id) tuples to a list of
387
 
            parents, also (file_id, revision_id) tuples.
388
 
        """
389
 
        self._ensure_real()
390
 
        return self._real_repository._generate_text_key_index()
391
 
 
392
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
393
301
    def get_revision_graph(self, revision_id=None):
394
302
        """See Repository.get_revision_graph()."""
395
 
        return self._get_revision_graph(revision_id)
396
 
 
397
 
    def _get_revision_graph(self, revision_id):
398
 
        """Private method for using with old (< 1.2) servers to fallback."""
399
303
        if revision_id is None:
400
304
            revision_id = ''
401
 
        elif revision.is_null(revision_id):
 
305
        elif revision_id == NULL_REVISION:
402
306
            return {}
403
307
 
404
308
        path = self.bzrdir._path_for_remote_call(self._client)
405
 
        try:
406
 
            response = self._client.call_expecting_body(
407
 
                'Repository.get_revision_graph', path, revision_id)
408
 
        except errors.ErrorFromSmartServer, err:
409
 
            self._translate_error(err)
410
 
        response_tuple, response_handler = response
411
 
        if response_tuple[0] != 'ok':
412
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
413
 
        coded = response_handler.read_body_bytes()
414
 
        if coded == '':
415
 
            # no revisions in this repository!
416
 
            return {}
417
 
        lines = coded.split('\n')
418
 
        revision_graph = {}
419
 
        for line in lines:
420
 
            d = tuple(line.split())
421
 
            revision_graph[d[0]] = d[1:]
422
 
            
423
 
        return revision_graph
 
309
        assert type(revision_id) is str
 
310
        response = self._client.call_expecting_body(
 
311
            'Repository.get_revision_graph', path, revision_id)
 
312
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
313
            raise errors.UnexpectedSmartServerResponse(response[0])
 
314
        if response[0][0] == 'ok':
 
315
            coded = response[1].read_body_bytes()
 
316
            if coded == '':
 
317
                # no revisions in this repository!
 
318
                return {}
 
319
            lines = coded.split('\n')
 
320
            revision_graph = {}
 
321
            for line in lines:
 
322
                d = tuple(line.split())
 
323
                revision_graph[d[0]] = d[1:]
 
324
                
 
325
            return revision_graph
 
326
        else:
 
327
            response_body = response[1].read_body_bytes()
 
328
            assert response_body == ''
 
329
            raise NoSuchRevision(self, revision_id)
424
330
 
425
331
    def has_revision(self, revision_id):
426
332
        """See Repository.has_revision()."""
427
 
        if revision_id == NULL_REVISION:
 
333
        if revision_id is None:
428
334
            # The null revision is always present.
429
335
            return True
430
336
        path = self.bzrdir._path_for_remote_call(self._client)
431
 
        response = self._client.call(
432
 
            'Repository.has_revision', path, revision_id)
433
 
        if response[0] not in ('yes', 'no'):
434
 
            raise errors.UnexpectedSmartServerResponse(response)
 
337
        response = self._client.call('Repository.has_revision', path, revision_id)
 
338
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
435
339
        return response[0] == 'yes'
436
340
 
437
 
    def has_revisions(self, revision_ids):
438
 
        """See Repository.has_revisions()."""
439
 
        result = set()
440
 
        for revision_id in revision_ids:
441
 
            if self.has_revision(revision_id):
442
 
                result.add(revision_id)
443
 
        return result
444
 
 
445
341
    def has_same_location(self, other):
446
342
        return (self.__class__ == other.__class__ and
447
343
                self.bzrdir.transport.base == other.bzrdir.transport.base)
448
344
        
449
345
    def get_graph(self, other_repository=None):
450
346
        """Return the graph for this repository format"""
451
 
        parents_provider = self
452
 
        if (other_repository is not None and
453
 
            other_repository.bzrdir.transport.base !=
454
 
            self.bzrdir.transport.base):
455
 
            parents_provider = graph._StackedParentsProvider(
456
 
                [parents_provider, other_repository._make_parents_provider()])
457
 
        return graph.Graph(parents_provider)
 
347
        return self._real_repository.get_graph(other_repository)
458
348
 
459
349
    def gather_stats(self, revid=None, committers=None):
460
350
        """See Repository.gather_stats()."""
461
351
        path = self.bzrdir._path_for_remote_call(self._client)
462
 
        # revid can be None to indicate no revisions, not just NULL_REVISION
463
 
        if revid is None or revision.is_null(revid):
 
352
        if revid in (None, NULL_REVISION):
464
353
            fmt_revid = ''
465
354
        else:
466
355
            fmt_revid = revid
468
357
            fmt_committers = 'no'
469
358
        else:
470
359
            fmt_committers = 'yes'
471
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
360
        response = self._client.call_expecting_body(
472
361
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
473
 
        if response_tuple[0] != 'ok':
474
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
362
        assert response[0][0] == 'ok', \
 
363
            'unexpected response code %s' % (response[0],)
475
364
 
476
 
        body = response_handler.read_body_bytes()
 
365
        body = response[1].read_body_bytes()
477
366
        result = {}
478
367
        for line in body.split('\n'):
479
368
            if not line:
487
376
 
488
377
        return result
489
378
 
490
 
    def find_branches(self, using=False):
491
 
        """See Repository.find_branches()."""
492
 
        # should be an API call to the server.
493
 
        self._ensure_real()
494
 
        return self._real_repository.find_branches(using=using)
495
 
 
496
379
    def get_physical_lock_status(self):
497
380
        """See Repository.get_physical_lock_status()."""
498
 
        # should be an API call to the server.
499
 
        self._ensure_real()
500
 
        return self._real_repository.get_physical_lock_status()
 
381
        return False
501
382
 
502
383
    def is_in_write_group(self):
503
384
        """Return True if there is an open write group.
514
395
        """See Repository.is_shared()."""
515
396
        path = self.bzrdir._path_for_remote_call(self._client)
516
397
        response = self._client.call('Repository.is_shared', path)
517
 
        if response[0] not in ('yes', 'no'):
518
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
398
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
519
399
        return response[0] == 'yes'
520
400
 
521
 
    def is_write_locked(self):
522
 
        return self._lock_mode == 'w'
523
 
 
524
401
    def lock_read(self):
525
402
        # wrong eventually - want a local lock cache context
526
403
        if not self._lock_mode:
527
404
            self._lock_mode = 'r'
528
405
            self._lock_count = 1
529
 
            self._parents_map = {}
530
 
            if 'hpss' in debug.debug_flags:
531
 
                self._requested_parents = set()
532
406
            if self._real_repository is not None:
533
407
                self._real_repository.lock_read()
534
408
        else:
538
412
        path = self.bzrdir._path_for_remote_call(self._client)
539
413
        if token is None:
540
414
            token = ''
541
 
        try:
542
 
            response = self._client.call('Repository.lock_write', path, token)
543
 
        except errors.ErrorFromSmartServer, err:
544
 
            self._translate_error(err, token=token)
545
 
 
 
415
        response = self._client.call('Repository.lock_write', path, token)
546
416
        if response[0] == 'ok':
547
417
            ok, token = response
548
418
            return token
 
419
        elif response[0] == 'LockContention':
 
420
            raise errors.LockContention('(remote lock)')
 
421
        elif response[0] == 'UnlockableTransport':
 
422
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
423
        elif response[0] == 'LockFailed':
 
424
            raise errors.LockFailed(response[1], response[2])
549
425
        else:
550
426
            raise errors.UnexpectedSmartServerResponse(response)
551
427
 
552
428
    def lock_write(self, token=None):
553
429
        if not self._lock_mode:
554
430
            self._lock_token = self._remote_lock_write(token)
555
 
            # if self._lock_token is None, then this is something like packs or
556
 
            # svn where we don't get to lock the repo, or a weave style repository
557
 
            # where we cannot lock it over the wire and attempts to do so will
558
 
            # fail.
 
431
            assert self._lock_token, 'Remote server did not return a token!'
559
432
            if self._real_repository is not None:
560
433
                self._real_repository.lock_write(token=self._lock_token)
561
434
            if token is not None:
564
437
                self._leave_lock = False
565
438
            self._lock_mode = 'w'
566
439
            self._lock_count = 1
567
 
            self._parents_map = {}
568
 
            if 'hpss' in debug.debug_flags:
569
 
                self._requested_parents = set()
570
440
        elif self._lock_mode == 'r':
571
441
            raise errors.ReadOnlyError(self)
572
442
        else:
573
443
            self._lock_count += 1
574
 
        return self._lock_token or None
 
444
        return self._lock_token
575
445
 
576
446
    def leave_lock_in_place(self):
577
 
        if not self._lock_token:
578
 
            raise NotImplementedError(self.leave_lock_in_place)
579
447
        self._leave_lock = True
580
448
 
581
449
    def dont_leave_lock_in_place(self):
582
 
        if not self._lock_token:
583
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
584
450
        self._leave_lock = False
585
451
 
586
452
    def _set_real_repository(self, repository):
589
455
        :param repository: The repository to fallback to for non-hpss
590
456
            implemented operations.
591
457
        """
592
 
        if isinstance(repository, RemoteRepository):
593
 
            raise AssertionError()
 
458
        assert not isinstance(repository, RemoteRepository)
594
459
        self._real_repository = repository
595
460
        if self._lock_mode == 'w':
596
461
            # if we are already locked, the real repository must be able to
612
477
 
613
478
    def _unlock(self, token):
614
479
        path = self.bzrdir._path_for_remote_call(self._client)
615
 
        if not token:
616
 
            # with no token the remote repository is not persistently locked.
617
 
            return
618
 
        try:
619
 
            response = self._client.call('Repository.unlock', path, token)
620
 
        except errors.ErrorFromSmartServer, err:
621
 
            self._translate_error(err, token=token)
 
480
        response = self._client.call('Repository.unlock', path, token)
622
481
        if response == ('ok',):
623
482
            return
 
483
        elif response[0] == 'TokenMismatch':
 
484
            raise errors.TokenMismatch(token, '(remote token)')
624
485
        else:
625
486
            raise errors.UnexpectedSmartServerResponse(response)
626
487
 
627
488
    def unlock(self):
 
489
        if self._lock_count == 1 and self._lock_mode == 'w':
 
490
            # don't unlock if inside a write group.
 
491
            if self.is_in_write_group():
 
492
                raise errors.BzrError(
 
493
                    'Must end write groups before releasing write locks.')
628
494
        self._lock_count -= 1
629
 
        if self._lock_count > 0:
630
 
            return
631
 
        self._parents_map = None
632
 
        if 'hpss' in debug.debug_flags:
633
 
            self._requested_parents = None
634
 
        old_mode = self._lock_mode
635
 
        self._lock_mode = None
636
 
        try:
637
 
            # The real repository is responsible at present for raising an
638
 
            # exception if it's in an unfinished write group.  However, it
639
 
            # normally will *not* actually remove the lock from disk - that's
640
 
            # done by the server on receiving the Repository.unlock call.
641
 
            # This is just to let the _real_repository stay up to date.
 
495
        if not self._lock_count:
 
496
            mode = self._lock_mode
 
497
            self._lock_mode = None
642
498
            if self._real_repository is not None:
643
499
                self._real_repository.unlock()
644
 
        finally:
645
 
            # The rpc-level lock should be released even if there was a
646
 
            # problem releasing the vfs-based lock.
647
 
            if old_mode == 'w':
 
500
            if mode != 'w':
648
501
                # Only write-locked repositories need to make a remote method
649
502
                # call to perfom the unlock.
650
 
                old_token = self._lock_token
651
 
                self._lock_token = None
652
 
                if not self._leave_lock:
653
 
                    self._unlock(old_token)
 
503
                return
 
504
            assert self._lock_token, 'Locked, but no token!'
 
505
            token = self._lock_token
 
506
            self._lock_token = None
 
507
            if not self._leave_lock:
 
508
                self._unlock(token)
654
509
 
655
510
    def break_lock(self):
656
511
        # should hand off to the network
664
519
        """
665
520
        import tempfile
666
521
        path = self.bzrdir._path_for_remote_call(self._client)
667
 
        try:
668
 
            response, protocol = self._client.call_expecting_body(
669
 
                'Repository.tarball', path, compression)
670
 
        except errors.UnknownSmartMethod:
671
 
            protocol.cancel_read_body()
672
 
            return None
 
522
        response, protocol = self._client.call_expecting_body(
 
523
            'Repository.tarball', path, compression)
673
524
        if response[0] == 'ok':
674
525
            # Extract the tarball and return it
675
526
            t = tempfile.NamedTemporaryFile()
677
528
            t.write(protocol.read_body_bytes())
678
529
            t.seek(0)
679
530
            return t
 
531
        if (response == ('error', "Generic bzr smart protocol error: "
 
532
                "bad request 'Repository.tarball'") or
 
533
              response == ('error', "Generic bzr smart protocol error: "
 
534
                "bad request u'Repository.tarball'")):
 
535
            protocol.cancel_read_body()
 
536
            return None
680
537
        raise errors.UnexpectedSmartServerResponse(response)
681
538
 
682
539
    def sprout(self, to_bzrdir, revision_id=None):
683
540
        # TODO: Option to control what format is created?
684
 
        self._ensure_real()
685
 
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
686
 
                                                             shared=False)
687
 
        dest_repo.fetch(self, revision_id=revision_id)
688
 
        return dest_repo
 
541
        to_repo = self._copy_repository_tarball(to_bzrdir, revision_id)
 
542
        if to_repo is None:
 
543
            self._ensure_real()
 
544
            return self._real_repository.sprout(
 
545
                to_bzrdir, revision_id=revision_id)
 
546
        else:
 
547
            return to_repo
689
548
 
690
549
    ### These methods are just thin shims to the VFS object for now.
691
550
 
706
565
        builder = self._real_repository.get_commit_builder(branch, parents,
707
566
                config, timestamp=timestamp, timezone=timezone,
708
567
                committer=committer, revprops=revprops, revision_id=revision_id)
 
568
        # Make the builder use this RemoteRepository rather than the real one.
 
569
        builder.repository = self
709
570
        return builder
710
571
 
711
 
    def add_fallback_repository(self, repository):
712
 
        """Add a repository to use for looking up data not held locally.
713
 
        
714
 
        :param repository: A repository.
715
 
        """
716
 
        if not self._format.supports_external_lookups:
717
 
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
718
 
        # We need to accumulate additional repositories here, to pass them in
719
 
        # on various RPC's.
720
 
        self._fallback_repositories.append(repository)
721
 
 
 
572
    @needs_write_lock
722
573
    def add_inventory(self, revid, inv, parents):
723
574
        self._ensure_real()
724
575
        return self._real_repository.add_inventory(revid, inv, parents)
725
576
 
 
577
    @needs_write_lock
726
578
    def add_revision(self, rev_id, rev, inv=None, config=None):
727
579
        self._ensure_real()
728
580
        return self._real_repository.add_revision(
733
585
        self._ensure_real()
734
586
        return self._real_repository.get_inventory(revision_id)
735
587
 
736
 
    def iter_inventories(self, revision_ids):
737
 
        self._ensure_real()
738
 
        return self._real_repository.iter_inventories(revision_ids)
739
 
 
740
588
    @needs_read_lock
741
589
    def get_revision(self, revision_id):
742
590
        self._ensure_real()
743
591
        return self._real_repository.get_revision(revision_id)
744
592
 
 
593
    @property
 
594
    def weave_store(self):
 
595
        self._ensure_real()
 
596
        return self._real_repository.weave_store
 
597
 
745
598
    def get_transaction(self):
746
599
        self._ensure_real()
747
600
        return self._real_repository.get_transaction()
752
605
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
753
606
 
754
607
    def make_working_trees(self):
755
 
        """See Repository.make_working_trees"""
756
 
        self._ensure_real()
757
 
        return self._real_repository.make_working_trees()
758
 
 
759
 
    def revision_ids_to_search_result(self, result_set):
760
 
        """Convert a set of revision ids to a graph SearchResult."""
761
 
        result_parents = set()
762
 
        for parents in self.get_graph().get_parent_map(
763
 
            result_set).itervalues():
764
 
            result_parents.update(parents)
765
 
        included_keys = result_set.intersection(result_parents)
766
 
        start_keys = result_set.difference(included_keys)
767
 
        exclude_keys = result_parents.difference(result_set)
768
 
        result = graph.SearchResult(start_keys, exclude_keys,
769
 
            len(result_set), result_set)
770
 
        return result
771
 
 
772
 
    @needs_read_lock
773
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
774
 
        """Return the revision ids that other has that this does not.
775
 
        
776
 
        These are returned in topological order.
777
 
 
778
 
        revision_id: only return revision ids included by revision_id.
779
 
        """
780
 
        return repository.InterRepository.get(
781
 
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
608
        """RemoteRepositories never create working trees by default."""
 
609
        return False
782
610
 
783
611
    def fetch(self, source, revision_id=None, pb=None):
784
612
        if self.has_same_location(source):
785
613
            # check that last_revision is in 'from' and then return a
786
614
            # no-operation.
787
615
            if (revision_id is not None and
788
 
                not revision.is_null(revision_id)):
 
616
                not _mod_revision.is_null(revision_id)):
789
617
                self.get_revision(revision_id)
790
618
            return 0, []
791
619
        self._ensure_real()
796
624
        self._ensure_real()
797
625
        self._real_repository.create_bundle(target, base, fileobj, format)
798
626
 
 
627
    @property
 
628
    def control_weaves(self):
 
629
        self._ensure_real()
 
630
        return self._real_repository.control_weaves
 
631
 
799
632
    @needs_read_lock
800
633
    def get_ancestry(self, revision_id, topo_sorted=True):
801
634
        self._ensure_real()
802
635
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
803
636
 
 
637
    @needs_read_lock
 
638
    def get_inventory_weave(self):
 
639
        self._ensure_real()
 
640
        return self._real_repository.get_inventory_weave()
 
641
 
804
642
    def fileids_altered_by_revision_ids(self, revision_ids):
805
643
        self._ensure_real()
806
644
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
807
645
 
808
 
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
809
 
        self._ensure_real()
810
 
        return self._real_repository._get_versioned_file_checker(
811
 
            revisions, revision_versions_cache)
812
 
        
813
646
    def iter_files_bytes(self, desired_files):
814
647
        """See Repository.iter_file_bytes.
815
648
        """
816
649
        self._ensure_real()
817
650
        return self._real_repository.iter_files_bytes(desired_files)
818
651
 
819
 
    @property
820
 
    def _fetch_order(self):
821
 
        """Decorate the real repository for now.
822
 
 
823
 
        In the long term getting this back from the remote repository as part
824
 
        of open would be more efficient.
825
 
        """
826
 
        self._ensure_real()
827
 
        return self._real_repository._fetch_order
828
 
 
829
 
    @property
830
 
    def _fetch_uses_deltas(self):
831
 
        """Decorate the real repository for now.
832
 
 
833
 
        In the long term getting this back from the remote repository as part
834
 
        of open would be more efficient.
835
 
        """
836
 
        self._ensure_real()
837
 
        return self._real_repository._fetch_uses_deltas
838
 
 
839
 
    @property
840
 
    def _fetch_reconcile(self):
841
 
        """Decorate the real repository for now.
842
 
 
843
 
        In the long term getting this back from the remote repository as part
844
 
        of open would be more efficient.
845
 
        """
846
 
        self._ensure_real()
847
 
        return self._real_repository._fetch_reconcile
848
 
 
849
 
    def get_parent_map(self, keys):
850
 
        """See bzrlib.Graph.get_parent_map()."""
851
 
        # Hack to build up the caching logic.
852
 
        ancestry = self._parents_map
853
 
        if ancestry is None:
854
 
            # Repository is not locked, so there's no cache.
855
 
            missing_revisions = set(keys)
856
 
            ancestry = {}
857
 
        else:
858
 
            missing_revisions = set(key for key in keys if key not in ancestry)
859
 
        if missing_revisions:
860
 
            parent_map = self._get_parent_map(missing_revisions)
861
 
            if 'hpss' in debug.debug_flags:
862
 
                mutter('retransmitted revisions: %d of %d',
863
 
                        len(set(ancestry).intersection(parent_map)),
864
 
                        len(parent_map))
865
 
            ancestry.update(parent_map)
866
 
        present_keys = [k for k in keys if k in ancestry]
867
 
        if 'hpss' in debug.debug_flags:
868
 
            if self._requested_parents is not None and len(ancestry) != 0:
869
 
                self._requested_parents.update(present_keys)
870
 
                mutter('Current RemoteRepository graph hit rate: %d%%',
871
 
                    100.0 * len(self._requested_parents) / len(ancestry))
872
 
        return dict((k, ancestry[k]) for k in present_keys)
873
 
 
874
 
    def _get_parent_map(self, keys):
875
 
        """Helper for get_parent_map that performs the RPC."""
876
 
        medium = self._client._medium
877
 
        if medium._is_remote_before((1, 2)):
878
 
            # We already found out that the server can't understand
879
 
            # Repository.get_parent_map requests, so just fetch the whole
880
 
            # graph.
881
 
            # XXX: Note that this will issue a deprecation warning. This is ok
882
 
            # :- its because we're working with a deprecated server anyway, and
883
 
            # the user will almost certainly have seen a warning about the
884
 
            # server version already.
885
 
            rg = self.get_revision_graph()
886
 
            # There is an api discrepency between get_parent_map and
887
 
            # get_revision_graph. Specifically, a "key:()" pair in
888
 
            # get_revision_graph just means a node has no parents. For
889
 
            # "get_parent_map" it means the node is a ghost. So fix up the
890
 
            # graph to correct this.
891
 
            #   https://bugs.launchpad.net/bzr/+bug/214894
892
 
            # There is one other "bug" which is that ghosts in
893
 
            # get_revision_graph() are not returned at all. But we won't worry
894
 
            # about that for now.
895
 
            for node_id, parent_ids in rg.iteritems():
896
 
                if parent_ids == ():
897
 
                    rg[node_id] = (NULL_REVISION,)
898
 
            rg[NULL_REVISION] = ()
899
 
            return rg
900
 
 
901
 
        keys = set(keys)
902
 
        if None in keys:
903
 
            raise ValueError('get_parent_map(None) is not valid')
904
 
        if NULL_REVISION in keys:
905
 
            keys.discard(NULL_REVISION)
906
 
            found_parents = {NULL_REVISION:()}
907
 
            if not keys:
908
 
                return found_parents
909
 
        else:
910
 
            found_parents = {}
911
 
        # TODO(Needs analysis): We could assume that the keys being requested
912
 
        # from get_parent_map are in a breadth first search, so typically they
913
 
        # will all be depth N from some common parent, and we don't have to
914
 
        # have the server iterate from the root parent, but rather from the
915
 
        # keys we're searching; and just tell the server the keyspace we
916
 
        # already have; but this may be more traffic again.
917
 
 
918
 
        # Transform self._parents_map into a search request recipe.
919
 
        # TODO: Manage this incrementally to avoid covering the same path
920
 
        # repeatedly. (The server will have to on each request, but the less
921
 
        # work done the better).
922
 
        parents_map = self._parents_map
923
 
        if parents_map is None:
924
 
            # Repository is not locked, so there's no cache.
925
 
            parents_map = {}
926
 
        start_set = set(parents_map)
927
 
        result_parents = set()
928
 
        for parents in parents_map.itervalues():
929
 
            result_parents.update(parents)
930
 
        stop_keys = result_parents.difference(start_set)
931
 
        included_keys = start_set.intersection(result_parents)
932
 
        start_set.difference_update(included_keys)
933
 
        recipe = (start_set, stop_keys, len(parents_map))
934
 
        body = self._serialise_search_recipe(recipe)
935
 
        path = self.bzrdir._path_for_remote_call(self._client)
936
 
        for key in keys:
937
 
            if type(key) is not str:
938
 
                raise ValueError(
939
 
                    "key %r not a plain string" % (key,))
940
 
        verb = 'Repository.get_parent_map'
941
 
        args = (path,) + tuple(keys)
942
 
        try:
943
 
            response = self._client.call_with_body_bytes_expecting_body(
944
 
                verb, args, self._serialise_search_recipe(recipe))
945
 
        except errors.UnknownSmartMethod:
946
 
            # Server does not support this method, so get the whole graph.
947
 
            # Worse, we have to force a disconnection, because the server now
948
 
            # doesn't realise it has a body on the wire to consume, so the
949
 
            # only way to recover is to abandon the connection.
950
 
            warning(
951
 
                'Server is too old for fast get_parent_map, reconnecting.  '
952
 
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
953
 
            medium.disconnect()
954
 
            # To avoid having to disconnect repeatedly, we keep track of the
955
 
            # fact the server doesn't understand remote methods added in 1.2.
956
 
            medium._remember_remote_is_before((1, 2))
957
 
            return self.get_revision_graph(None)
958
 
        response_tuple, response_handler = response
959
 
        if response_tuple[0] not in ['ok']:
960
 
            response_handler.cancel_read_body()
961
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
962
 
        if response_tuple[0] == 'ok':
963
 
            coded = bz2.decompress(response_handler.read_body_bytes())
964
 
            if coded == '':
965
 
                # no revisions found
966
 
                return {}
967
 
            lines = coded.split('\n')
968
 
            revision_graph = {}
969
 
            for line in lines:
970
 
                d = tuple(line.split())
971
 
                if len(d) > 1:
972
 
                    revision_graph[d[0]] = d[1:]
973
 
                else:
974
 
                    # No parents - so give the Graph result (NULL_REVISION,).
975
 
                    revision_graph[d[0]] = (NULL_REVISION,)
976
 
            return revision_graph
977
 
 
978
652
    @needs_read_lock
979
653
    def get_signature_text(self, revision_id):
980
654
        self._ensure_real()
981
655
        return self._real_repository.get_signature_text(revision_id)
982
656
 
983
657
    @needs_read_lock
984
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
985
658
    def get_revision_graph_with_ghosts(self, revision_ids=None):
986
659
        self._ensure_real()
987
660
        return self._real_repository.get_revision_graph_with_ghosts(
1025
698
        return self._real_repository.get_revision_reconcile(revision_id)
1026
699
 
1027
700
    @needs_read_lock
1028
 
    def check(self, revision_ids=None):
 
701
    def check(self, revision_ids):
1029
702
        self._ensure_real()
1030
 
        return self._real_repository.check(revision_ids=revision_ids)
 
703
        return self._real_repository.check(revision_ids)
1031
704
 
1032
705
    def copy_content_into(self, destination, revision_id=None):
1033
706
        self._ensure_real()
1039
712
        # destination
1040
713
        from bzrlib import osutils
1041
714
        import tarfile
 
715
        import tempfile
 
716
        from StringIO import StringIO
1042
717
        # TODO: Maybe a progress bar while streaming the tarball?
1043
718
        note("Copying repository content as tarball...")
1044
719
        tar_file = self._get_tarball('bz2')
1048
723
        try:
1049
724
            tar = tarfile.open('repository', fileobj=tar_file,
1050
725
                mode='r|bz2')
1051
 
            tmpdir = osutils.mkdtemp()
 
726
            tmpdir = tempfile.mkdtemp()
1052
727
            try:
1053
728
                _extract_tar(tar, tmpdir)
1054
729
                tmp_bzrdir = BzrDir.open(tmpdir)
1062
737
        # TODO: Suggestion from john: using external tar is much faster than
1063
738
        # python's tarfile library, but it may not work on windows.
1064
739
 
1065
 
    @property
1066
 
    def inventories(self):
1067
 
        """Decorate the real repository for now.
1068
 
 
1069
 
        In the long term a full blown network facility is needed to
1070
 
        avoid creating a real repository object locally.
1071
 
        """
1072
 
        self._ensure_real()
1073
 
        return self._real_repository.inventories
1074
 
 
1075
740
    @needs_write_lock
1076
741
    def pack(self):
1077
742
        """Compress the data within the repository.
1081
746
        self._ensure_real()
1082
747
        return self._real_repository.pack()
1083
748
 
1084
 
    @property
1085
 
    def revisions(self):
1086
 
        """Decorate the real repository for now.
1087
 
 
1088
 
        In the short term this should become a real object to intercept graph
1089
 
        lookups.
1090
 
 
1091
 
        In the long term a full blown network facility is needed.
1092
 
        """
1093
 
        self._ensure_real()
1094
 
        return self._real_repository.revisions
1095
 
 
1096
749
    def set_make_working_trees(self, new_value):
1097
 
        self._ensure_real()
1098
 
        self._real_repository.set_make_working_trees(new_value)
1099
 
 
1100
 
    @property
1101
 
    def signatures(self):
1102
 
        """Decorate the real repository for now.
1103
 
 
1104
 
        In the long term a full blown network facility is needed to avoid
1105
 
        creating a real repository object locally.
1106
 
        """
1107
 
        self._ensure_real()
1108
 
        return self._real_repository.signatures
 
750
        raise NotImplementedError(self.set_make_working_trees)
1109
751
 
1110
752
    @needs_write_lock
1111
753
    def sign_revision(self, revision_id, gpg_strategy):
1112
754
        self._ensure_real()
1113
755
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
1114
756
 
1115
 
    @property
1116
 
    def texts(self):
1117
 
        """Decorate the real repository for now.
1118
 
 
1119
 
        In the long term a full blown network facility is needed to avoid
1120
 
        creating a real repository object locally.
1121
 
        """
1122
 
        self._ensure_real()
1123
 
        return self._real_repository.texts
1124
 
 
1125
757
    @needs_read_lock
1126
758
    def get_revisions(self, revision_ids):
1127
759
        self._ensure_real()
1145
777
        return self._real_repository.store_revision_signature(
1146
778
            gpg_strategy, plaintext, revision_id)
1147
779
 
1148
 
    def add_signature_text(self, revision_id, signature):
1149
 
        self._ensure_real()
1150
 
        return self._real_repository.add_signature_text(revision_id, signature)
1151
 
 
1152
780
    def has_signature_for_revision_id(self, revision_id):
1153
781
        self._ensure_real()
1154
782
        return self._real_repository.has_signature_for_revision_id(revision_id)
1155
783
 
1156
 
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1157
 
        self._ensure_real()
1158
 
        return self._real_repository.item_keys_introduced_by(revision_ids,
1159
 
            _files_pb=_files_pb)
1160
 
 
1161
 
    def revision_graph_can_have_wrong_parents(self):
1162
 
        # The answer depends on the remote repo format.
1163
 
        self._ensure_real()
1164
 
        return self._real_repository.revision_graph_can_have_wrong_parents()
1165
 
 
1166
 
    def _find_inconsistent_revision_parents(self):
1167
 
        self._ensure_real()
1168
 
        return self._real_repository._find_inconsistent_revision_parents()
1169
 
 
1170
 
    def _check_for_inconsistent_revision_parents(self):
1171
 
        self._ensure_real()
1172
 
        return self._real_repository._check_for_inconsistent_revision_parents()
1173
 
 
1174
 
    def _make_parents_provider(self):
1175
 
        return self
1176
 
 
1177
 
    def _serialise_search_recipe(self, recipe):
1178
 
        """Serialise a graph search recipe.
1179
 
 
1180
 
        :param recipe: A search recipe (start, stop, count).
1181
 
        :return: Serialised bytes.
1182
 
        """
1183
 
        start_keys = ' '.join(recipe[0])
1184
 
        stop_keys = ' '.join(recipe[1])
1185
 
        count = str(recipe[2])
1186
 
        return '\n'.join((start_keys, stop_keys, count))
1187
 
 
1188
784
 
1189
785
class RemoteBranchLockableFiles(LockableFiles):
1190
786
    """A 'LockableFiles' implementation that talks to a smart server.
1205
801
        self._dir_mode = None
1206
802
        self._file_mode = None
1207
803
 
 
804
    def get(self, path):
 
805
        """'get' a remote path as per the LockableFiles interface.
 
806
 
 
807
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
808
             just retrieve a file, instead we ask the smart server to generate
 
809
             a configuration for us - which is retrieved as an INI file.
 
810
        """
 
811
        if path == 'branch.conf':
 
812
            path = self.bzrdir._path_for_remote_call(self._client)
 
813
            response = self._client.call_expecting_body(
 
814
                'Branch.get_config_file', path)
 
815
            assert response[0][0] == 'ok', \
 
816
                'unexpected response code %s' % (response[0],)
 
817
            return StringIO(response[1].read_body_bytes())
 
818
        else:
 
819
            # VFS fallback.
 
820
            return LockableFiles.get(self, path)
 
821
 
1208
822
 
1209
823
class RemoteBranchFormat(branch.BranchFormat):
1210
824
 
1219
833
        return 'Remote BZR Branch'
1220
834
 
1221
835
    def open(self, a_bzrdir):
 
836
        assert isinstance(a_bzrdir, RemoteBzrDir)
1222
837
        return a_bzrdir.open_branch()
1223
838
 
1224
839
    def initialize(self, a_bzrdir):
 
840
        assert isinstance(a_bzrdir, RemoteBzrDir)
1225
841
        return a_bzrdir.create_branch()
1226
842
 
1227
843
    def supports_tags(self):
1247
863
        # We intentionally don't call the parent class's __init__, because it
1248
864
        # will try to assign to self.tags, which is a property in this subclass.
1249
865
        # And the parent's __init__ doesn't do much anyway.
1250
 
        self._revision_id_to_revno_cache = None
1251
866
        self._revision_history_cache = None
1252
 
        self._last_revision_info_cache = None
1253
867
        self.bzrdir = remote_bzrdir
1254
868
        if _client is not None:
1255
869
            self._client = _client
1256
870
        else:
1257
 
            self._client = remote_bzrdir._client
 
871
            self._client = client._SmartClient(self.bzrdir._shared_medium)
1258
872
        self.repository = remote_repository
1259
873
        if real_branch is not None:
1260
874
            self._real_branch = real_branch
1274
888
        self._control_files = None
1275
889
        self._lock_mode = None
1276
890
        self._lock_token = None
1277
 
        self._repo_lock_token = None
1278
891
        self._lock_count = 0
1279
892
        self._leave_lock = False
1280
893
 
1281
 
    def _get_real_transport(self):
1282
 
        # if we try vfs access, return the real branch's vfs transport
1283
 
        self._ensure_real()
1284
 
        return self._real_branch._transport
1285
 
 
1286
 
    _transport = property(_get_real_transport)
1287
 
 
1288
894
    def __str__(self):
1289
895
        return "%s(%s)" % (self.__class__.__name__, self.base)
1290
896
 
1295
901
 
1296
902
        Used before calls to self._real_branch.
1297
903
        """
1298
 
        if self._real_branch is None:
1299
 
            if not vfs.vfs_enabled():
1300
 
                raise AssertionError('smart server vfs must be enabled '
1301
 
                    'to use vfs implementation')
 
904
        if not self._real_branch:
 
905
            assert vfs.vfs_enabled()
1302
906
            self.bzrdir._ensure_real()
1303
907
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1304
908
            # Give the remote repository the matching real repo.
1313
917
            if self._lock_mode == 'r':
1314
918
                self._real_branch.lock_read()
1315
919
 
1316
 
    def _translate_error(self, err, **context):
1317
 
        self.repository._translate_error(err, branch=self, **context)
1318
 
 
1319
 
    def _clear_cached_state(self):
1320
 
        super(RemoteBranch, self)._clear_cached_state()
1321
 
        if self._real_branch is not None:
1322
 
            self._real_branch._clear_cached_state()
1323
 
 
1324
 
    def _clear_cached_state_of_remote_branch_only(self):
1325
 
        """Like _clear_cached_state, but doesn't clear the cache of
1326
 
        self._real_branch.
1327
 
 
1328
 
        This is useful when falling back to calling a method of
1329
 
        self._real_branch that changes state.  In that case the underlying
1330
 
        branch changes, so we need to invalidate this RemoteBranch's cache of
1331
 
        it.  However, there's no need to invalidate the _real_branch's cache
1332
 
        too, in fact doing so might harm performance.
1333
 
        """
1334
 
        super(RemoteBranch, self)._clear_cached_state()
1335
 
        
1336
920
    @property
1337
921
    def control_files(self):
1338
922
        # Defer actually creating RemoteBranchLockableFiles until its needed,
1352
936
        self._ensure_real()
1353
937
        return self._real_branch.get_physical_lock_status()
1354
938
 
1355
 
    def get_stacked_on_url(self):
1356
 
        """Get the URL this branch is stacked against.
1357
 
 
1358
 
        :raises NotStacked: If the branch is not stacked.
1359
 
        :raises UnstackableBranchFormat: If the branch does not support
1360
 
            stacking.
1361
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1362
 
            stacking.
1363
 
        """
1364
 
        self._ensure_real()
1365
 
        return self._real_branch.get_stacked_on_url()
1366
 
 
1367
939
    def lock_read(self):
1368
940
        if not self._lock_mode:
1369
941
            self._lock_mode = 'r'
1381
953
            repo_token = self.repository.lock_write()
1382
954
            self.repository.unlock()
1383
955
        path = self.bzrdir._path_for_remote_call(self._client)
1384
 
        try:
1385
 
            response = self._client.call(
1386
 
                'Branch.lock_write', path, branch_token, repo_token or '')
1387
 
        except errors.ErrorFromSmartServer, err:
1388
 
            self._translate_error(err, token=token)
1389
 
        if response[0] != 'ok':
 
956
        response = self._client.call('Branch.lock_write', path, branch_token,
 
957
                                     repo_token)
 
958
        if response[0] == 'ok':
 
959
            ok, branch_token, repo_token = response
 
960
            return branch_token, repo_token
 
961
        elif response[0] == 'LockContention':
 
962
            raise errors.LockContention('(remote lock)')
 
963
        elif response[0] == 'TokenMismatch':
 
964
            raise errors.TokenMismatch(token, '(remote token)')
 
965
        elif response[0] == 'UnlockableTransport':
 
966
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
967
        elif response[0] == 'ReadOnlyError':
 
968
            raise errors.ReadOnlyError(self)
 
969
        elif response[0] == 'LockFailed':
 
970
            raise errors.LockFailed(response[1], response[2])
 
971
        else:
1390
972
            raise errors.UnexpectedSmartServerResponse(response)
1391
 
        ok, branch_token, repo_token = response
1392
 
        return branch_token, repo_token
1393
973
            
1394
974
    def lock_write(self, token=None):
1395
975
        if not self._lock_mode:
1396
976
            remote_tokens = self._remote_lock_write(token)
1397
977
            self._lock_token, self._repo_lock_token = remote_tokens
1398
 
            if not self._lock_token:
1399
 
                raise SmartProtocolError('Remote server did not return a token!')
 
978
            assert self._lock_token, 'Remote server did not return a token!'
1400
979
            # TODO: We really, really, really don't want to call _ensure_real
1401
980
            # here, but it's the easiest way to ensure coherency between the
1402
981
            # state of the RemoteBranch and RemoteRepository objects and the
1428
1007
                if token != self._lock_token:
1429
1008
                    raise errors.TokenMismatch(token, self._lock_token)
1430
1009
            self._lock_count += 1
1431
 
        return self._lock_token or None
 
1010
        return self._lock_token
1432
1011
 
1433
1012
    def _unlock(self, branch_token, repo_token):
1434
1013
        path = self.bzrdir._path_for_remote_call(self._client)
1435
 
        try:
1436
 
            response = self._client.call('Branch.unlock', path, branch_token,
1437
 
                                         repo_token or '')
1438
 
        except errors.ErrorFromSmartServer, err:
1439
 
            self._translate_error(err, token=str((branch_token, repo_token)))
 
1014
        response = self._client.call('Branch.unlock', path, branch_token,
 
1015
                                     repo_token)
1440
1016
        if response == ('ok',):
1441
1017
            return
1442
 
        raise errors.UnexpectedSmartServerResponse(response)
 
1018
        elif response[0] == 'TokenMismatch':
 
1019
            raise errors.TokenMismatch(
 
1020
                str((branch_token, repo_token)), '(remote tokens)')
 
1021
        else:
 
1022
            raise errors.UnexpectedSmartServerResponse(response)
1443
1023
 
1444
1024
    def unlock(self):
1445
1025
        self._lock_count -= 1
1448
1028
            mode = self._lock_mode
1449
1029
            self._lock_mode = None
1450
1030
            if self._real_branch is not None:
1451
 
                if (not self._leave_lock and mode == 'w' and
1452
 
                    self._repo_lock_token):
 
1031
                if not self._leave_lock:
1453
1032
                    # If this RemoteBranch will remove the physical lock for the
1454
1033
                    # repository, make sure the _real_branch doesn't do it
1455
1034
                    # first.  (Because the _real_branch's repository is set to
1460
1039
                # Only write-locked branched need to make a remote method call
1461
1040
                # to perfom the unlock.
1462
1041
                return
1463
 
            if not self._lock_token:
1464
 
                raise AssertionError('Locked, but no token!')
 
1042
            assert self._lock_token, 'Locked, but no token!'
1465
1043
            branch_token = self._lock_token
1466
1044
            repo_token = self._repo_lock_token
1467
1045
            self._lock_token = None
1474
1052
        return self._real_branch.break_lock()
1475
1053
 
1476
1054
    def leave_lock_in_place(self):
1477
 
        if not self._lock_token:
1478
 
            raise NotImplementedError(self.leave_lock_in_place)
1479
1055
        self._leave_lock = True
1480
1056
 
1481
1057
    def dont_leave_lock_in_place(self):
1482
 
        if not self._lock_token:
1483
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
1484
1058
        self._leave_lock = False
1485
1059
 
1486
 
    def _last_revision_info(self):
 
1060
    def last_revision_info(self):
 
1061
        """See Branch.last_revision_info()."""
1487
1062
        path = self.bzrdir._path_for_remote_call(self._client)
1488
1063
        response = self._client.call('Branch.last_revision_info', path)
1489
 
        if response[0] != 'ok':
1490
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1064
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1491
1065
        revno = int(response[1])
1492
1066
        last_revision = response[2]
1493
1067
        return (revno, last_revision)
1495
1069
    def _gen_revision_history(self):
1496
1070
        """See Branch._gen_revision_history()."""
1497
1071
        path = self.bzrdir._path_for_remote_call(self._client)
1498
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
1072
        response = self._client.call_expecting_body(
1499
1073
            'Branch.revision_history', path)
1500
 
        if response_tuple[0] != 'ok':
1501
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1502
 
        result = response_handler.read_body_bytes().split('\x00')
 
1074
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1075
                                        % (response[0],))
 
1076
        result = response[1].read_body_bytes().split('\x00')
1503
1077
        if result == ['']:
1504
1078
            return []
1505
1079
        return result
1506
1080
 
1507
 
    def _set_last_revision_descendant(self, revision_id, other_branch,
1508
 
            allow_diverged=False, allow_overwrite_descendant=False):
1509
 
        path = self.bzrdir._path_for_remote_call(self._client)
1510
 
        try:
1511
 
            response = self._client.call('Branch.set_last_revision_ex',
1512
 
                path, self._lock_token, self._repo_lock_token, revision_id,
1513
 
                int(allow_diverged), int(allow_overwrite_descendant))
1514
 
        except errors.ErrorFromSmartServer, err:
1515
 
            self._translate_error(err, other_branch=other_branch)
1516
 
        self._clear_cached_state()
1517
 
        if len(response) != 3 and response[0] != 'ok':
1518
 
            raise errors.UnexpectedSmartServerResponse(response)
1519
 
        new_revno, new_revision_id = response[1:]
1520
 
        self._last_revision_info_cache = new_revno, new_revision_id
1521
 
        self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1522
 
 
1523
 
    def _set_last_revision(self, revision_id):
1524
 
        path = self.bzrdir._path_for_remote_call(self._client)
1525
 
        self._clear_cached_state()
1526
 
        try:
1527
 
            response = self._client.call('Branch.set_last_revision',
1528
 
                path, self._lock_token, self._repo_lock_token, revision_id)
1529
 
        except errors.ErrorFromSmartServer, err:
1530
 
            self._translate_error(err)
1531
 
        if response != ('ok',):
1532
 
            raise errors.UnexpectedSmartServerResponse(response)
1533
 
 
1534
1081
    @needs_write_lock
1535
1082
    def set_revision_history(self, rev_history):
1536
1083
        # Send just the tip revision of the history; the server will generate
1537
1084
        # the full history from that.  If the revision doesn't exist in this
1538
1085
        # branch, NoSuchRevision will be raised.
 
1086
        path = self.bzrdir._path_for_remote_call(self._client)
1539
1087
        if rev_history == []:
1540
1088
            rev_id = 'null:'
1541
1089
        else:
1542
1090
            rev_id = rev_history[-1]
1543
 
        self._set_last_revision(rev_id)
 
1091
        self._clear_cached_state()
 
1092
        response = self._client.call('Branch.set_last_revision',
 
1093
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1094
        if response[0] == 'NoSuchRevision':
 
1095
            raise NoSuchRevision(self, rev_id)
 
1096
        else:
 
1097
            assert response == ('ok',), (
 
1098
                'unexpected response code %r' % (response,))
1544
1099
        self._cache_revision_history(rev_history)
1545
1100
 
1546
1101
    def get_parent(self):
1551
1106
        self._ensure_real()
1552
1107
        return self._real_branch.set_parent(url)
1553
1108
        
1554
 
    def set_stacked_on_url(self, stacked_location):
1555
 
        """Set the URL this branch is stacked against.
1556
 
 
1557
 
        :raises UnstackableBranchFormat: If the branch does not support
1558
 
            stacking.
1559
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1560
 
            stacking.
1561
 
        """
1562
 
        self._ensure_real()
1563
 
        return self._real_branch.set_stacked_on_url(stacked_location)
 
1109
    def get_config(self):
 
1110
        return RemoteBranchConfig(self)
1564
1111
 
1565
1112
    def sprout(self, to_bzrdir, revision_id=None):
1566
1113
        # Like Branch.sprout, except that it sprouts a branch in the default
1567
1114
        # format, because RemoteBranches can't be created at arbitrary URLs.
1568
1115
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1569
1116
        # to_bzrdir.create_branch...
1570
 
        self._ensure_real()
1571
 
        result = self._real_branch._format.initialize(to_bzrdir)
 
1117
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1572
1118
        self.copy_content_into(result, revision_id=revision_id)
1573
1119
        result.set_parent(self.bzrdir.root_transport.base)
1574
1120
        return result
1576
1122
    @needs_write_lock
1577
1123
    def pull(self, source, overwrite=False, stop_revision=None,
1578
1124
             **kwargs):
1579
 
        self._clear_cached_state_of_remote_branch_only()
 
1125
        # FIXME: This asks the real branch to run the hooks, which means
 
1126
        # they're called with the wrong target branch parameter. 
 
1127
        # The test suite specifically allows this at present but it should be
 
1128
        # fixed.  It should get a _override_hook_target branch,
 
1129
        # as push does.  -- mbp 20070405
1580
1130
        self._ensure_real()
1581
 
        return self._real_branch.pull(
 
1131
        self._real_branch.pull(
1582
1132
            source, overwrite=overwrite, stop_revision=stop_revision,
1583
 
            _override_hook_target=self, **kwargs)
 
1133
            **kwargs)
1584
1134
 
1585
1135
    @needs_read_lock
1586
1136
    def push(self, target, overwrite=False, stop_revision=None):
1592
1142
    def is_locked(self):
1593
1143
        return self._lock_count >= 1
1594
1144
 
1595
 
    @needs_read_lock
1596
 
    def revision_id_to_revno(self, revision_id):
1597
 
        self._ensure_real()
1598
 
        return self._real_branch.revision_id_to_revno(revision_id)
1599
 
 
1600
 
    @needs_write_lock
1601
1145
    def set_last_revision_info(self, revno, revision_id):
1602
 
        revision_id = ensure_null(revision_id)
1603
 
        path = self.bzrdir._path_for_remote_call(self._client)
1604
 
        try:
1605
 
            response = self._client.call('Branch.set_last_revision_info',
1606
 
                path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1607
 
        except errors.UnknownSmartMethod:
1608
 
            self._ensure_real()
1609
 
            self._clear_cached_state_of_remote_branch_only()
1610
 
            self._real_branch.set_last_revision_info(revno, revision_id)
1611
 
            self._last_revision_info_cache = revno, revision_id
1612
 
            return
1613
 
        except errors.ErrorFromSmartServer, err:
1614
 
            self._translate_error(err)
1615
 
        if response == ('ok',):
1616
 
            self._clear_cached_state()
1617
 
            self._last_revision_info_cache = revno, revision_id
1618
 
            # Update the _real_branch's cache too.
1619
 
            if self._real_branch is not None:
1620
 
                cache = self._last_revision_info_cache
1621
 
                self._real_branch._last_revision_info_cache = cache
1622
 
        else:
1623
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1146
        self._ensure_real()
 
1147
        self._clear_cached_state()
 
1148
        return self._real_branch.set_last_revision_info(revno, revision_id)
1624
1149
 
1625
 
    @needs_write_lock
1626
1150
    def generate_revision_history(self, revision_id, last_rev=None,
1627
1151
                                  other_branch=None):
1628
 
        medium = self._client._medium
1629
 
        if not medium._is_remote_before((1, 6)):
1630
 
            try:
1631
 
                self._set_last_revision_descendant(revision_id, other_branch,
1632
 
                    allow_diverged=True, allow_overwrite_descendant=True)
1633
 
                return
1634
 
            except errors.UnknownSmartMethod:
1635
 
                medium._remember_remote_is_before((1, 6))
1636
 
        self._clear_cached_state_of_remote_branch_only()
1637
1152
        self._ensure_real()
1638
 
        self._real_branch.generate_revision_history(
 
1153
        return self._real_branch.generate_revision_history(
1639
1154
            revision_id, last_rev=last_rev, other_branch=other_branch)
1640
1155
 
1641
1156
    @property
1647
1162
        self._ensure_real()
1648
1163
        return self._real_branch.set_push_location(location)
1649
1164
 
1650
 
    @needs_write_lock
1651
 
    def update_revisions(self, other, stop_revision=None, overwrite=False,
1652
 
                         graph=None):
1653
 
        """See Branch.update_revisions."""
1654
 
        other.lock_read()
1655
 
        try:
1656
 
            if stop_revision is None:
1657
 
                stop_revision = other.last_revision()
1658
 
                if revision.is_null(stop_revision):
1659
 
                    # if there are no commits, we're done.
1660
 
                    return
1661
 
            self.fetch(other, stop_revision)
1662
 
 
1663
 
            if overwrite:
1664
 
                # Just unconditionally set the new revision.  We don't care if
1665
 
                # the branches have diverged.
1666
 
                self._set_last_revision(stop_revision)
1667
 
            else:
1668
 
                medium = self._client._medium
1669
 
                if not medium._is_remote_before((1, 6)):
1670
 
                    try:
1671
 
                        self._set_last_revision_descendant(stop_revision, other)
1672
 
                        return
1673
 
                    except errors.UnknownSmartMethod:
1674
 
                        medium._remember_remote_is_before((1, 6))
1675
 
                # Fallback for pre-1.6 servers: check for divergence
1676
 
                # client-side, then do _set_last_revision.
1677
 
                last_rev = revision.ensure_null(self.last_revision())
1678
 
                if graph is None:
1679
 
                    graph = self.repository.get_graph()
1680
 
                if self._check_if_descendant_or_diverged(
1681
 
                        stop_revision, last_rev, graph, other):
1682
 
                    # stop_revision is a descendant of last_rev, but we aren't
1683
 
                    # overwriting, so we're done.
1684
 
                    return
1685
 
                self._set_last_revision(stop_revision)
1686
 
        finally:
1687
 
            other.unlock()
 
1165
    def update_revisions(self, other, stop_revision=None):
 
1166
        self._ensure_real()
 
1167
        return self._real_branch.update_revisions(
 
1168
            other, stop_revision=stop_revision)
 
1169
 
 
1170
 
 
1171
class RemoteBranchConfig(BranchConfig):
 
1172
 
 
1173
    def username(self):
 
1174
        self.branch._ensure_real()
 
1175
        return self.branch._real_branch.get_config().username()
 
1176
 
 
1177
    def _get_branch_data_config(self):
 
1178
        self.branch._ensure_real()
 
1179
        if self._branch_data_config is None:
 
1180
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1181
        return self._branch_data_config
1688
1182
 
1689
1183
 
1690
1184
def _extract_tar(tar, to_dir):
1694
1188
    """
1695
1189
    for tarinfo in tar:
1696
1190
        tar.extract(tarinfo, to_dir)
1697
 
 
1698
 
 
1699
 
def _translate_error(err, **context):
1700
 
    """Translate an ErrorFromSmartServer into a more useful error.
1701
 
 
1702
 
    Possible context keys:
1703
 
      - branch
1704
 
      - repository
1705
 
      - bzrdir
1706
 
      - token
1707
 
      - other_branch
1708
 
    """
1709
 
    def find(name):
1710
 
        try:
1711
 
            return context[name]
1712
 
        except KeyError, keyErr:
1713
 
            mutter('Missing key %r in context %r', keyErr.args[0], context)
1714
 
            raise err
1715
 
    if err.error_verb == 'NoSuchRevision':
1716
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
1717
 
    elif err.error_verb == 'nosuchrevision':
1718
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
1719
 
    elif err.error_tuple == ('nobranch',):
1720
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1721
 
    elif err.error_verb == 'norepository':
1722
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
1723
 
    elif err.error_verb == 'LockContention':
1724
 
        raise errors.LockContention('(remote lock)')
1725
 
    elif err.error_verb == 'UnlockableTransport':
1726
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
1727
 
    elif err.error_verb == 'LockFailed':
1728
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
1729
 
    elif err.error_verb == 'TokenMismatch':
1730
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
1731
 
    elif err.error_verb == 'Diverged':
1732
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
1733
 
    elif err.error_verb == 'TipChangeRejected':
1734
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1735
 
    raise
1736