~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-13 01:01:35 UTC
  • mfrom: (4137.1.1 jamesw-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090313010135-3huetgz68wxrhsb3
(kikuyo) Small improvements to the GNU ChangeLog formatter.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
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.
16
19
 
17
20
import bz2
18
21
 
19
22
from bzrlib import (
20
 
    bencode,
21
23
    branch,
22
24
    bzrdir,
23
 
    config,
24
 
    controldir,
25
25
    debug,
26
26
    errors,
27
27
    graph,
28
 
    lock,
29
28
    lockdir,
 
29
    pack,
30
30
    repository,
31
 
    repository as _mod_repository,
32
31
    revision,
33
 
    revision as _mod_revision,
34
 
    static_tuple,
35
32
    symbol_versioning,
 
33
    urlutils,
36
34
)
37
 
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
35
from bzrlib.branch import BranchReferenceFormat
38
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
39
 
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
40
38
from bzrlib.errors import (
41
39
    NoSuchRevision,
42
40
    SmartProtocolError,
44
42
from bzrlib.lockable_files import LockableFiles
45
43
from bzrlib.smart import client, vfs, repository as smart_repo
46
44
from bzrlib.revision import ensure_null, NULL_REVISION
47
 
from bzrlib.repository import RepositoryWriteLockResult
48
45
from bzrlib.trace import mutter, note, warning
 
46
from bzrlib.util import bencode
49
47
 
50
48
 
51
49
class _RpcHelper(object):
63
61
        except errors.ErrorFromSmartServer, err:
64
62
            self._translate_error(err, **err_context)
65
63
 
66
 
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
67
 
        try:
68
 
            return self._client.call_with_body_bytes(method, args, body_bytes)
69
 
        except errors.ErrorFromSmartServer, err:
70
 
            self._translate_error(err, **err_context)
71
 
 
72
64
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
65
                                             **err_context):
74
66
        try:
81
73
def response_tuple_to_repo_format(response):
82
74
    """Convert a response tuple describing a repository format to a format."""
83
75
    format = RemoteRepositoryFormat()
84
 
    format._rich_root_data = (response[0] == 'yes')
85
 
    format._supports_tree_reference = (response[1] == 'yes')
86
 
    format._supports_external_lookups = (response[2] == 'yes')
 
76
    format.rich_root_data = (response[0] == 'yes')
 
77
    format.supports_tree_reference = (response[1] == 'yes')
 
78
    format.supports_external_lookups = (response[2] == 'yes')
87
79
    format._network_name = response[3]
88
80
    return format
89
81
 
93
85
class RemoteBzrDir(BzrDir, _RpcHelper):
94
86
    """Control directory on a remote server, accessed via bzr:// or similar."""
95
87
 
96
 
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
88
    def __init__(self, transport, format, _client=None):
97
89
        """Construct a RemoteBzrDir.
98
90
 
99
91
        :param _client: Private parameter for testing. Disables probing and the
103
95
        # this object holds a delegated bzrdir that uses file-level operations
104
96
        # to talk to the other side
105
97
        self._real_bzrdir = None
106
 
        self._has_working_tree = None
107
98
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
108
99
        # create_branch for details.
109
100
        self._next_open_branch_result = None
113
104
            self._client = client._SmartClient(medium)
114
105
        else:
115
106
            self._client = _client
116
 
            if not _force_probe:
117
 
                return
118
 
 
119
 
        self._probe_bzrdir()
120
 
 
121
 
    def __repr__(self):
122
 
        return '%s(%r)' % (self.__class__.__name__, self._client)
123
 
 
124
 
    def _probe_bzrdir(self):
125
 
        medium = self._client._medium
 
107
            return
 
108
 
126
109
        path = self._path_for_remote_call(self._client)
127
 
        if medium._is_remote_before((2, 1)):
128
 
            self._rpc_open(path)
129
 
            return
130
 
        try:
131
 
            self._rpc_open_2_1(path)
132
 
            return
133
 
        except errors.UnknownSmartMethod:
134
 
            medium._remember_remote_is_before((2, 1))
135
 
            self._rpc_open(path)
136
 
 
137
 
    def _rpc_open_2_1(self, path):
138
 
        response = self._call('BzrDir.open_2.1', path)
139
 
        if response == ('no',):
140
 
            raise errors.NotBranchError(path=self.root_transport.base)
141
 
        elif response[0] == 'yes':
142
 
            if response[1] == 'yes':
143
 
                self._has_working_tree = True
144
 
            elif response[1] == 'no':
145
 
                self._has_working_tree = False
146
 
            else:
147
 
                raise errors.UnexpectedSmartServerResponse(response)
148
 
        else:
149
 
            raise errors.UnexpectedSmartServerResponse(response)
150
 
 
151
 
    def _rpc_open(self, path):
152
110
        response = self._call('BzrDir.open', path)
153
111
        if response not in [('yes',), ('no',)]:
154
112
            raise errors.UnexpectedSmartServerResponse(response)
155
113
        if response == ('no',):
156
 
            raise errors.NotBranchError(path=self.root_transport.base)
 
114
            raise errors.NotBranchError(path=transport.base)
157
115
 
158
116
    def _ensure_real(self):
159
117
        """Ensure that there is a _real_bzrdir set.
161
119
        Used before calls to self._real_bzrdir.
162
120
        """
163
121
        if not self._real_bzrdir:
164
 
            if 'hpssvfs' in debug.debug_flags:
165
 
                import traceback
166
 
                warning('VFS BzrDir access triggered\n%s',
167
 
                    ''.join(traceback.format_stack()))
168
122
            self._real_bzrdir = BzrDir.open_from_transport(
169
123
                self.root_transport, _server_formats=False)
170
124
            self._format._network_name = \
199
153
        except errors.UnknownSmartMethod:
200
154
            medium._remember_remote_is_before((1, 13))
201
155
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
202
 
        except errors.UnknownErrorFromSmartServer, err:
203
 
            if err.error_tuple != ('BranchReference',):
204
 
                raise
205
 
            # We need to resolve the branch reference to determine the
206
 
            # cloning_metadir.  This causes unnecessary RPCs to open the
207
 
            # referenced branch (and bzrdir, etc) but only when the caller
208
 
            # didn't already resolve the branch reference.
209
 
            referenced_branch = self.open_branch()
210
 
            return referenced_branch.bzrdir.cloning_metadir()
211
156
        if len(response) != 3:
212
157
            raise errors.UnexpectedSmartServerResponse(response)
213
158
        control_name, repo_name, branch_info = response
246
191
        self._ensure_real()
247
192
        self._real_bzrdir.destroy_repository()
248
193
 
249
 
    def create_branch(self, name=None):
 
194
    def create_branch(self):
250
195
        # as per meta1 formats - just delegate to the format object which may
251
196
        # be parameterised.
252
 
        real_branch = self._format.get_branch_format().initialize(self,
253
 
            name=name)
 
197
        real_branch = self._format.get_branch_format().initialize(self)
254
198
        if not isinstance(real_branch, RemoteBranch):
255
 
            result = RemoteBranch(self, self.find_repository(), real_branch,
256
 
                                  name=name)
 
199
            result = RemoteBranch(self, self.find_repository(), real_branch)
257
200
        else:
258
201
            result = real_branch
259
202
        # BzrDir.clone_on_transport() uses the result of create_branch but does
265
208
        self._next_open_branch_result = result
266
209
        return result
267
210
 
268
 
    def destroy_branch(self, name=None):
 
211
    def destroy_branch(self):
269
212
        """See BzrDir.destroy_branch"""
270
213
        self._ensure_real()
271
 
        self._real_bzrdir.destroy_branch(name=name)
 
214
        self._real_bzrdir.destroy_branch()
272
215
        self._next_open_branch_result = None
273
216
 
274
217
    def create_workingtree(self, revision_id=None, from_branch=None):
275
218
        raise errors.NotLocalUrl(self.transport.base)
276
219
 
277
 
    def find_branch_format(self, name=None):
 
220
    def find_branch_format(self):
278
221
        """Find the branch 'format' for this bzrdir.
279
222
 
280
223
        This might be a synthetic object for e.g. RemoteBranch and SVN.
281
224
        """
282
 
        b = self.open_branch(name=name)
 
225
        b = self.open_branch()
283
226
        return b._format
284
227
 
285
 
    def get_branch_reference(self, name=None):
 
228
    def get_branch_reference(self):
286
229
        """See BzrDir.get_branch_reference()."""
287
 
        if name is not None:
288
 
            # XXX JRV20100304: Support opening colocated branches
289
 
            raise errors.NoColocatedBranchSupport(self)
290
230
        response = self._get_branch_reference()
291
231
        if response[0] == 'ref':
292
232
            return response[1]
296
236
    def _get_branch_reference(self):
297
237
        path = self._path_for_remote_call(self._client)
298
238
        medium = self._client._medium
299
 
        candidate_calls = [
300
 
            ('BzrDir.open_branchV3', (2, 1)),
301
 
            ('BzrDir.open_branchV2', (1, 13)),
302
 
            ('BzrDir.open_branch', None),
303
 
            ]
304
 
        for verb, required_version in candidate_calls:
305
 
            if required_version and medium._is_remote_before(required_version):
306
 
                continue
 
239
        if not medium._is_remote_before((1, 13)):
307
240
            try:
308
 
                response = self._call(verb, path)
 
241
                response = self._call('BzrDir.open_branchV2', path)
 
242
                if response[0] not in ('ref', 'branch'):
 
243
                    raise errors.UnexpectedSmartServerResponse(response)
 
244
                return response
309
245
            except errors.UnknownSmartMethod:
310
 
                if required_version is None:
311
 
                    raise
312
 
                medium._remember_remote_is_before(required_version)
313
 
            else:
314
 
                break
315
 
        if verb == 'BzrDir.open_branch':
316
 
            if response[0] != 'ok':
317
 
                raise errors.UnexpectedSmartServerResponse(response)
318
 
            if response[1] != '':
319
 
                return ('ref', response[1])
320
 
            else:
321
 
                return ('branch', '')
322
 
        if response[0] not in ('ref', 'branch'):
 
246
                medium._remember_remote_is_before((1, 13))
 
247
        response = self._call('BzrDir.open_branch', path)
 
248
        if response[0] != 'ok':
323
249
            raise errors.UnexpectedSmartServerResponse(response)
324
 
        return response
 
250
        if response[1] != '':
 
251
            return ('ref', response[1])
 
252
        else:
 
253
            return ('branch', '')
325
254
 
326
 
    def _get_tree_branch(self, name=None):
 
255
    def _get_tree_branch(self):
327
256
        """See BzrDir._get_tree_branch()."""
328
 
        return None, self.open_branch(name=name)
 
257
        return None, self.open_branch()
329
258
 
330
 
    def open_branch(self, name=None, unsupported=False,
331
 
                    ignore_fallbacks=False):
332
 
        if unsupported:
 
259
    def open_branch(self, _unsupported=False):
 
260
        if _unsupported:
333
261
            raise NotImplementedError('unsupported flag support not implemented yet.')
334
262
        if self._next_open_branch_result is not None:
335
263
            # See create_branch for details.
340
268
        if response[0] == 'ref':
341
269
            # a branch reference, use the existing BranchReference logic.
342
270
            format = BranchReferenceFormat()
343
 
            return format.open(self, name=name, _found=True,
344
 
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
271
            return format.open(self, _found=True, location=response[1])
345
272
        branch_format_name = response[1]
346
273
        if not branch_format_name:
347
274
            branch_format_name = None
348
275
        format = RemoteBranchFormat(network_name=branch_format_name)
349
 
        return RemoteBranch(self, self.find_repository(), format=format,
350
 
            setup_stacking=not ignore_fallbacks, name=name)
 
276
        return RemoteBranch(self, self.find_repository(), format=format)
351
277
 
352
278
    def _open_repo_v1(self, path):
353
279
        verb = 'BzrDir.find_repository'
414
340
        else:
415
341
            raise errors.NoRepositoryPresent(self)
416
342
 
417
 
    def has_workingtree(self):
418
 
        if self._has_working_tree is None:
419
 
            self._ensure_real()
420
 
            self._has_working_tree = self._real_bzrdir.has_workingtree()
421
 
        return self._has_working_tree
422
 
 
423
343
    def open_workingtree(self, recommend_upgrade=True):
424
 
        if self.has_workingtree():
 
344
        self._ensure_real()
 
345
        if self._real_bzrdir.has_workingtree():
425
346
            raise errors.NotLocalUrl(self.root_transport)
426
347
        else:
427
348
            raise errors.NoWorkingTree(self.root_transport.base)
430
351
        """Return the path to be used for this bzrdir in a remote call."""
431
352
        return client.remote_path_from_transport(self.root_transport)
432
353
 
433
 
    def get_branch_transport(self, branch_format, name=None):
 
354
    def get_branch_transport(self, branch_format):
434
355
        self._ensure_real()
435
 
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
356
        return self._real_bzrdir.get_branch_transport(branch_format)
436
357
 
437
358
    def get_repository_transport(self, repository_format):
438
359
        self._ensure_real()
459
380
        return self._real_bzrdir.clone(url, revision_id=revision_id,
460
381
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
461
382
 
462
 
    def _get_config(self):
463
 
        return RemoteBzrDirConfig(self)
 
383
    def get_config(self):
 
384
        self._ensure_real()
 
385
        return self._real_bzrdir.get_config()
464
386
 
465
387
 
466
388
class RemoteRepositoryFormat(repository.RepositoryFormat):
490
412
        self._custom_format = None
491
413
        self._network_name = None
492
414
        self._creating_bzrdir = None
493
 
        self._supports_chks = None
494
 
        self._supports_external_lookups = None
495
 
        self._supports_tree_reference = None
496
 
        self._rich_root_data = None
497
 
 
498
 
    def __repr__(self):
499
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
500
 
            self._network_name)
501
 
 
502
 
    @property
503
 
    def fast_deltas(self):
504
 
        self._ensure_real()
505
 
        return self._custom_format.fast_deltas
506
 
 
507
 
    @property
508
 
    def rich_root_data(self):
509
 
        if self._rich_root_data is None:
510
 
            self._ensure_real()
511
 
            self._rich_root_data = self._custom_format.rich_root_data
512
 
        return self._rich_root_data
513
 
 
514
 
    @property
515
 
    def supports_chks(self):
516
 
        if self._supports_chks is None:
517
 
            self._ensure_real()
518
 
            self._supports_chks = self._custom_format.supports_chks
519
 
        return self._supports_chks
520
 
 
521
 
    @property
522
 
    def supports_external_lookups(self):
523
 
        if self._supports_external_lookups is None:
524
 
            self._ensure_real()
525
 
            self._supports_external_lookups = \
526
 
                self._custom_format.supports_external_lookups
527
 
        return self._supports_external_lookups
528
 
 
529
 
    @property
530
 
    def supports_tree_reference(self):
531
 
        if self._supports_tree_reference is None:
532
 
            self._ensure_real()
533
 
            self._supports_tree_reference = \
534
 
                self._custom_format.supports_tree_reference
535
 
        return self._supports_tree_reference
536
415
 
537
416
    def _vfs_initialize(self, a_bzrdir, shared):
538
417
        """Helper for common code in initialize."""
570
449
        # 1) get the network name to use.
571
450
        if self._custom_format:
572
451
            network_name = self._custom_format.network_name()
573
 
        elif self._network_name:
574
 
            network_name = self._network_name
575
452
        else:
576
453
            # Select the current bzrlib default and ask for that.
577
454
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
625
502
        return self._custom_format._fetch_reconcile
626
503
 
627
504
    def get_format_description(self):
628
 
        self._ensure_real()
629
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
505
        return 'bzr remote repository'
630
506
 
631
507
    def __eq__(self, other):
632
508
        return self.__class__ is other.__class__
633
509
 
 
510
    def check_conversion_target(self, target_format):
 
511
        if self.rich_root_data and not target_format.rich_root_data:
 
512
            raise errors.BadConversionTarget(
 
513
                'Does not support rich root data.', target_format)
 
514
        if (self.supports_tree_reference and
 
515
            not getattr(target_format, 'supports_tree_reference', False)):
 
516
            raise errors.BadConversionTarget(
 
517
                'Does not support nested trees', target_format)
 
518
 
634
519
    def network_name(self):
635
520
        if self._network_name:
636
521
            return self._network_name
638
523
        return self._creating_repo._real_repository._format.network_name()
639
524
 
640
525
    @property
641
 
    def pack_compresses(self):
642
 
        self._ensure_real()
643
 
        return self._custom_format.pack_compresses
644
 
 
645
 
    @property
646
526
    def _serializer(self):
647
527
        self._ensure_real()
648
528
        return self._custom_format._serializer
649
529
 
650
530
 
651
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
652
 
    controldir.ControlComponent):
 
531
class RemoteRepository(_RpcHelper):
653
532
    """Repository accessed over rpc.
654
533
 
655
534
    For the moment most operations are performed using local transport-backed
681
560
        self._lock_token = None
682
561
        self._lock_count = 0
683
562
        self._leave_lock = False
684
 
        # Cache of revision parents; misses are cached during read locks, and
685
 
        # write locks when no _real_repository has been set.
686
563
        self._unstacked_provider = graph.CachingParentsProvider(
687
564
            get_parent_map=self._get_parent_map_rpc)
688
565
        self._unstacked_provider.disable_cache()
698
575
        # Additional places to query for data.
699
576
        self._fallback_repositories = []
700
577
 
701
 
    @property
702
 
    def user_transport(self):
703
 
        return self.bzrdir.user_transport
704
 
 
705
 
    @property
706
 
    def control_transport(self):
707
 
        # XXX: Normally you shouldn't directly get at the remote repository
708
 
        # transport, but I'm not sure it's worth making this method
709
 
        # optional -- mbp 2010-04-21
710
 
        return self.bzrdir.get_repository_transport(None)
711
 
        
712
578
    def __str__(self):
713
579
        return "%s(%s)" % (self.__class__.__name__, self.base)
714
580
 
717
583
    def abort_write_group(self, suppress_errors=False):
718
584
        """Complete a write group on the decorated repository.
719
585
 
720
 
        Smart methods perform operations in a single step so this API
 
586
        Smart methods peform operations in a single step so this api
721
587
        is not really applicable except as a compatibility thunk
722
588
        for older plugins that don't use e.g. the CommitBuilder
723
589
        facility.
728
594
        return self._real_repository.abort_write_group(
729
595
            suppress_errors=suppress_errors)
730
596
 
731
 
    @property
732
 
    def chk_bytes(self):
733
 
        """Decorate the real repository for now.
734
 
 
735
 
        In the long term a full blown network facility is needed to avoid
736
 
        creating a real repository object locally.
737
 
        """
738
 
        self._ensure_real()
739
 
        return self._real_repository.chk_bytes
740
 
 
741
597
    def commit_write_group(self):
742
598
        """Complete a write group on the decorated repository.
743
599
 
744
 
        Smart methods perform operations in a single step so this API
 
600
        Smart methods peform operations in a single step so this api
745
601
        is not really applicable except as a compatibility thunk
746
602
        for older plugins that don't use e.g. the CommitBuilder
747
603
        facility.
757
613
        self._ensure_real()
758
614
        return self._real_repository.suspend_write_group()
759
615
 
760
 
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
761
 
        self._ensure_real()
762
 
        return self._real_repository.get_missing_parent_inventories(
763
 
            check_for_missing_texts=check_for_missing_texts)
764
 
 
765
 
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
766
 
        self._ensure_real()
767
 
        return self._real_repository.get_rev_id_for_revno(
768
 
            revno, known_pair)
769
 
 
770
 
    def get_rev_id_for_revno(self, revno, known_pair):
771
 
        """See Repository.get_rev_id_for_revno."""
772
 
        path = self.bzrdir._path_for_remote_call(self._client)
773
 
        try:
774
 
            if self._client._medium._is_remote_before((1, 17)):
775
 
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
776
 
            response = self._call(
777
 
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
778
 
        except errors.UnknownSmartMethod:
779
 
            self._client._medium._remember_remote_is_before((1, 17))
780
 
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
781
 
        if response[0] == 'ok':
782
 
            return True, response[1]
783
 
        elif response[0] == 'history-incomplete':
784
 
            known_pair = response[1:3]
785
 
            for fallback in self._fallback_repositories:
786
 
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
787
 
                if found:
788
 
                    return True, result
789
 
                else:
790
 
                    known_pair = result
791
 
            # Not found in any fallbacks
792
 
            return False, known_pair
793
 
        else:
794
 
            raise errors.UnexpectedSmartServerResponse(response)
795
 
 
796
616
    def _ensure_real(self):
797
617
        """Ensure that there is a _real_repository set.
798
618
 
799
619
        Used before calls to self._real_repository.
800
 
 
801
 
        Note that _ensure_real causes many roundtrips to the server which are
802
 
        not desirable, and prevents the use of smart one-roundtrip RPC's to
803
 
        perform complex operations (such as accessing parent data, streaming
804
 
        revisions etc). Adding calls to _ensure_real should only be done when
805
 
        bringing up new functionality, adding fallbacks for smart methods that
806
 
        require a fallback path, and never to replace an existing smart method
807
 
        invocation. If in doubt chat to the bzr network team.
808
620
        """
809
621
        if self._real_repository is None:
810
 
            if 'hpssvfs' in debug.debug_flags:
811
 
                import traceback
812
 
                warning('VFS Repository access triggered\n%s',
813
 
                    ''.join(traceback.format_stack()))
814
 
            self._unstacked_provider.missing_keys.clear()
815
622
            self.bzrdir._ensure_real()
816
623
            self._set_real_repository(
817
624
                self.bzrdir._real_bzrdir.open_repository())
844
651
        self._ensure_real()
845
652
        return self._real_repository._generate_text_key_index()
846
653
 
 
654
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
 
655
    def get_revision_graph(self, revision_id=None):
 
656
        """See Repository.get_revision_graph()."""
 
657
        return self._get_revision_graph(revision_id)
 
658
 
847
659
    def _get_revision_graph(self, revision_id):
848
660
        """Private method for using with old (< 1.2) servers to fallback."""
849
661
        if revision_id is None:
877
689
        """Return a source for streaming from this repository."""
878
690
        return RemoteStreamSource(self, to_format)
879
691
 
880
 
    @needs_read_lock
881
692
    def has_revision(self, revision_id):
882
 
        """True if this repository has a copy of the revision."""
883
 
        # Copy of bzrlib.repository.Repository.has_revision
884
 
        return revision_id in self.has_revisions((revision_id,))
 
693
        """See Repository.has_revision()."""
 
694
        if revision_id == NULL_REVISION:
 
695
            # The null revision is always present.
 
696
            return True
 
697
        path = self.bzrdir._path_for_remote_call(self._client)
 
698
        response = self._call('Repository.has_revision', path, revision_id)
 
699
        if response[0] not in ('yes', 'no'):
 
700
            raise errors.UnexpectedSmartServerResponse(response)
 
701
        if response[0] == 'yes':
 
702
            return True
 
703
        for fallback_repo in self._fallback_repositories:
 
704
            if fallback_repo.has_revision(revision_id):
 
705
                return True
 
706
        return False
885
707
 
886
 
    @needs_read_lock
887
708
    def has_revisions(self, revision_ids):
888
 
        """Probe to find out the presence of multiple revisions.
889
 
 
890
 
        :param revision_ids: An iterable of revision_ids.
891
 
        :return: A set of the revision_ids that were present.
892
 
        """
893
 
        # Copy of bzrlib.repository.Repository.has_revisions
894
 
        parent_map = self.get_parent_map(revision_ids)
895
 
        result = set(parent_map)
896
 
        if _mod_revision.NULL_REVISION in revision_ids:
897
 
            result.add(_mod_revision.NULL_REVISION)
 
709
        """See Repository.has_revisions()."""
 
710
        # FIXME: This does many roundtrips, particularly when there are
 
711
        # fallback repositories.  -- mbp 20080905
 
712
        result = set()
 
713
        for revision_id in revision_ids:
 
714
            if self.has_revision(revision_id):
 
715
                result.add(revision_id)
898
716
        return result
899
717
 
900
 
    def _has_same_fallbacks(self, other_repo):
901
 
        """Returns true if the repositories have the same fallbacks."""
902
 
        # XXX: copied from Repository; it should be unified into a base class
903
 
        # <https://bugs.launchpad.net/bzr/+bug/401622>
904
 
        my_fb = self._fallback_repositories
905
 
        other_fb = other_repo._fallback_repositories
906
 
        if len(my_fb) != len(other_fb):
907
 
            return False
908
 
        for f, g in zip(my_fb, other_fb):
909
 
            if not f.has_same_location(g):
910
 
                return False
911
 
        return True
912
 
 
913
718
    def has_same_location(self, other):
914
 
        # TODO: Move to RepositoryBase and unify with the regular Repository
915
 
        # one; unfortunately the tests rely on slightly different behaviour at
916
 
        # present -- mbp 20090710
917
719
        return (self.__class__ is other.__class__ and
918
720
                self.bzrdir.transport.base == other.bzrdir.transport.base)
919
721
 
922
724
        parents_provider = self._make_parents_provider(other_repository)
923
725
        return graph.Graph(parents_provider)
924
726
 
925
 
    @needs_read_lock
926
 
    def get_known_graph_ancestry(self, revision_ids):
927
 
        """Return the known graph for a set of revision ids and their ancestors.
928
 
        """
929
 
        st = static_tuple.StaticTuple
930
 
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
931
 
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
932
 
        return graph.GraphThunkIdsToKeys(known_graph)
933
 
 
934
727
    def gather_stats(self, revid=None, committers=None):
935
728
        """See Repository.gather_stats()."""
936
729
        path = self.bzrdir._path_for_remote_call(self._client)
996
789
    def is_write_locked(self):
997
790
        return self._lock_mode == 'w'
998
791
 
999
 
    def _warn_if_deprecated(self, branch=None):
1000
 
        # If we have a real repository, the check will be done there, if we
1001
 
        # don't the check will be done remotely.
1002
 
        pass
1003
 
 
1004
792
    def lock_read(self):
1005
 
        """Lock the repository for read operations.
1006
 
 
1007
 
        :return: A bzrlib.lock.LogicalLockResult.
1008
 
        """
1009
793
        # wrong eventually - want a local lock cache context
1010
794
        if not self._lock_mode:
1011
 
            self._note_lock('r')
1012
795
            self._lock_mode = 'r'
1013
796
            self._lock_count = 1
1014
 
            self._unstacked_provider.enable_cache(cache_misses=True)
 
797
            self._unstacked_provider.enable_cache(cache_misses=False)
1015
798
            if self._real_repository is not None:
1016
799
                self._real_repository.lock_read()
1017
 
            for repo in self._fallback_repositories:
1018
 
                repo.lock_read()
1019
800
        else:
1020
801
            self._lock_count += 1
1021
 
        return lock.LogicalLockResult(self.unlock)
1022
802
 
1023
803
    def _remote_lock_write(self, token):
1024
804
        path = self.bzrdir._path_for_remote_call(self._client)
1035
815
 
1036
816
    def lock_write(self, token=None, _skip_rpc=False):
1037
817
        if not self._lock_mode:
1038
 
            self._note_lock('w')
1039
818
            if _skip_rpc:
1040
819
                if self._lock_token is not None:
1041
820
                    if token != self._lock_token:
1055
834
                self._leave_lock = False
1056
835
            self._lock_mode = 'w'
1057
836
            self._lock_count = 1
1058
 
            cache_misses = self._real_repository is None
1059
 
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1060
 
            for repo in self._fallback_repositories:
1061
 
                # Writes don't affect fallback repos
1062
 
                repo.lock_read()
 
837
            self._unstacked_provider.enable_cache(cache_misses=False)
1063
838
        elif self._lock_mode == 'r':
1064
839
            raise errors.ReadOnlyError(self)
1065
840
        else:
1066
841
            self._lock_count += 1
1067
 
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
842
        return self._lock_token or None
1068
843
 
1069
844
    def leave_lock_in_place(self):
1070
845
        if not self._lock_token:
1091
866
        if isinstance(repository, RemoteRepository):
1092
867
            raise AssertionError()
1093
868
        self._real_repository = repository
1094
 
        # three code paths happen here:
1095
 
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1096
 
        # up stacking. In this case self._fallback_repositories is [], and the
1097
 
        # real repo is already setup. Preserve the real repo and
1098
 
        # RemoteRepository.add_fallback_repository will avoid adding
1099
 
        # duplicates.
1100
 
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
1101
 
        # ensure_real is triggered from a branch, the real repository to
1102
 
        # set already has a matching list with separate instances, but
1103
 
        # as they are also RemoteRepositories we don't worry about making the
1104
 
        # lists be identical.
1105
 
        # 3) new servers, RemoteRepository.ensure_real is triggered before
1106
 
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1107
 
        # and need to populate it.
1108
 
        if (self._fallback_repositories and
1109
 
            len(self._real_repository._fallback_repositories) !=
1110
 
            len(self._fallback_repositories)):
1111
 
            if len(self._real_repository._fallback_repositories):
1112
 
                raise AssertionError(
1113
 
                    "cannot cleanly remove existing _fallback_repositories")
 
869
        # If the _real_repository has _fallback_repositories, clear them out,
 
870
        # because we want it to have the same set as this repository.  This is
 
871
        # reasonable to do because the fallbacks we clear here are from a
 
872
        # "real" branch, and we're about to replace them with the equivalents
 
873
        # from a RemoteBranch.
 
874
        self._real_repository._fallback_repositories = []
1114
875
        for fb in self._fallback_repositories:
1115
876
            self._real_repository.add_fallback_repository(fb)
1116
877
        if self._lock_mode == 'w':
1123
884
    def start_write_group(self):
1124
885
        """Start a write group on the decorated repository.
1125
886
 
1126
 
        Smart methods perform operations in a single step so this API
 
887
        Smart methods peform operations in a single step so this api
1127
888
        is not really applicable except as a compatibility thunk
1128
889
        for older plugins that don't use e.g. the CommitBuilder
1129
890
        facility.
1144
905
        else:
1145
906
            raise errors.UnexpectedSmartServerResponse(response)
1146
907
 
1147
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1148
908
    def unlock(self):
1149
909
        if not self._lock_count:
1150
 
            return lock.cant_unlock_not_held(self)
 
910
            raise errors.LockNotHeld(self)
1151
911
        self._lock_count -= 1
1152
912
        if self._lock_count > 0:
1153
913
            return
1167
927
            # problem releasing the vfs-based lock.
1168
928
            if old_mode == 'w':
1169
929
                # Only write-locked repositories need to make a remote method
1170
 
                # call to perform the unlock.
 
930
                # call to perfom the unlock.
1171
931
                old_token = self._lock_token
1172
932
                self._lock_token = None
1173
933
                if not self._leave_lock:
1174
934
                    self._unlock(old_token)
1175
 
        # Fallbacks are always 'lock_read()' so we don't pay attention to
1176
 
        # self._leave_lock
1177
 
        for repo in self._fallback_repositories:
1178
 
            repo.unlock()
1179
935
 
1180
936
    def break_lock(self):
1181
937
        # should hand off to the network
1239
995
 
1240
996
        :param repository: A repository.
1241
997
        """
1242
 
        if not self._format.supports_external_lookups:
1243
 
            raise errors.UnstackableRepositoryFormat(
1244
 
                self._format.network_name(), self.base)
 
998
        # XXX: At the moment the RemoteRepository will allow fallbacks
 
999
        # unconditionally - however, a _real_repository will usually exist,
 
1000
        # and may raise an error if it's not accommodated by the underlying
 
1001
        # format.  Eventually we should check when opening the repository
 
1002
        # whether it's willing to allow them or not.
 
1003
        #
1245
1004
        # We need to accumulate additional repositories here, to pass them in
1246
1005
        # on various RPC's.
1247
1006
        #
1248
 
        if self.is_locked():
1249
 
            # We will call fallback.unlock() when we transition to the unlocked
1250
 
            # state, so always add a lock here. If a caller passes us a locked
1251
 
            # repository, they are responsible for unlocking it later.
1252
 
            repository.lock_read()
1253
 
        self._check_fallback_repository(repository)
1254
1007
        self._fallback_repositories.append(repository)
1255
1008
        # If self._real_repository was parameterised already (e.g. because a
1256
1009
        # _real_branch had its get_stacked_on_url method called), then the
1257
1010
        # repository to be added may already be in the _real_repositories list.
1258
1011
        if self._real_repository is not None:
1259
 
            fallback_locations = [repo.user_url for repo in
1260
 
                self._real_repository._fallback_repositories]
1261
 
            if repository.user_url not in fallback_locations:
 
1012
            if repository not in self._real_repository._fallback_repositories:
1262
1013
                self._real_repository.add_fallback_repository(repository)
1263
 
 
1264
 
    def _check_fallback_repository(self, repository):
1265
 
        """Check that this repository can fallback to repository safely.
1266
 
 
1267
 
        Raise an error if not.
1268
 
 
1269
 
        :param repository: A repository to fallback to.
1270
 
        """
1271
 
        return _mod_repository.InterRepository._assert_same_model(
1272
 
            self, repository)
 
1014
        else:
 
1015
            # They are also seen by the fallback repository.  If it doesn't
 
1016
            # exist yet they'll be added then.  This implicitly copies them.
 
1017
            self._ensure_real()
1273
1018
 
1274
1019
    def add_inventory(self, revid, inv, parents):
1275
1020
        self._ensure_real()
1276
1021
        return self._real_repository.add_inventory(revid, inv, parents)
1277
1022
 
1278
1023
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1279
 
            parents, basis_inv=None, propagate_caches=False):
 
1024
                               parents):
1280
1025
        self._ensure_real()
1281
1026
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1282
 
            delta, new_revision_id, parents, basis_inv=basis_inv,
1283
 
            propagate_caches=propagate_caches)
 
1027
            delta, new_revision_id, parents)
1284
1028
 
1285
1029
    def add_revision(self, rev_id, rev, inv=None, config=None):
1286
1030
        self._ensure_real()
1292
1036
        self._ensure_real()
1293
1037
        return self._real_repository.get_inventory(revision_id)
1294
1038
 
1295
 
    def iter_inventories(self, revision_ids, ordering=None):
 
1039
    def iter_inventories(self, revision_ids):
1296
1040
        self._ensure_real()
1297
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1041
        return self._real_repository.iter_inventories(revision_ids)
1298
1042
 
1299
1043
    @needs_read_lock
1300
1044
    def get_revision(self, revision_id):
1315
1059
        self._ensure_real()
1316
1060
        return self._real_repository.make_working_trees()
1317
1061
 
1318
 
    def refresh_data(self):
1319
 
        """Re-read any data needed to synchronise with disk.
1320
 
 
1321
 
        This method is intended to be called after another repository instance
1322
 
        (such as one used by a smart server) has inserted data into the
1323
 
        repository. On all repositories this will work outside of write groups.
1324
 
        Some repository formats (pack and newer for bzrlib native formats)
1325
 
        support refresh_data inside write groups. If called inside a write
1326
 
        group on a repository that does not support refreshing in a write group
1327
 
        IsInWriteGroupError will be raised.
1328
 
        """
1329
 
        if self._real_repository is not None:
1330
 
            self._real_repository.refresh_data()
1331
 
 
1332
1062
    def revision_ids_to_search_result(self, result_set):
1333
1063
        """Convert a set of revision ids to a graph SearchResult."""
1334
1064
        result_parents = set()
1355
1085
 
1356
1086
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1357
1087
            fetch_spec=None):
1358
 
        # No base implementation to use as RemoteRepository is not a subclass
1359
 
        # of Repository; so this is a copy of Repository.fetch().
1360
1088
        if fetch_spec is not None and revision_id is not None:
1361
1089
            raise AssertionError(
1362
1090
                "fetch_spec and revision_id are mutually exclusive.")
1363
 
        if self.is_in_write_group():
1364
 
            raise errors.InternalBzrError(
1365
 
                "May not fetch while in a write group.")
1366
 
        # fast path same-url fetch operations
1367
 
        if (self.has_same_location(source)
1368
 
            and fetch_spec is None
1369
 
            and self._has_same_fallbacks(source)):
 
1091
        # Not delegated to _real_repository so that InterRepository.get has a
 
1092
        # chance to find an InterRepository specialised for RemoteRepository.
 
1093
        if self.has_same_location(source) and fetch_spec is None:
1370
1094
            # check that last_revision is in 'from' and then return a
1371
1095
            # no-operation.
1372
1096
            if (revision_id is not None and
1373
1097
                not revision.is_null(revision_id)):
1374
1098
                self.get_revision(revision_id)
1375
1099
            return 0, []
1376
 
        # if there is no specific appropriate InterRepository, this will get
1377
 
        # the InterRepository base class, which raises an
1378
 
        # IncompatibleRepositories when asked to fetch.
1379
1100
        inter = repository.InterRepository.get(source, self)
1380
 
        return inter.fetch(revision_id=revision_id, pb=pb,
1381
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1101
        try:
 
1102
            return inter.fetch(revision_id=revision_id, pb=pb,
 
1103
                    find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1104
        except NotImplementedError:
 
1105
            raise errors.IncompatibleRepositories(source, self)
1382
1106
 
1383
1107
    def create_bundle(self, target, base, fileobj, format=None):
1384
1108
        self._ensure_real()
1415
1139
            # We already found out that the server can't understand
1416
1140
            # Repository.get_parent_map requests, so just fetch the whole
1417
1141
            # graph.
1418
 
            #
1419
 
            # Note that this reads the whole graph, when only some keys are
1420
 
            # wanted.  On this old server there's no way (?) to get them all
1421
 
            # in one go, and the user probably will have seen a warning about
1422
 
            # the server being old anyhow.
1423
 
            rg = self._get_revision_graph(None)
1424
 
            # There is an API discrepancy between get_parent_map and
 
1142
            # XXX: Note that this will issue a deprecation warning. This is ok
 
1143
            # :- its because we're working with a deprecated server anyway, and
 
1144
            # the user will almost certainly have seen a warning about the
 
1145
            # server version already.
 
1146
            rg = self.get_revision_graph()
 
1147
            # There is an api discrepency between get_parent_map and
1425
1148
            # get_revision_graph. Specifically, a "key:()" pair in
1426
1149
            # get_revision_graph just means a node has no parents. For
1427
1150
            # "get_parent_map" it means the node is a ghost. So fix up the
1457
1180
        # TODO: Manage this incrementally to avoid covering the same path
1458
1181
        # repeatedly. (The server will have to on each request, but the less
1459
1182
        # work done the better).
1460
 
        #
1461
 
        # Negative caching notes:
1462
 
        # new server sends missing when a request including the revid
1463
 
        # 'include-missing:' is present in the request.
1464
 
        # missing keys are serialised as missing:X, and we then call
1465
 
        # provider.note_missing(X) for-all X
1466
1183
        parents_map = self._unstacked_provider.get_cached_map()
1467
1184
        if parents_map is None:
1468
1185
            # Repository is not locked, so there's no cache.
1469
1186
            parents_map = {}
1470
 
        # start_set is all the keys in the cache
1471
1187
        start_set = set(parents_map)
1472
 
        # result set is all the references to keys in the cache
1473
1188
        result_parents = set()
1474
1189
        for parents in parents_map.itervalues():
1475
1190
            result_parents.update(parents)
1476
1191
        stop_keys = result_parents.difference(start_set)
1477
 
        # We don't need to send ghosts back to the server as a position to
1478
 
        # stop either.
1479
 
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
1480
 
        key_count = len(parents_map)
1481
 
        if (NULL_REVISION in result_parents
1482
 
            and NULL_REVISION in self._unstacked_provider.missing_keys):
1483
 
            # If we pruned NULL_REVISION from the stop_keys because it's also
1484
 
            # in our cache of "missing" keys we need to increment our key count
1485
 
            # by 1, because the reconsitituted SearchResult on the server will
1486
 
            # still consider NULL_REVISION to be an included key.
1487
 
            key_count += 1
1488
1192
        included_keys = start_set.intersection(result_parents)
1489
1193
        start_set.difference_update(included_keys)
1490
 
        recipe = ('manual', start_set, stop_keys, key_count)
 
1194
        recipe = (start_set, stop_keys, len(parents_map))
1491
1195
        body = self._serialise_search_recipe(recipe)
1492
1196
        path = self.bzrdir._path_for_remote_call(self._client)
1493
1197
        for key in keys:
1495
1199
                raise ValueError(
1496
1200
                    "key %r not a plain string" % (key,))
1497
1201
        verb = 'Repository.get_parent_map'
1498
 
        args = (path, 'include-missing:') + tuple(keys)
 
1202
        args = (path,) + tuple(keys)
1499
1203
        try:
1500
1204
            response = self._call_with_body_bytes_expecting_body(
1501
1205
                verb, args, body)
1511
1215
            # To avoid having to disconnect repeatedly, we keep track of the
1512
1216
            # fact the server doesn't understand remote methods added in 1.2.
1513
1217
            medium._remember_remote_is_before((1, 2))
1514
 
            # Recurse just once and we should use the fallback code.
1515
 
            return self._get_parent_map_rpc(keys)
 
1218
            return self.get_revision_graph(None)
1516
1219
        response_tuple, response_handler = response
1517
1220
        if response_tuple[0] not in ['ok']:
1518
1221
            response_handler.cancel_read_body()
1529
1232
                if len(d) > 1:
1530
1233
                    revision_graph[d[0]] = d[1:]
1531
1234
                else:
1532
 
                    # No parents:
1533
 
                    if d[0].startswith('missing:'):
1534
 
                        revid = d[0][8:]
1535
 
                        self._unstacked_provider.note_missing_key(revid)
1536
 
                    else:
1537
 
                        # no parents - so give the Graph result
1538
 
                        # (NULL_REVISION,).
1539
 
                        revision_graph[d[0]] = (NULL_REVISION,)
 
1235
                    # No parents - so give the Graph result (NULL_REVISION,).
 
1236
                    revision_graph[d[0]] = (NULL_REVISION,)
1540
1237
            return revision_graph
1541
1238
 
1542
1239
    @needs_read_lock
1545
1242
        return self._real_repository.get_signature_text(revision_id)
1546
1243
 
1547
1244
    @needs_read_lock
1548
 
    def _get_inventory_xml(self, revision_id):
1549
 
        self._ensure_real()
1550
 
        return self._real_repository._get_inventory_xml(revision_id)
 
1245
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
 
1246
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1247
        self._ensure_real()
 
1248
        return self._real_repository.get_revision_graph_with_ghosts(
 
1249
            revision_ids=revision_ids)
 
1250
 
 
1251
    @needs_read_lock
 
1252
    def get_inventory_xml(self, revision_id):
 
1253
        self._ensure_real()
 
1254
        return self._real_repository.get_inventory_xml(revision_id)
 
1255
 
 
1256
    def deserialise_inventory(self, revision_id, xml):
 
1257
        self._ensure_real()
 
1258
        return self._real_repository.deserialise_inventory(revision_id, xml)
1551
1259
 
1552
1260
    def reconcile(self, other=None, thorough=False):
1553
1261
        self._ensure_real()
1558
1266
        return self._real_repository.all_revision_ids()
1559
1267
 
1560
1268
    @needs_read_lock
1561
 
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1269
    def get_deltas_for_revisions(self, revisions):
1562
1270
        self._ensure_real()
1563
 
        return self._real_repository.get_deltas_for_revisions(revisions,
1564
 
            specific_fileids=specific_fileids)
 
1271
        return self._real_repository.get_deltas_for_revisions(revisions)
1565
1272
 
1566
1273
    @needs_read_lock
1567
 
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1274
    def get_revision_delta(self, revision_id):
1568
1275
        self._ensure_real()
1569
 
        return self._real_repository.get_revision_delta(revision_id,
1570
 
            specific_fileids=specific_fileids)
 
1276
        return self._real_repository.get_revision_delta(revision_id)
1571
1277
 
1572
1278
    @needs_read_lock
1573
1279
    def revision_trees(self, revision_ids):
1580
1286
        return self._real_repository.get_revision_reconcile(revision_id)
1581
1287
 
1582
1288
    @needs_read_lock
1583
 
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
1289
    def check(self, revision_ids=None):
1584
1290
        self._ensure_real()
1585
 
        return self._real_repository.check(revision_ids=revision_ids,
1586
 
            callback_refs=callback_refs, check_repo=check_repo)
 
1291
        return self._real_repository.check(revision_ids=revision_ids)
1587
1292
 
1588
1293
    def copy_content_into(self, destination, revision_id=None):
1589
1294
        self._ensure_real()
1629
1334
        return self._real_repository.inventories
1630
1335
 
1631
1336
    @needs_write_lock
1632
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1337
    def pack(self):
1633
1338
        """Compress the data within the repository.
1634
1339
 
1635
1340
        This is not currently implemented within the smart server.
1636
1341
        """
1637
1342
        self._ensure_real()
1638
 
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
1343
        return self._real_repository.pack()
1639
1344
 
1640
1345
    @property
1641
1346
    def revisions(self):
1729
1434
        self._ensure_real()
1730
1435
        return self._real_repository.revision_graph_can_have_wrong_parents()
1731
1436
 
1732
 
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
 
1437
    def _find_inconsistent_revision_parents(self):
1733
1438
        self._ensure_real()
1734
 
        return self._real_repository._find_inconsistent_revision_parents(
1735
 
            revisions_iterator)
 
1439
        return self._real_repository._find_inconsistent_revision_parents()
1736
1440
 
1737
1441
    def _check_for_inconsistent_revision_parents(self):
1738
1442
        self._ensure_real()
1744
1448
            providers.insert(0, other)
1745
1449
        providers.extend(r._make_parents_provider() for r in
1746
1450
                         self._fallback_repositories)
1747
 
        return graph.StackedParentsProvider(providers)
 
1451
        return graph._StackedParentsProvider(providers)
1748
1452
 
1749
1453
    def _serialise_search_recipe(self, recipe):
1750
1454
        """Serialise a graph search recipe.
1752
1456
        :param recipe: A search recipe (start, stop, count).
1753
1457
        :return: Serialised bytes.
1754
1458
        """
1755
 
        start_keys = ' '.join(recipe[1])
1756
 
        stop_keys = ' '.join(recipe[2])
1757
 
        count = str(recipe[3])
 
1459
        start_keys = ' '.join(recipe[0])
 
1460
        stop_keys = ' '.join(recipe[1])
 
1461
        count = str(recipe[2])
1758
1462
        return '\n'.join((start_keys, stop_keys, count))
1759
1463
 
1760
1464
    def _serialise_search_result(self, search_result):
1763
1467
            parts.extend(search_result.heads)
1764
1468
        else:
1765
1469
            recipe = search_result.get_recipe()
1766
 
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1470
            parts = ['search', self._serialise_search_recipe(recipe)]
1767
1471
        return '\n'.join(parts)
1768
1472
 
1769
1473
    def autopack(self):
1774
1478
            self._ensure_real()
1775
1479
            self._real_repository._pack_collection.autopack()
1776
1480
            return
1777
 
        self.refresh_data()
 
1481
        if self._real_repository is not None:
 
1482
            # Reset the real repository's cache of pack names.
 
1483
            # XXX: At some point we may be able to skip this and just rely on
 
1484
            # the automatic retry logic to do the right thing, but for now we
 
1485
            # err on the side of being correct rather than being optimal.
 
1486
            self._real_repository._pack_collection.reload_pack_names()
1778
1487
        if response[0] != 'ok':
1779
1488
            raise errors.UnexpectedSmartServerResponse(response)
1780
1489
 
1790
1499
        return result
1791
1500
 
1792
1501
    def insert_stream(self, stream, src_format, resume_tokens):
1793
 
        target = self.target_repo
1794
 
        target._unstacked_provider.missing_keys.clear()
1795
 
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1796
 
        if target._lock_token:
1797
 
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1798
 
            lock_args = (target._lock_token or '',)
1799
 
        else:
1800
 
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
1801
 
            lock_args = ()
1802
 
        client = target._client
 
1502
        repo = self.target_repo
 
1503
        client = repo._client
1803
1504
        medium = client._medium
1804
 
        path = target.bzrdir._path_for_remote_call(client)
1805
 
        # Probe for the verb to use with an empty stream before sending the
1806
 
        # real stream to it.  We do this both to avoid the risk of sending a
1807
 
        # large request that is then rejected, and because we don't want to
1808
 
        # implement a way to buffer, rewind, or restart the stream.
1809
 
        found_verb = False
1810
 
        for verb, required_version in candidate_calls:
1811
 
            if medium._is_remote_before(required_version):
1812
 
                continue
1813
 
            if resume_tokens:
1814
 
                # We've already done the probing (and set _is_remote_before) on
1815
 
                # a previous insert.
1816
 
                found_verb = True
1817
 
                break
 
1505
        if medium._is_remote_before((1, 13)):
 
1506
            # No possible way this can work.
 
1507
            return self._insert_real(stream, src_format, resume_tokens)
 
1508
        path = repo.bzrdir._path_for_remote_call(client)
 
1509
        if not resume_tokens:
 
1510
            # XXX: Ugly but important for correctness, *will* be fixed during
 
1511
            # 1.13 cycle. Pushing a stream that is interrupted results in a
 
1512
            # fallback to the _real_repositories sink *with a partial stream*.
 
1513
            # Thats bad because we insert less data than bzr expected. To avoid
 
1514
            # this we do a trial push to make sure the verb is accessible, and
 
1515
            # do not fallback when actually pushing the stream. A cleanup patch
 
1516
            # is going to look at rewinding/restarting the stream/partial
 
1517
            # buffering etc.
1818
1518
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1819
1519
            try:
1820
1520
                response = client.call_with_body_stream(
1821
 
                    (verb, path, '') + lock_args, byte_stream)
 
1521
                    ('Repository.insert_stream', path, ''), byte_stream)
1822
1522
            except errors.UnknownSmartMethod:
1823
 
                medium._remember_remote_is_before(required_version)
1824
 
            else:
1825
 
                found_verb = True
1826
 
                break
1827
 
        if not found_verb:
1828
 
            # Have to use VFS.
1829
 
            return self._insert_real(stream, src_format, resume_tokens)
1830
 
        self._last_inv_record = None
1831
 
        self._last_substream = None
1832
 
        if required_version < (1, 19):
1833
 
            # Remote side doesn't support inventory deltas.  Wrap the stream to
1834
 
            # make sure we don't send any.  If the stream contains inventory
1835
 
            # deltas we'll interrupt the smart insert_stream request and
1836
 
            # fallback to VFS.
1837
 
            stream = self._stop_stream_if_inventory_delta(stream)
 
1523
                medium._remember_remote_is_before((1,13))
 
1524
                return self._insert_real(stream, src_format, resume_tokens)
1838
1525
        byte_stream = smart_repo._stream_to_byte_stream(
1839
1526
            stream, src_format)
1840
1527
        resume_tokens = ' '.join(resume_tokens)
1841
1528
        response = client.call_with_body_stream(
1842
 
            (verb, path, resume_tokens) + lock_args, byte_stream)
 
1529
            ('Repository.insert_stream', path, resume_tokens), byte_stream)
1843
1530
        if response[0][0] not in ('ok', 'missing-basis'):
1844
1531
            raise errors.UnexpectedSmartServerResponse(response)
1845
 
        if self._last_substream is not None:
1846
 
            # The stream included an inventory-delta record, but the remote
1847
 
            # side isn't new enough to support them.  So we need to send the
1848
 
            # rest of the stream via VFS.
1849
 
            self.target_repo.refresh_data()
1850
 
            return self._resume_stream_with_vfs(response, src_format)
1851
1532
        if response[0][0] == 'missing-basis':
1852
1533
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1853
1534
            resume_tokens = tokens
1854
 
            return resume_tokens, set(missing_keys)
 
1535
            return resume_tokens, missing_keys
1855
1536
        else:
1856
 
            self.target_repo.refresh_data()
 
1537
            if self.target_repo._real_repository is not None:
 
1538
                collection = getattr(self.target_repo._real_repository,
 
1539
                    '_pack_collection', None)
 
1540
                if collection is not None:
 
1541
                    collection.reload_pack_names()
1857
1542
            return [], set()
1858
1543
 
1859
 
    def _resume_stream_with_vfs(self, response, src_format):
1860
 
        """Resume sending a stream via VFS, first resending the record and
1861
 
        substream that couldn't be sent via an insert_stream verb.
1862
 
        """
1863
 
        if response[0][0] == 'missing-basis':
1864
 
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1865
 
            # Ignore missing_keys, we haven't finished inserting yet
1866
 
        else:
1867
 
            tokens = []
1868
 
        def resume_substream():
1869
 
            # Yield the substream that was interrupted.
1870
 
            for record in self._last_substream:
1871
 
                yield record
1872
 
            self._last_substream = None
1873
 
        def resume_stream():
1874
 
            # Finish sending the interrupted substream
1875
 
            yield ('inventory-deltas', resume_substream())
1876
 
            # Then simply continue sending the rest of the stream.
1877
 
            for substream_kind, substream in self._last_stream:
1878
 
                yield substream_kind, substream
1879
 
        return self._insert_real(resume_stream(), src_format, tokens)
1880
 
 
1881
 
    def _stop_stream_if_inventory_delta(self, stream):
1882
 
        """Normally this just lets the original stream pass-through unchanged.
1883
 
 
1884
 
        However if any 'inventory-deltas' substream occurs it will stop
1885
 
        streaming, and store the interrupted substream and stream in
1886
 
        self._last_substream and self._last_stream so that the stream can be
1887
 
        resumed by _resume_stream_with_vfs.
1888
 
        """
1889
 
                    
1890
 
        stream_iter = iter(stream)
1891
 
        for substream_kind, substream in stream_iter:
1892
 
            if substream_kind == 'inventory-deltas':
1893
 
                self._last_substream = substream
1894
 
                self._last_stream = stream_iter
1895
 
                return
1896
 
            else:
1897
 
                yield substream_kind, substream
1898
 
            
1899
1544
 
1900
1545
class RemoteStreamSource(repository.StreamSource):
1901
1546
    """Stream data from a remote server."""
1902
1547
 
1903
1548
    def get_stream(self, search):
1904
 
        if (self.from_repository._fallback_repositories and
1905
 
            self.to_format._fetch_order == 'topological'):
1906
 
            return self._real_stream(self.from_repository, search)
1907
 
        sources = []
1908
 
        seen = set()
1909
 
        repos = [self.from_repository]
1910
 
        while repos:
1911
 
            repo = repos.pop(0)
1912
 
            if repo in seen:
1913
 
                continue
1914
 
            seen.add(repo)
1915
 
            repos.extend(repo._fallback_repositories)
1916
 
            sources.append(repo)
1917
 
        return self.missing_parents_chain(search, sources)
1918
 
 
1919
 
    def get_stream_for_missing_keys(self, missing_keys):
1920
 
        self.from_repository._ensure_real()
1921
 
        real_repo = self.from_repository._real_repository
1922
 
        real_source = real_repo._get_source(self.to_format)
1923
 
        return real_source.get_stream_for_missing_keys(missing_keys)
1924
 
 
1925
 
    def _real_stream(self, repo, search):
1926
 
        """Get a stream for search from repo.
1927
 
        
1928
 
        This never called RemoteStreamSource.get_stream, and is a heler
1929
 
        for RemoteStreamSource._get_stream to allow getting a stream 
1930
 
        reliably whether fallback back because of old servers or trying
1931
 
        to stream from a non-RemoteRepository (which the stacked support
1932
 
        code will do).
1933
 
        """
1934
 
        source = repo._get_source(self.to_format)
1935
 
        if isinstance(source, RemoteStreamSource):
1936
 
            repo._ensure_real()
1937
 
            source = repo._real_repository._get_source(self.to_format)
1938
 
        return source.get_stream(search)
1939
 
 
1940
 
    def _get_stream(self, repo, search):
1941
 
        """Core worker to get a stream from repo for search.
1942
 
 
1943
 
        This is used by both get_stream and the stacking support logic. It
1944
 
        deliberately gets a stream for repo which does not need to be
1945
 
        self.from_repository. In the event that repo is not Remote, or
1946
 
        cannot do a smart stream, a fallback is made to the generic
1947
 
        repository._get_stream() interface, via self._real_stream.
1948
 
 
1949
 
        In the event of stacking, streams from _get_stream will not
1950
 
        contain all the data for search - this is normal (see get_stream).
1951
 
 
1952
 
        :param repo: A repository.
1953
 
        :param search: A search.
1954
 
        """
1955
 
        # Fallbacks may be non-smart
1956
 
        if not isinstance(repo, RemoteRepository):
1957
 
            return self._real_stream(repo, search)
 
1549
        # streaming with fallback repositories is not well defined yet: The
 
1550
        # remote repository cannot see the fallback repositories, and thus
 
1551
        # cannot satisfy the entire search in the general case. Likewise the
 
1552
        # fallback repositories cannot reify the search to determine what they
 
1553
        # should send. It likely needs a return value in the stream listing the
 
1554
        # edge of the search to resume from in fallback repositories.
 
1555
        if self.from_repository._fallback_repositories:
 
1556
            return repository.StreamSource.get_stream(self, search)
 
1557
        repo = self.from_repository
1958
1558
        client = repo._client
1959
1559
        medium = client._medium
 
1560
        if medium._is_remote_before((1, 13)):
 
1561
            # No possible way this can work.
 
1562
            return repository.StreamSource.get_stream(self, search)
1960
1563
        path = repo.bzrdir._path_for_remote_call(client)
1961
 
        search_bytes = repo._serialise_search_result(search)
1962
 
        args = (path, self.to_format.network_name())
1963
 
        candidate_verbs = [
1964
 
            ('Repository.get_stream_1.19', (1, 19)),
1965
 
            ('Repository.get_stream', (1, 13))]
1966
 
        found_verb = False
1967
 
        for verb, version in candidate_verbs:
1968
 
            if medium._is_remote_before(version):
1969
 
                continue
1970
 
            try:
1971
 
                response = repo._call_with_body_bytes_expecting_body(
1972
 
                    verb, args, search_bytes)
1973
 
            except errors.UnknownSmartMethod:
1974
 
                medium._remember_remote_is_before(version)
1975
 
            else:
1976
 
                response_tuple, response_handler = response
1977
 
                found_verb = True
1978
 
                break
1979
 
        if not found_verb:
1980
 
            return self._real_stream(repo, search)
 
1564
        try:
 
1565
            search_bytes = repo._serialise_search_result(search)
 
1566
            response = repo._call_with_body_bytes_expecting_body(
 
1567
                'Repository.get_stream',
 
1568
                (path, self.to_format.network_name()), search_bytes)
 
1569
            response_tuple, response_handler = response
 
1570
        except errors.UnknownSmartMethod:
 
1571
            medium._remember_remote_is_before((1,13))
 
1572
            return repository.StreamSource.get_stream(self, search)
1981
1573
        if response_tuple[0] != 'ok':
1982
1574
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1983
1575
        byte_stream = response_handler.read_streamed_body()
1984
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
1985
 
            self._record_counter)
 
1576
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1986
1577
        if src_format.network_name() != repo._format.network_name():
1987
1578
            raise AssertionError(
1988
1579
                "Mismatched RemoteRepository and stream src %r, %r" % (
1989
1580
                src_format.network_name(), repo._format.network_name()))
1990
1581
        return stream
1991
1582
 
1992
 
    def missing_parents_chain(self, search, sources):
1993
 
        """Chain multiple streams together to handle stacking.
1994
 
 
1995
 
        :param search: The overall search to satisfy with streams.
1996
 
        :param sources: A list of Repository objects to query.
1997
 
        """
1998
 
        self.from_serialiser = self.from_repository._format._serializer
1999
 
        self.seen_revs = set()
2000
 
        self.referenced_revs = set()
2001
 
        # If there are heads in the search, or the key count is > 0, we are not
2002
 
        # done.
2003
 
        while not search.is_empty() and len(sources) > 1:
2004
 
            source = sources.pop(0)
2005
 
            stream = self._get_stream(source, search)
2006
 
            for kind, substream in stream:
2007
 
                if kind != 'revisions':
2008
 
                    yield kind, substream
2009
 
                else:
2010
 
                    yield kind, self.missing_parents_rev_handler(substream)
2011
 
            search = search.refine(self.seen_revs, self.referenced_revs)
2012
 
            self.seen_revs = set()
2013
 
            self.referenced_revs = set()
2014
 
        if not search.is_empty():
2015
 
            for kind, stream in self._get_stream(sources[0], search):
2016
 
                yield kind, stream
2017
 
 
2018
 
    def missing_parents_rev_handler(self, substream):
2019
 
        for content in substream:
2020
 
            revision_bytes = content.get_bytes_as('fulltext')
2021
 
            revision = self.from_serialiser.read_revision_from_string(
2022
 
                revision_bytes)
2023
 
            self.seen_revs.add(content.key[-1])
2024
 
            self.referenced_revs.update(revision.parent_ids)
2025
 
            yield content
2026
 
 
2027
1583
 
2028
1584
class RemoteBranchLockableFiles(LockableFiles):
2029
1585
    """A 'LockableFiles' implementation that talks to a smart server.
2064
1620
                self._network_name)
2065
1621
 
2066
1622
    def get_format_description(self):
2067
 
        self._ensure_real()
2068
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
1623
        return 'Remote BZR Branch'
2069
1624
 
2070
1625
    def network_name(self):
2071
1626
        return self._network_name
2072
1627
 
2073
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2074
 
        return a_bzrdir.open_branch(name=name, 
2075
 
            ignore_fallbacks=ignore_fallbacks)
 
1628
    def open(self, a_bzrdir):
 
1629
        return a_bzrdir.open_branch()
2076
1630
 
2077
 
    def _vfs_initialize(self, a_bzrdir, name):
 
1631
    def _vfs_initialize(self, a_bzrdir):
2078
1632
        # Initialisation when using a local bzrdir object, or a non-vfs init
2079
1633
        # method is not available on the server.
2080
1634
        # self._custom_format is always set - the start of initialize ensures
2081
1635
        # that.
2082
1636
        if isinstance(a_bzrdir, RemoteBzrDir):
2083
1637
            a_bzrdir._ensure_real()
2084
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2085
 
                name)
 
1638
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2086
1639
        else:
2087
1640
            # We assume the bzrdir is parameterised; it may not be.
2088
 
            result = self._custom_format.initialize(a_bzrdir, name)
 
1641
            result = self._custom_format.initialize(a_bzrdir)
2089
1642
        if (isinstance(a_bzrdir, RemoteBzrDir) and
2090
1643
            not isinstance(result, RemoteBranch)):
2091
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2092
 
                                  name=name)
 
1644
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2093
1645
        return result
2094
1646
 
2095
 
    def initialize(self, a_bzrdir, name=None):
 
1647
    def initialize(self, a_bzrdir):
2096
1648
        # 1) get the network name to use.
2097
1649
        if self._custom_format:
2098
1650
            network_name = self._custom_format.network_name()
2104
1656
            network_name = reference_format.network_name()
2105
1657
        # Being asked to create on a non RemoteBzrDir:
2106
1658
        if not isinstance(a_bzrdir, RemoteBzrDir):
2107
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1659
            return self._vfs_initialize(a_bzrdir)
2108
1660
        medium = a_bzrdir._client._medium
2109
1661
        if medium._is_remote_before((1, 13)):
2110
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1662
            return self._vfs_initialize(a_bzrdir)
2111
1663
        # Creating on a remote bzr dir.
2112
1664
        # 2) try direct creation via RPC
2113
1665
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2114
 
        if name is not None:
2115
 
            # XXX JRV20100304: Support creating colocated branches
2116
 
            raise errors.NoColocatedBranchSupport(self)
2117
1666
        verb = 'BzrDir.create_branch'
2118
1667
        try:
2119
1668
            response = a_bzrdir._call(verb, path, network_name)
2120
1669
        except errors.UnknownSmartMethod:
2121
1670
            # Fallback - use vfs methods
2122
1671
            medium._remember_remote_is_before((1, 13))
2123
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
1672
            return self._vfs_initialize(a_bzrdir)
2124
1673
        if response[0] != 'ok':
2125
1674
            raise errors.UnexpectedSmartServerResponse(response)
2126
1675
        # Turn the response into a RemoteRepository object.
2134
1683
                a_bzrdir._client)
2135
1684
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2136
1685
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2137
 
            format=format, setup_stacking=False, name=name)
 
1686
            format=format, setup_stacking=False)
2138
1687
        # XXX: We know this is a new branch, so it must have revno 0, revid
2139
1688
        # NULL_REVISION. Creating the branch locked would make this be unable
2140
1689
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2155
1704
        self._ensure_real()
2156
1705
        return self._custom_format.supports_stacking()
2157
1706
 
2158
 
    def supports_set_append_revisions_only(self):
2159
 
        self._ensure_real()
2160
 
        return self._custom_format.supports_set_append_revisions_only()
2161
 
 
2162
 
 
2163
 
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
 
1707
 
 
1708
class RemoteBranch(branch.Branch, _RpcHelper):
2164
1709
    """Branch stored on a server accessed by HPSS RPC.
2165
1710
 
2166
1711
    At the moment most operations are mapped down to simple file operations.
2167
1712
    """
2168
1713
 
2169
1714
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2170
 
        _client=None, format=None, setup_stacking=True, name=None):
 
1715
        _client=None, format=None, setup_stacking=True):
2171
1716
        """Create a RemoteBranch instance.
2172
1717
 
2173
1718
        :param real_branch: An optional local implementation of the branch
2179
1724
        :param setup_stacking: If True make an RPC call to determine the
2180
1725
            stacked (or not) status of the branch. If False assume the branch
2181
1726
            is not stacked.
2182
 
        :param name: Colocated branch name
2183
1727
        """
2184
1728
        # We intentionally don't call the parent class's __init__, because it
2185
1729
        # will try to assign to self.tags, which is a property in this subclass.
2186
1730
        # And the parent's __init__ doesn't do much anyway.
 
1731
        self._revision_id_to_revno_cache = None
 
1732
        self._partial_revision_id_to_revno_cache = {}
 
1733
        self._revision_history_cache = None
 
1734
        self._last_revision_info_cache = None
 
1735
        self._merge_sorted_revisions_cache = None
2187
1736
        self.bzrdir = remote_bzrdir
2188
1737
        if _client is not None:
2189
1738
            self._client = _client
2202
1751
            self._real_branch.repository = self.repository
2203
1752
        else:
2204
1753
            self._real_branch = None
2205
 
        # Fill out expected attributes of branch for bzrlib API users.
2206
 
        self._clear_cached_state()
2207
 
        # TODO: deprecate self.base in favor of user_url
2208
 
        self.base = self.bzrdir.user_url
2209
 
        self._name = name
 
1754
        # Fill out expected attributes of branch for bzrlib api users.
 
1755
        self.base = self.bzrdir.root_transport.base
2210
1756
        self._control_files = None
2211
1757
        self._lock_mode = None
2212
1758
        self._lock_token = None
2223
1769
                    self._real_branch._format.network_name()
2224
1770
        else:
2225
1771
            self._format = format
2226
 
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
2227
 
        # branch.open_branch method.
2228
 
        self._real_ignore_fallbacks = not setup_stacking
2229
1772
        if not self._format._network_name:
2230
1773
            # Did not get from open_branchV2 - old server.
2231
1774
            self._ensure_real()
2236
1779
        hooks = branch.Branch.hooks['open']
2237
1780
        for hook in hooks:
2238
1781
            hook(self)
2239
 
        self._is_stacked = False
2240
1782
        if setup_stacking:
2241
1783
            self._setup_stacking()
2242
1784
 
2248
1790
        except (errors.NotStacked, errors.UnstackableBranchFormat,
2249
1791
            errors.UnstackableRepositoryFormat), e:
2250
1792
            return
2251
 
        self._is_stacked = True
2252
 
        self._activate_fallback_location(fallback_url)
2253
 
 
2254
 
    def _get_config(self):
2255
 
        return RemoteBranchConfig(self)
 
1793
        # it's relative to this branch...
 
1794
        fallback_url = urlutils.join(self.base, fallback_url)
 
1795
        transports = [self.bzrdir.root_transport]
 
1796
        stacked_on = branch.Branch.open(fallback_url,
 
1797
                                        possible_transports=transports)
 
1798
        self.repository.add_fallback_repository(stacked_on.repository)
2256
1799
 
2257
1800
    def _get_real_transport(self):
2258
1801
        # if we try vfs access, return the real branch's vfs transport
2276
1819
                raise AssertionError('smart server vfs must be enabled '
2277
1820
                    'to use vfs implementation')
2278
1821
            self.bzrdir._ensure_real()
2279
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2280
 
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
1822
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2281
1823
            if self.repository._real_repository is None:
2282
1824
                # Give the remote repository the matching real repo.
2283
1825
                real_repo = self._real_branch.repository
2357
1899
            raise errors.UnexpectedSmartServerResponse(response)
2358
1900
        return response[1]
2359
1901
 
2360
 
    def set_stacked_on_url(self, url):
2361
 
        branch.Branch.set_stacked_on_url(self, url)
2362
 
        if not url:
2363
 
            self._is_stacked = False
2364
 
        else:
2365
 
            self._is_stacked = True
2366
 
        
2367
1902
    def _vfs_get_tags_bytes(self):
2368
1903
        self._ensure_real()
2369
1904
        return self._real_branch._get_tags_bytes()
2379
1914
            return self._vfs_get_tags_bytes()
2380
1915
        return response[0]
2381
1916
 
2382
 
    def _vfs_set_tags_bytes(self, bytes):
2383
 
        self._ensure_real()
2384
 
        return self._real_branch._set_tags_bytes(bytes)
2385
 
 
2386
 
    def _set_tags_bytes(self, bytes):
2387
 
        medium = self._client._medium
2388
 
        if medium._is_remote_before((1, 18)):
2389
 
            self._vfs_set_tags_bytes(bytes)
2390
 
            return
2391
 
        try:
2392
 
            args = (
2393
 
                self._remote_path(), self._lock_token, self._repo_lock_token)
2394
 
            response = self._call_with_body_bytes(
2395
 
                'Branch.set_tags_bytes', args, bytes)
2396
 
        except errors.UnknownSmartMethod:
2397
 
            medium._remember_remote_is_before((1, 18))
2398
 
            self._vfs_set_tags_bytes(bytes)
2399
 
 
2400
1917
    def lock_read(self):
2401
 
        """Lock the branch for read operations.
2402
 
 
2403
 
        :return: A bzrlib.lock.LogicalLockResult.
2404
 
        """
2405
1918
        self.repository.lock_read()
2406
1919
        if not self._lock_mode:
2407
 
            self._note_lock('r')
2408
1920
            self._lock_mode = 'r'
2409
1921
            self._lock_count = 1
2410
1922
            if self._real_branch is not None:
2411
1923
                self._real_branch.lock_read()
2412
1924
        else:
2413
1925
            self._lock_count += 1
2414
 
        return lock.LogicalLockResult(self.unlock)
2415
1926
 
2416
1927
    def _remote_lock_write(self, token):
2417
1928
        if token is None:
2418
1929
            branch_token = repo_token = ''
2419
1930
        else:
2420
1931
            branch_token = token
2421
 
            repo_token = self.repository.lock_write().repository_token
 
1932
            repo_token = self.repository.lock_write()
2422
1933
            self.repository.unlock()
2423
1934
        err_context = {'token': token}
2424
 
        try:
2425
 
            response = self._call(
2426
 
                'Branch.lock_write', self._remote_path(), branch_token,
2427
 
                repo_token or '', **err_context)
2428
 
        except errors.LockContention, e:
2429
 
            # The LockContention from the server doesn't have any
2430
 
            # information about the lock_url. We re-raise LockContention
2431
 
            # with valid lock_url.
2432
 
            raise errors.LockContention('(remote lock)',
2433
 
                self.repository.base.split('.bzr/')[0])
 
1935
        response = self._call(
 
1936
            'Branch.lock_write', self._remote_path(), branch_token,
 
1937
            repo_token or '', **err_context)
2434
1938
        if response[0] != 'ok':
2435
1939
            raise errors.UnexpectedSmartServerResponse(response)
2436
1940
        ok, branch_token, repo_token = response
2438
1942
 
2439
1943
    def lock_write(self, token=None):
2440
1944
        if not self._lock_mode:
2441
 
            self._note_lock('w')
2442
1945
            # Lock the branch and repo in one remote call.
2443
1946
            remote_tokens = self._remote_lock_write(token)
2444
1947
            self._lock_token, self._repo_lock_token = remote_tokens
2457
1960
            self._lock_mode = 'w'
2458
1961
            self._lock_count = 1
2459
1962
        elif self._lock_mode == 'r':
2460
 
            raise errors.ReadOnlyError(self)
 
1963
            raise errors.ReadOnlyTransaction
2461
1964
        else:
2462
1965
            if token is not None:
2463
1966
                # A token was given to lock_write, and we're relocking, so
2468
1971
            self._lock_count += 1
2469
1972
            # Re-lock the repository too.
2470
1973
            self.repository.lock_write(self._repo_lock_token)
2471
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
1974
        return self._lock_token or None
 
1975
 
 
1976
    def _set_tags_bytes(self, bytes):
 
1977
        self._ensure_real()
 
1978
        return self._real_branch._set_tags_bytes(bytes)
2472
1979
 
2473
1980
    def _unlock(self, branch_token, repo_token):
2474
1981
        err_context = {'token': str((branch_token, repo_token))}
2479
1986
            return
2480
1987
        raise errors.UnexpectedSmartServerResponse(response)
2481
1988
 
2482
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2483
1989
    def unlock(self):
2484
1990
        try:
2485
1991
            self._lock_count -= 1
2498
2004
                    self._real_branch.unlock()
2499
2005
                if mode != 'w':
2500
2006
                    # Only write-locked branched need to make a remote method
2501
 
                    # call to perform the unlock.
 
2007
                    # call to perfom the unlock.
2502
2008
                    return
2503
2009
                if not self._lock_token:
2504
2010
                    raise AssertionError('Locked, but no token!')
2525
2031
            raise NotImplementedError(self.dont_leave_lock_in_place)
2526
2032
        self._leave_lock = False
2527
2033
 
2528
 
    @needs_read_lock
2529
 
    def get_rev_id(self, revno, history=None):
2530
 
        if revno == 0:
2531
 
            return _mod_revision.NULL_REVISION
2532
 
        last_revision_info = self.last_revision_info()
2533
 
        ok, result = self.repository.get_rev_id_for_revno(
2534
 
            revno, last_revision_info)
2535
 
        if ok:
2536
 
            return result
2537
 
        missing_parent = result[1]
2538
 
        # Either the revision named by the server is missing, or its parent
2539
 
        # is.  Call get_parent_map to determine which, so that we report a
2540
 
        # useful error.
2541
 
        parent_map = self.repository.get_parent_map([missing_parent])
2542
 
        if missing_parent in parent_map:
2543
 
            missing_parent = parent_map[missing_parent]
2544
 
        raise errors.RevisionNotPresent(missing_parent, self.repository)
2545
 
 
2546
2034
    def _last_revision_info(self):
2547
2035
        response = self._call('Branch.last_revision_info', self._remote_path())
2548
2036
        if response[0] != 'ok':
2553
2041
 
2554
2042
    def _gen_revision_history(self):
2555
2043
        """See Branch._gen_revision_history()."""
2556
 
        if self._is_stacked:
2557
 
            self._ensure_real()
2558
 
            return self._real_branch._gen_revision_history()
2559
2044
        response_tuple, response_handler = self._call_expecting_body(
2560
2045
            'Branch.revision_history', self._remote_path())
2561
2046
        if response_tuple[0] != 'ok':
2646
2131
        self._ensure_real()
2647
2132
        return self._real_branch._get_parent_location()
2648
2133
 
 
2134
    def set_parent(self, url):
 
2135
        self._ensure_real()
 
2136
        return self._real_branch.set_parent(url)
 
2137
 
2649
2138
    def _set_parent_location(self, url):
2650
 
        medium = self._client._medium
2651
 
        if medium._is_remote_before((1, 15)):
2652
 
            return self._vfs_set_parent_location(url)
2653
 
        try:
2654
 
            call_url = url or ''
2655
 
            if type(call_url) is not str:
2656
 
                raise AssertionError('url must be a str or None (%s)' % url)
2657
 
            response = self._call('Branch.set_parent_location',
2658
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
2659
 
                call_url)
2660
 
        except errors.UnknownSmartMethod:
2661
 
            medium._remember_remote_is_before((1, 15))
2662
 
            return self._vfs_set_parent_location(url)
2663
 
        if response != ():
2664
 
            raise errors.UnexpectedSmartServerResponse(response)
2665
 
 
2666
 
    def _vfs_set_parent_location(self, url):
 
2139
        # Used by tests, to poke bad urls into branch configurations
 
2140
        if url is None:
 
2141
            self.set_parent(url)
 
2142
        else:
 
2143
            self._ensure_real()
 
2144
            return self._real_branch._set_parent_location(url)
 
2145
 
 
2146
    def set_stacked_on_url(self, stacked_location):
 
2147
        """Set the URL this branch is stacked against.
 
2148
 
 
2149
        :raises UnstackableBranchFormat: If the branch does not support
 
2150
            stacking.
 
2151
        :raises UnstackableRepositoryFormat: If the repository does not support
 
2152
            stacking.
 
2153
        """
2667
2154
        self._ensure_real()
2668
 
        return self._real_branch._set_parent_location(url)
 
2155
        return self._real_branch.set_stacked_on_url(stacked_location)
2669
2156
 
2670
2157
    @needs_write_lock
2671
2158
    def pull(self, source, overwrite=False, stop_revision=None,
2739
2226
        return self._real_branch.set_push_location(location)
2740
2227
 
2741
2228
 
2742
 
class RemoteConfig(object):
2743
 
    """A Config that reads and writes from smart verbs.
2744
 
 
2745
 
    It is a low-level object that considers config data to be name/value pairs
2746
 
    that may be associated with a section. Assigning meaning to the these
2747
 
    values is done at higher levels like bzrlib.config.TreeConfig.
2748
 
    """
2749
 
 
2750
 
    def get_option(self, name, section=None, default=None):
2751
 
        """Return the value associated with a named option.
2752
 
 
2753
 
        :param name: The name of the value
2754
 
        :param section: The section the option is in (if any)
2755
 
        :param default: The value to return if the value is not set
2756
 
        :return: The value or default value
2757
 
        """
2758
 
        try:
2759
 
            configobj = self._get_configobj()
2760
 
            if section is None:
2761
 
                section_obj = configobj
2762
 
            else:
2763
 
                try:
2764
 
                    section_obj = configobj[section]
2765
 
                except KeyError:
2766
 
                    return default
2767
 
            return section_obj.get(name, default)
2768
 
        except errors.UnknownSmartMethod:
2769
 
            return self._vfs_get_option(name, section, default)
2770
 
 
2771
 
    def _response_to_configobj(self, response):
2772
 
        if len(response[0]) and response[0][0] != 'ok':
2773
 
            raise errors.UnexpectedSmartServerResponse(response)
2774
 
        lines = response[1].read_body_bytes().splitlines()
2775
 
        return config.ConfigObj(lines, encoding='utf-8')
2776
 
 
2777
 
 
2778
 
class RemoteBranchConfig(RemoteConfig):
2779
 
    """A RemoteConfig for Branches."""
2780
 
 
2781
 
    def __init__(self, branch):
2782
 
        self._branch = branch
2783
 
 
2784
 
    def _get_configobj(self):
2785
 
        path = self._branch._remote_path()
2786
 
        response = self._branch._client.call_expecting_body(
2787
 
            'Branch.get_config_file', path)
2788
 
        return self._response_to_configobj(response)
2789
 
 
2790
 
    def set_option(self, value, name, section=None):
2791
 
        """Set the value associated with a named option.
2792
 
 
2793
 
        :param value: The value to set
2794
 
        :param name: The name of the value to set
2795
 
        :param section: The section the option is in (if any)
2796
 
        """
2797
 
        medium = self._branch._client._medium
2798
 
        if medium._is_remote_before((1, 14)):
2799
 
            return self._vfs_set_option(value, name, section)
2800
 
        if isinstance(value, dict):
2801
 
            if medium._is_remote_before((2, 2)):
2802
 
                return self._vfs_set_option(value, name, section)
2803
 
            return self._set_config_option_dict(value, name, section)
2804
 
        else:
2805
 
            return self._set_config_option(value, name, section)
2806
 
 
2807
 
    def _set_config_option(self, value, name, section):
2808
 
        try:
2809
 
            path = self._branch._remote_path()
2810
 
            response = self._branch._client.call('Branch.set_config_option',
2811
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
2812
 
                value.encode('utf8'), name, section or '')
2813
 
        except errors.UnknownSmartMethod:
2814
 
            medium = self._branch._client._medium
2815
 
            medium._remember_remote_is_before((1, 14))
2816
 
            return self._vfs_set_option(value, name, section)
2817
 
        if response != ():
2818
 
            raise errors.UnexpectedSmartServerResponse(response)
2819
 
 
2820
 
    def _serialize_option_dict(self, option_dict):
2821
 
        utf8_dict = {}
2822
 
        for key, value in option_dict.items():
2823
 
            if isinstance(key, unicode):
2824
 
                key = key.encode('utf8')
2825
 
            if isinstance(value, unicode):
2826
 
                value = value.encode('utf8')
2827
 
            utf8_dict[key] = value
2828
 
        return bencode.bencode(utf8_dict)
2829
 
 
2830
 
    def _set_config_option_dict(self, value, name, section):
2831
 
        try:
2832
 
            path = self._branch._remote_path()
2833
 
            serialised_dict = self._serialize_option_dict(value)
2834
 
            response = self._branch._client.call(
2835
 
                'Branch.set_config_option_dict',
2836
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
2837
 
                serialised_dict, name, section or '')
2838
 
        except errors.UnknownSmartMethod:
2839
 
            medium = self._branch._client._medium
2840
 
            medium._remember_remote_is_before((2, 2))
2841
 
            return self._vfs_set_option(value, name, section)
2842
 
        if response != ():
2843
 
            raise errors.UnexpectedSmartServerResponse(response)
2844
 
 
2845
 
    def _real_object(self):
2846
 
        self._branch._ensure_real()
2847
 
        return self._branch._real_branch
2848
 
 
2849
 
    def _vfs_set_option(self, value, name, section=None):
2850
 
        return self._real_object()._get_config().set_option(
2851
 
            value, name, section)
2852
 
 
2853
 
 
2854
 
class RemoteBzrDirConfig(RemoteConfig):
2855
 
    """A RemoteConfig for BzrDirs."""
2856
 
 
2857
 
    def __init__(self, bzrdir):
2858
 
        self._bzrdir = bzrdir
2859
 
 
2860
 
    def _get_configobj(self):
2861
 
        medium = self._bzrdir._client._medium
2862
 
        verb = 'BzrDir.get_config_file'
2863
 
        if medium._is_remote_before((1, 15)):
2864
 
            raise errors.UnknownSmartMethod(verb)
2865
 
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2866
 
        response = self._bzrdir._call_expecting_body(
2867
 
            verb, path)
2868
 
        return self._response_to_configobj(response)
2869
 
 
2870
 
    def _vfs_get_option(self, name, section, default):
2871
 
        return self._real_object()._get_config().get_option(
2872
 
            name, section, default)
2873
 
 
2874
 
    def set_option(self, value, name, section=None):
2875
 
        """Set the value associated with a named option.
2876
 
 
2877
 
        :param value: The value to set
2878
 
        :param name: The name of the value to set
2879
 
        :param section: The section the option is in (if any)
2880
 
        """
2881
 
        return self._real_object()._get_config().set_option(
2882
 
            value, name, section)
2883
 
 
2884
 
    def _real_object(self):
2885
 
        self._bzrdir._ensure_real()
2886
 
        return self._bzrdir._real_bzrdir
2887
 
 
2888
 
 
2889
 
 
2890
2229
def _extract_tar(tar, to_dir):
2891
2230
    """Extract all the contents of a tarfile object.
2892
2231
 
2930
2269
                    'Missing key %r in context %r', key_err.args[0], context)
2931
2270
                raise err
2932
2271
 
2933
 
    if err.error_verb == 'IncompatibleRepositories':
2934
 
        raise errors.IncompatibleRepositories(err.error_args[0],
2935
 
            err.error_args[1], err.error_args[2])
2936
 
    elif err.error_verb == 'NoSuchRevision':
 
2272
    if err.error_verb == 'NoSuchRevision':
2937
2273
        raise NoSuchRevision(find('branch'), err.error_args[0])
2938
2274
    elif err.error_verb == 'nosuchrevision':
2939
2275
        raise NoSuchRevision(find('repository'), err.error_args[0])
2940
 
    elif err.error_verb == 'nobranch':
2941
 
        if len(err.error_args) >= 1:
2942
 
            extra = err.error_args[0]
2943
 
        else:
2944
 
            extra = None
2945
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2946
 
            detail=extra)
 
2276
    elif err.error_tuple == ('nobranch',):
 
2277
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2947
2278
    elif err.error_verb == 'norepository':
2948
2279
        raise errors.NoRepositoryPresent(find('bzrdir'))
2949
2280
    elif err.error_verb == 'LockContention':