~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-10 17:52:08 UTC
  • mfrom: (5021 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5023.
  • Revision ID: john@arbash-meinel.com-20100210175208-bubuwav4uqigu291
Merge bzr.dev 5021 to resolve NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import bz2
18
 
import zlib
19
18
 
20
19
from bzrlib import (
21
20
    bencode,
22
21
    branch,
23
 
    bzrdir as _mod_bzrdir,
 
22
    bzrdir,
24
23
    config,
25
 
    controldir,
26
24
    debug,
27
25
    errors,
28
 
    gpg,
29
26
    graph,
30
27
    lock,
31
28
    lockdir,
32
 
    osutils,
33
 
    registry,
34
 
    repository as _mod_repository,
 
29
    repository,
 
30
    revision,
35
31
    revision as _mod_revision,
36
 
    static_tuple,
37
32
    symbol_versioning,
38
 
    testament as _mod_testament,
39
 
    urlutils,
40
 
    vf_repository,
41
 
    vf_search,
42
 
    )
43
 
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
33
)
 
34
from bzrlib.branch import BranchReferenceFormat
 
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
44
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
45
37
from bzrlib.errors import (
46
38
    NoSuchRevision,
47
39
    SmartProtocolError,
48
40
    )
49
 
from bzrlib.i18n import gettext
50
 
from bzrlib.inventory import Inventory
51
41
from bzrlib.lockable_files import LockableFiles
52
42
from bzrlib.smart import client, vfs, repository as smart_repo
53
 
from bzrlib.smart.client import _SmartClient
54
 
from bzrlib.revision import NULL_REVISION
55
 
from bzrlib.revisiontree import InventoryRevisionTree
56
 
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
57
 
from bzrlib.serializer import format_registry as serializer_format_registry
58
 
from bzrlib.trace import mutter, note, warning, log_exception_quietly
59
 
 
60
 
 
61
 
_DEFAULT_SEARCH_DEPTH = 100
 
43
from bzrlib.revision import ensure_null, NULL_REVISION
 
44
from bzrlib.trace import mutter, note, warning
62
45
 
63
46
 
64
47
class _RpcHelper(object):
101
84
    return format
102
85
 
103
86
 
104
 
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
105
 
# does not have to be imported unless a remote format is involved.
106
 
 
107
 
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
108
 
    """Format representing bzrdirs accessed via a smart server"""
109
 
 
110
 
    supports_workingtrees = False
111
 
 
112
 
    def __init__(self):
113
 
        _mod_bzrdir.BzrDirMetaFormat1.__init__(self)
114
 
        # XXX: It's a bit ugly that the network name is here, because we'd
115
 
        # like to believe that format objects are stateless or at least
116
 
        # immutable,  However, we do at least avoid mutating the name after
117
 
        # it's returned.  See <https://bugs.launchpad.net/bzr/+bug/504102>
118
 
        self._network_name = None
119
 
 
120
 
    def __repr__(self):
121
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
122
 
            self._network_name)
123
 
 
124
 
    def get_format_description(self):
125
 
        if self._network_name:
126
 
            try:
127
 
                real_format = controldir.network_format_registry.get(
128
 
                        self._network_name)
129
 
            except KeyError:
130
 
                pass
131
 
            else:
132
 
                return 'Remote: ' + real_format.get_format_description()
133
 
        return 'bzr remote bzrdir'
134
 
 
135
 
    def get_format_string(self):
136
 
        raise NotImplementedError(self.get_format_string)
137
 
 
138
 
    def network_name(self):
139
 
        if self._network_name:
140
 
            return self._network_name
141
 
        else:
142
 
            raise AssertionError("No network name set.")
143
 
 
144
 
    def initialize_on_transport(self, transport):
145
 
        try:
146
 
            # hand off the request to the smart server
147
 
            client_medium = transport.get_smart_medium()
148
 
        except errors.NoSmartMedium:
149
 
            # TODO: lookup the local format from a server hint.
150
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
151
 
            return local_dir_format.initialize_on_transport(transport)
152
 
        client = _SmartClient(client_medium)
153
 
        path = client.remote_path_from_transport(transport)
154
 
        try:
155
 
            response = client.call('BzrDirFormat.initialize', path)
156
 
        except errors.ErrorFromSmartServer, err:
157
 
            _translate_error(err, path=path)
158
 
        if response[0] != 'ok':
159
 
            raise errors.SmartProtocolError('unexpected response code %s' % (response,))
160
 
        format = RemoteBzrDirFormat()
161
 
        self._supply_sub_formats_to(format)
162
 
        return RemoteBzrDir(transport, format)
163
 
 
164
 
    def parse_NoneTrueFalse(self, arg):
165
 
        if not arg:
166
 
            return None
167
 
        if arg == 'False':
168
 
            return False
169
 
        if arg == 'True':
170
 
            return True
171
 
        raise AssertionError("invalid arg %r" % arg)
172
 
 
173
 
    def _serialize_NoneTrueFalse(self, arg):
174
 
        if arg is False:
175
 
            return 'False'
176
 
        if arg:
177
 
            return 'True'
178
 
        return ''
179
 
 
180
 
    def _serialize_NoneString(self, arg):
181
 
        return arg or ''
182
 
 
183
 
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
184
 
        create_prefix=False, force_new_repo=False, stacked_on=None,
185
 
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
186
 
        shared_repo=False):
187
 
        try:
188
 
            # hand off the request to the smart server
189
 
            client_medium = transport.get_smart_medium()
190
 
        except errors.NoSmartMedium:
191
 
            do_vfs = True
192
 
        else:
193
 
            # Decline to open it if the server doesn't support our required
194
 
            # version (3) so that the VFS-based transport will do it.
195
 
            if client_medium.should_probe():
196
 
                try:
197
 
                    server_version = client_medium.protocol_version()
198
 
                    if server_version != '2':
199
 
                        do_vfs = True
200
 
                    else:
201
 
                        do_vfs = False
202
 
                except errors.SmartProtocolError:
203
 
                    # Apparently there's no usable smart server there, even though
204
 
                    # the medium supports the smart protocol.
205
 
                    do_vfs = True
206
 
            else:
207
 
                do_vfs = False
208
 
        if not do_vfs:
209
 
            client = _SmartClient(client_medium)
210
 
            path = client.remote_path_from_transport(transport)
211
 
            if client_medium._is_remote_before((1, 16)):
212
 
                do_vfs = True
213
 
        if do_vfs:
214
 
            # TODO: lookup the local format from a server hint.
215
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
216
 
            self._supply_sub_formats_to(local_dir_format)
217
 
            return local_dir_format.initialize_on_transport_ex(transport,
218
 
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
219
 
                force_new_repo=force_new_repo, stacked_on=stacked_on,
220
 
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
221
 
                make_working_trees=make_working_trees, shared_repo=shared_repo,
222
 
                vfs_only=True)
223
 
        return self._initialize_on_transport_ex_rpc(client, path, transport,
224
 
            use_existing_dir, create_prefix, force_new_repo, stacked_on,
225
 
            stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
226
 
 
227
 
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
228
 
        use_existing_dir, create_prefix, force_new_repo, stacked_on,
229
 
        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
230
 
        args = []
231
 
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
232
 
        args.append(self._serialize_NoneTrueFalse(create_prefix))
233
 
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
234
 
        args.append(self._serialize_NoneString(stacked_on))
235
 
        # stack_on_pwd is often/usually our transport
236
 
        if stack_on_pwd:
237
 
            try:
238
 
                stack_on_pwd = transport.relpath(stack_on_pwd)
239
 
                if not stack_on_pwd:
240
 
                    stack_on_pwd = '.'
241
 
            except errors.PathNotChild:
242
 
                pass
243
 
        args.append(self._serialize_NoneString(stack_on_pwd))
244
 
        args.append(self._serialize_NoneString(repo_format_name))
245
 
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
246
 
        args.append(self._serialize_NoneTrueFalse(shared_repo))
247
 
        request_network_name = self._network_name or \
248
 
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
249
 
        try:
250
 
            response = client.call('BzrDirFormat.initialize_ex_1.16',
251
 
                request_network_name, path, *args)
252
 
        except errors.UnknownSmartMethod:
253
 
            client._medium._remember_remote_is_before((1,16))
254
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
255
 
            self._supply_sub_formats_to(local_dir_format)
256
 
            return local_dir_format.initialize_on_transport_ex(transport,
257
 
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
258
 
                force_new_repo=force_new_repo, stacked_on=stacked_on,
259
 
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
260
 
                make_working_trees=make_working_trees, shared_repo=shared_repo,
261
 
                vfs_only=True)
262
 
        except errors.ErrorFromSmartServer, err:
263
 
            _translate_error(err, path=path)
264
 
        repo_path = response[0]
265
 
        bzrdir_name = response[6]
266
 
        require_stacking = response[7]
267
 
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
268
 
        format = RemoteBzrDirFormat()
269
 
        format._network_name = bzrdir_name
270
 
        self._supply_sub_formats_to(format)
271
 
        bzrdir = RemoteBzrDir(transport, format, _client=client)
272
 
        if repo_path:
273
 
            repo_format = response_tuple_to_repo_format(response[1:])
274
 
            if repo_path == '.':
275
 
                repo_path = ''
276
 
            if repo_path:
277
 
                repo_bzrdir_format = RemoteBzrDirFormat()
278
 
                repo_bzrdir_format._network_name = response[5]
279
 
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
280
 
                    repo_bzrdir_format)
281
 
            else:
282
 
                repo_bzr = bzrdir
283
 
            final_stack = response[8] or None
284
 
            final_stack_pwd = response[9] or None
285
 
            if final_stack_pwd:
286
 
                final_stack_pwd = urlutils.join(
287
 
                    transport.base, final_stack_pwd)
288
 
            remote_repo = RemoteRepository(repo_bzr, repo_format)
289
 
            if len(response) > 10:
290
 
                # Updated server verb that locks remotely.
291
 
                repo_lock_token = response[10] or None
292
 
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
293
 
                if repo_lock_token:
294
 
                    remote_repo.dont_leave_lock_in_place()
295
 
            else:
296
 
                remote_repo.lock_write()
297
 
            policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
298
 
                final_stack_pwd, require_stacking)
299
 
            policy.acquire_repository()
300
 
        else:
301
 
            remote_repo = None
302
 
            policy = None
303
 
        bzrdir._format.set_branch_format(self.get_branch_format())
304
 
        if require_stacking:
305
 
            # The repo has already been created, but we need to make sure that
306
 
            # we'll make a stackable branch.
307
 
            bzrdir._format.require_stacking(_skip_repo=True)
308
 
        return remote_repo, bzrdir, require_stacking, policy
309
 
 
310
 
    def _open(self, transport):
311
 
        return RemoteBzrDir(transport, self)
312
 
 
313
 
    def __eq__(self, other):
314
 
        if not isinstance(other, RemoteBzrDirFormat):
315
 
            return False
316
 
        return self.get_format_description() == other.get_format_description()
317
 
 
318
 
    def __return_repository_format(self):
319
 
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
320
 
        # repository format has been asked for, tell the RemoteRepositoryFormat
321
 
        # that it should use that for init() etc.
322
 
        result = RemoteRepositoryFormat()
323
 
        custom_format = getattr(self, '_repository_format', None)
324
 
        if custom_format:
325
 
            if isinstance(custom_format, RemoteRepositoryFormat):
326
 
                return custom_format
327
 
            else:
328
 
                # We will use the custom format to create repositories over the
329
 
                # wire; expose its details like rich_root_data for code to
330
 
                # query
331
 
                result._custom_format = custom_format
332
 
        return result
333
 
 
334
 
    def get_branch_format(self):
335
 
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
336
 
        if not isinstance(result, RemoteBranchFormat):
337
 
            new_result = RemoteBranchFormat()
338
 
            new_result._custom_format = result
339
 
            # cache the result
340
 
            self.set_branch_format(new_result)
341
 
            result = new_result
342
 
        return result
343
 
 
344
 
    repository_format = property(__return_repository_format,
345
 
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
346
 
 
347
 
 
348
 
class RemoteControlStore(config.IniFileStore):
349
 
    """Control store which attempts to use HPSS calls to retrieve control store.
350
 
 
351
 
    Note that this is specific to bzr-based formats.
352
 
    """
353
 
 
354
 
    def __init__(self, bzrdir):
355
 
        super(RemoteControlStore, self).__init__()
356
 
        self.bzrdir = bzrdir
357
 
        self._real_store = None
358
 
 
359
 
    def lock_write(self, token=None):
360
 
        self._ensure_real()
361
 
        return self._real_store.lock_write(token)
362
 
 
363
 
    def unlock(self):
364
 
        self._ensure_real()
365
 
        return self._real_store.unlock()
366
 
 
367
 
    @needs_write_lock
368
 
    def save(self):
369
 
        # We need to be able to override the undecorated implementation
370
 
        self.save_without_locking()
371
 
 
372
 
    def save_without_locking(self):
373
 
        super(RemoteControlStore, self).save()
374
 
 
375
 
    def _ensure_real(self):
376
 
        self.bzrdir._ensure_real()
377
 
        if self._real_store is None:
378
 
            self._real_store = config.ControlStore(self.bzrdir)
379
 
 
380
 
    def external_url(self):
381
 
        return self.bzrdir.user_url
382
 
 
383
 
    def _load_content(self):
384
 
        medium = self.bzrdir._client._medium
385
 
        path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
386
 
        try:
387
 
            response, handler = self.bzrdir._call_expecting_body(
388
 
                'BzrDir.get_config_file', path)
389
 
        except errors.UnknownSmartMethod:
390
 
            self._ensure_real()
391
 
            return self._real_store._load_content()
392
 
        if len(response) and response[0] != 'ok':
393
 
            raise errors.UnexpectedSmartServerResponse(response)
394
 
        return handler.read_body_bytes()
395
 
 
396
 
    def _save_content(self, content):
397
 
        # FIXME JRV 2011-11-22: Ideally this should use a
398
 
        # HPSS call too, but at the moment it is not possible
399
 
        # to write lock control directories.
400
 
        self._ensure_real()
401
 
        return self._real_store._save_content(content)
402
 
 
403
 
 
404
 
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
 
87
# Note: RemoteBzrDirFormat is in bzrdir.py
 
88
 
 
89
class RemoteBzrDir(BzrDir, _RpcHelper):
405
90
    """Control directory on a remote server, accessed via bzr:// or similar."""
406
91
 
407
92
    def __init__(self, transport, format, _client=None, _force_probe=False):
410
95
        :param _client: Private parameter for testing. Disables probing and the
411
96
            use of a real bzrdir.
412
97
        """
413
 
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
98
        BzrDir.__init__(self, transport, format)
414
99
        # this object holds a delegated bzrdir that uses file-level operations
415
100
        # to talk to the other side
416
101
        self._real_bzrdir = None
476
161
                import traceback
477
162
                warning('VFS BzrDir access triggered\n%s',
478
163
                    ''.join(traceback.format_stack()))
479
 
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
 
164
            self._real_bzrdir = BzrDir.open_from_transport(
480
165
                self.root_transport, _server_formats=False)
481
166
            self._format._network_name = \
482
167
                self._real_bzrdir._format.network_name()
488
173
        # Prevent aliasing problems in the next_open_branch_result cache.
489
174
        # See create_branch for rationale.
490
175
        self._next_open_branch_result = None
491
 
        return _mod_bzrdir.BzrDir.break_lock(self)
 
176
        return BzrDir.break_lock(self)
492
177
 
493
178
    def _vfs_cloning_metadir(self, require_stacking=False):
494
179
        self._ensure_real()
525
210
        if len(branch_info) != 2:
526
211
            raise errors.UnexpectedSmartServerResponse(response)
527
212
        branch_ref, branch_name = branch_info
528
 
        try:
529
 
            format = controldir.network_format_registry.get(control_name)
530
 
        except KeyError:
531
 
            raise errors.UnknownFormatError(kind='control', format=control_name)
532
 
 
 
213
        format = bzrdir.network_format_registry.get(control_name)
533
214
        if repo_name:
534
 
            try:
535
 
                format.repository_format = _mod_repository.network_format_registry.get(
536
 
                    repo_name)
537
 
            except KeyError:
538
 
                raise errors.UnknownFormatError(kind='repository',
539
 
                    format=repo_name)
 
215
            format.repository_format = repository.network_format_registry.get(
 
216
                repo_name)
540
217
        if branch_ref == 'ref':
541
218
            # XXX: we need possible_transports here to avoid reopening the
542
219
            # connection to the referenced location
543
 
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
 
220
            ref_bzrdir = BzrDir.open(branch_name)
544
221
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
545
222
            format.set_branch_format(branch_format)
546
223
        elif branch_ref == 'branch':
547
224
            if branch_name:
548
 
                try:
549
 
                    branch_format = branch.network_format_registry.get(
550
 
                        branch_name)
551
 
                except KeyError:
552
 
                    raise errors.UnknownFormatError(kind='branch',
553
 
                        format=branch_name)
554
 
                format.set_branch_format(branch_format)
 
225
                format.set_branch_format(
 
226
                    branch.network_format_registry.get(branch_name))
555
227
        else:
556
228
            raise errors.UnexpectedSmartServerResponse(response)
557
229
        return format
567
239
 
568
240
    def destroy_repository(self):
569
241
        """See BzrDir.destroy_repository"""
570
 
        path = self._path_for_remote_call(self._client)
571
 
        try:
572
 
            response = self._call('BzrDir.destroy_repository', path)
573
 
        except errors.UnknownSmartMethod:
574
 
            self._ensure_real()
575
 
            self._real_bzrdir.destroy_repository()
576
 
            return
577
 
        if response[0] != 'ok':
578
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
242
        self._ensure_real()
 
243
        self._real_bzrdir.destroy_repository()
579
244
 
580
 
    def create_branch(self, name=None, repository=None,
581
 
                      append_revisions_only=None):
 
245
    def create_branch(self):
582
246
        # as per meta1 formats - just delegate to the format object which may
583
247
        # be parameterised.
584
 
        real_branch = self._format.get_branch_format().initialize(self,
585
 
            name=name, repository=repository,
586
 
            append_revisions_only=append_revisions_only)
 
248
        real_branch = self._format.get_branch_format().initialize(self)
587
249
        if not isinstance(real_branch, RemoteBranch):
588
 
            if not isinstance(repository, RemoteRepository):
589
 
                raise AssertionError(
590
 
                    'need a RemoteRepository to use with RemoteBranch, got %r'
591
 
                    % (repository,))
592
 
            result = RemoteBranch(self, repository, real_branch, name=name)
 
250
            result = RemoteBranch(self, self.find_repository(), real_branch)
593
251
        else:
594
252
            result = real_branch
595
253
        # BzrDir.clone_on_transport() uses the result of create_branch but does
601
259
        self._next_open_branch_result = result
602
260
        return result
603
261
 
604
 
    def destroy_branch(self, name=None):
 
262
    def destroy_branch(self):
605
263
        """See BzrDir.destroy_branch"""
606
 
        path = self._path_for_remote_call(self._client)
607
 
        try:
608
 
            if name is not None:
609
 
                args = (name, )
610
 
            else:
611
 
                args = ()
612
 
            response = self._call('BzrDir.destroy_branch', path, *args)
613
 
        except errors.UnknownSmartMethod:
614
 
            self._ensure_real()
615
 
            self._real_bzrdir.destroy_branch(name=name)
616
 
            self._next_open_branch_result = None
617
 
            return
 
264
        self._ensure_real()
 
265
        self._real_bzrdir.destroy_branch()
618
266
        self._next_open_branch_result = None
619
 
        if response[0] != 'ok':
620
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
621
267
 
622
 
    def create_workingtree(self, revision_id=None, from_branch=None,
623
 
        accelerator_tree=None, hardlink=False):
 
268
    def create_workingtree(self, revision_id=None, from_branch=None):
624
269
        raise errors.NotLocalUrl(self.transport.base)
625
270
 
626
 
    def find_branch_format(self, name=None):
 
271
    def find_branch_format(self):
627
272
        """Find the branch 'format' for this bzrdir.
628
273
 
629
274
        This might be a synthetic object for e.g. RemoteBranch and SVN.
630
275
        """
631
 
        b = self.open_branch(name=name)
 
276
        b = self.open_branch()
632
277
        return b._format
633
278
 
634
 
    def get_branch_reference(self, name=None):
 
279
    def get_branch_reference(self):
635
280
        """See BzrDir.get_branch_reference()."""
636
 
        if name is not None:
637
 
            # XXX JRV20100304: Support opening colocated branches
638
 
            raise errors.NoColocatedBranchSupport(self)
639
281
        response = self._get_branch_reference()
640
282
        if response[0] == 'ref':
641
283
            return response[1]
672
314
            raise errors.UnexpectedSmartServerResponse(response)
673
315
        return response
674
316
 
675
 
    def _get_tree_branch(self, name=None):
 
317
    def _get_tree_branch(self):
676
318
        """See BzrDir._get_tree_branch()."""
677
 
        return None, self.open_branch(name=name)
 
319
        return None, self.open_branch()
678
320
 
679
 
    def open_branch(self, name=None, unsupported=False,
680
 
                    ignore_fallbacks=False, possible_transports=None):
681
 
        if unsupported:
 
321
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
 
322
        if _unsupported:
682
323
            raise NotImplementedError('unsupported flag support not implemented yet.')
683
324
        if self._next_open_branch_result is not None:
684
325
            # See create_branch for details.
689
330
        if response[0] == 'ref':
690
331
            # a branch reference, use the existing BranchReference logic.
691
332
            format = BranchReferenceFormat()
692
 
            return format.open(self, name=name, _found=True,
693
 
                location=response[1], ignore_fallbacks=ignore_fallbacks,
694
 
                possible_transports=possible_transports)
 
333
            return format.open(self, _found=True, location=response[1],
 
334
                ignore_fallbacks=ignore_fallbacks)
695
335
        branch_format_name = response[1]
696
336
        if not branch_format_name:
697
337
            branch_format_name = None
698
338
        format = RemoteBranchFormat(network_name=branch_format_name)
699
339
        return RemoteBranch(self, self.find_repository(), format=format,
700
 
            setup_stacking=not ignore_fallbacks, name=name,
701
 
            possible_transports=possible_transports)
 
340
            setup_stacking=not ignore_fallbacks)
702
341
 
703
342
    def _open_repo_v1(self, path):
704
343
        verb = 'BzrDir.find_repository'
767
406
 
768
407
    def has_workingtree(self):
769
408
        if self._has_working_tree is None:
770
 
            path = self._path_for_remote_call(self._client)
771
 
            try:
772
 
                response = self._call('BzrDir.has_workingtree', path)
773
 
            except errors.UnknownSmartMethod:
774
 
                self._ensure_real()
775
 
                self._has_working_tree = self._real_bzrdir.has_workingtree()
776
 
            else:
777
 
                if response[0] not in ('yes', 'no'):
778
 
                    raise SmartProtocolError('unexpected response code %s' % (response,))
779
 
                self._has_working_tree = (response[0] == 'yes')
 
409
            self._ensure_real()
 
410
            self._has_working_tree = self._real_bzrdir.has_workingtree()
780
411
        return self._has_working_tree
781
412
 
782
413
    def open_workingtree(self, recommend_upgrade=True):
787
418
 
788
419
    def _path_for_remote_call(self, client):
789
420
        """Return the path to be used for this bzrdir in a remote call."""
790
 
        return urlutils.split_segment_parameters_raw(
791
 
            client.remote_path_from_transport(self.root_transport))[0]
 
421
        return client.remote_path_from_transport(self.root_transport)
792
422
 
793
 
    def get_branch_transport(self, branch_format, name=None):
 
423
    def get_branch_transport(self, branch_format):
794
424
        self._ensure_real()
795
 
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
425
        return self._real_bzrdir.get_branch_transport(branch_format)
796
426
 
797
427
    def get_repository_transport(self, repository_format):
798
428
        self._ensure_real()
806
436
        """Upgrading of remote bzrdirs is not supported yet."""
807
437
        return False
808
438
 
809
 
    def needs_format_conversion(self, format):
 
439
    def needs_format_conversion(self, format=None):
810
440
        """Upgrading of remote bzrdirs is not supported yet."""
 
441
        if format is None:
 
442
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
443
                % 'needs_format_conversion(format=None)')
811
444
        return False
812
445
 
 
446
    def clone(self, url, revision_id=None, force_new_repo=False,
 
447
              preserve_stacking=False):
 
448
        self._ensure_real()
 
449
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
450
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
451
 
813
452
    def _get_config(self):
814
453
        return RemoteBzrDirConfig(self)
815
454
 
816
 
    def _get_config_store(self):
817
 
        return RemoteControlStore(self)
818
 
 
819
 
 
820
 
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
 
455
 
 
456
class RemoteRepositoryFormat(repository.RepositoryFormat):
821
457
    """Format for repositories accessed over a _SmartClient.
822
458
 
823
459
    Instances of this repository are represented by RemoteRepository
838
474
    """
839
475
 
840
476
    _matchingbzrdir = RemoteBzrDirFormat()
841
 
    supports_full_versioned_files = True
842
 
    supports_leaving_lock = True
843
477
 
844
478
    def __init__(self):
845
 
        _mod_repository.RepositoryFormat.__init__(self)
 
479
        repository.RepositoryFormat.__init__(self)
846
480
        self._custom_format = None
847
481
        self._network_name = None
848
482
        self._creating_bzrdir = None
849
 
        self._revision_graph_can_have_wrong_parents = None
850
483
        self._supports_chks = None
851
484
        self._supports_external_lookups = None
852
485
        self._supports_tree_reference = None
853
 
        self._supports_funky_characters = None
854
 
        self._supports_nesting_repositories = None
855
486
        self._rich_root_data = None
856
487
 
857
488
    def __repr__(self):
886
517
        return self._supports_external_lookups
887
518
 
888
519
    @property
889
 
    def supports_funky_characters(self):
890
 
        if self._supports_funky_characters is None:
891
 
            self._ensure_real()
892
 
            self._supports_funky_characters = \
893
 
                self._custom_format.supports_funky_characters
894
 
        return self._supports_funky_characters
895
 
 
896
 
    @property
897
 
    def supports_nesting_repositories(self):
898
 
        if self._supports_nesting_repositories is None:
899
 
            self._ensure_real()
900
 
            self._supports_nesting_repositories = \
901
 
                self._custom_format.supports_nesting_repositories
902
 
        return self._supports_nesting_repositories
903
 
 
904
 
    @property
905
520
    def supports_tree_reference(self):
906
521
        if self._supports_tree_reference is None:
907
522
            self._ensure_real()
909
524
                self._custom_format.supports_tree_reference
910
525
        return self._supports_tree_reference
911
526
 
912
 
    @property
913
 
    def revision_graph_can_have_wrong_parents(self):
914
 
        if self._revision_graph_can_have_wrong_parents is None:
915
 
            self._ensure_real()
916
 
            self._revision_graph_can_have_wrong_parents = \
917
 
                self._custom_format.revision_graph_can_have_wrong_parents
918
 
        return self._revision_graph_can_have_wrong_parents
919
 
 
920
527
    def _vfs_initialize(self, a_bzrdir, shared):
921
528
        """Helper for common code in initialize."""
922
529
        if self._custom_format:
957
564
            network_name = self._network_name
958
565
        else:
959
566
            # Select the current bzrlib default and ask for that.
960
 
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
 
567
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
961
568
            reference_format = reference_bzrdir_format.repository_format
962
569
            network_name = reference_format.network_name()
963
570
        # 2) try direct creation via RPC
989
596
 
990
597
    def _ensure_real(self):
991
598
        if self._custom_format is None:
992
 
            try:
993
 
                self._custom_format = _mod_repository.network_format_registry.get(
994
 
                    self._network_name)
995
 
            except KeyError:
996
 
                raise errors.UnknownFormatError(kind='repository',
997
 
                    format=self._network_name)
 
599
            self._custom_format = repository.network_format_registry.get(
 
600
                self._network_name)
998
601
 
999
602
    @property
1000
603
    def _fetch_order(self):
1035
638
        return self._custom_format._serializer
1036
639
 
1037
640
 
1038
 
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1039
 
        lock._RelockDebugMixin):
 
641
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
1040
642
    """Repository accessed over rpc.
1041
643
 
1042
644
    For the moment most operations are performed using local transport-backed
1066
668
        self._format = format
1067
669
        self._lock_mode = None
1068
670
        self._lock_token = None
1069
 
        self._write_group_tokens = None
1070
671
        self._lock_count = 0
1071
672
        self._leave_lock = False
1072
673
        # Cache of revision parents; misses are cached during read locks, and
1086
687
        # Additional places to query for data.
1087
688
        self._fallback_repositories = []
1088
689
 
1089
 
    @property
1090
 
    def user_transport(self):
1091
 
        return self.bzrdir.user_transport
1092
 
 
1093
 
    @property
1094
 
    def control_transport(self):
1095
 
        # XXX: Normally you shouldn't directly get at the remote repository
1096
 
        # transport, but I'm not sure it's worth making this method
1097
 
        # optional -- mbp 2010-04-21
1098
 
        return self.bzrdir.get_repository_transport(None)
1099
 
 
1100
690
    def __str__(self):
1101
691
        return "%s(%s)" % (self.__class__.__name__, self.base)
1102
692
 
1112
702
 
1113
703
        :param suppress_errors: see Repository.abort_write_group.
1114
704
        """
1115
 
        if self._real_repository:
1116
 
            self._ensure_real()
1117
 
            return self._real_repository.abort_write_group(
1118
 
                suppress_errors=suppress_errors)
1119
 
        if not self.is_in_write_group():
1120
 
            if suppress_errors:
1121
 
                mutter('(suppressed) not in write group')
1122
 
                return
1123
 
            raise errors.BzrError("not in write group")
1124
 
        path = self.bzrdir._path_for_remote_call(self._client)
1125
 
        try:
1126
 
            response = self._call('Repository.abort_write_group', path,
1127
 
                self._lock_token, self._write_group_tokens)
1128
 
        except Exception, exc:
1129
 
            self._write_group = None
1130
 
            if not suppress_errors:
1131
 
                raise
1132
 
            mutter('abort_write_group failed')
1133
 
            log_exception_quietly()
1134
 
            note(gettext('bzr: ERROR (ignored): %s'), exc)
1135
 
        else:
1136
 
            if response != ('ok', ):
1137
 
                raise errors.UnexpectedSmartServerResponse(response)
1138
 
            self._write_group_tokens = None
 
705
        self._ensure_real()
 
706
        return self._real_repository.abort_write_group(
 
707
            suppress_errors=suppress_errors)
1139
708
 
1140
709
    @property
1141
710
    def chk_bytes(self):
1155
724
        for older plugins that don't use e.g. the CommitBuilder
1156
725
        facility.
1157
726
        """
1158
 
        if self._real_repository:
1159
 
            self._ensure_real()
1160
 
            return self._real_repository.commit_write_group()
1161
 
        if not self.is_in_write_group():
1162
 
            raise errors.BzrError("not in write group")
1163
 
        path = self.bzrdir._path_for_remote_call(self._client)
1164
 
        response = self._call('Repository.commit_write_group', path,
1165
 
            self._lock_token, self._write_group_tokens)
1166
 
        if response != ('ok', ):
1167
 
            raise errors.UnexpectedSmartServerResponse(response)
1168
 
        self._write_group_tokens = None
 
727
        self._ensure_real()
 
728
        return self._real_repository.commit_write_group()
1169
729
 
1170
730
    def resume_write_group(self, tokens):
1171
 
        if self._real_repository:
1172
 
            return self._real_repository.resume_write_group(tokens)
1173
 
        path = self.bzrdir._path_for_remote_call(self._client)
1174
 
        try:
1175
 
            response = self._call('Repository.check_write_group', path,
1176
 
               self._lock_token, tokens)
1177
 
        except errors.UnknownSmartMethod:
1178
 
            self._ensure_real()
1179
 
            return self._real_repository.resume_write_group(tokens)
1180
 
        if response != ('ok', ):
1181
 
            raise errors.UnexpectedSmartServerResponse(response)
1182
 
        self._write_group_tokens = tokens
 
731
        self._ensure_real()
 
732
        return self._real_repository.resume_write_group(tokens)
1183
733
 
1184
734
    def suspend_write_group(self):
1185
 
        if self._real_repository:
1186
 
            return self._real_repository.suspend_write_group()
1187
 
        ret = self._write_group_tokens or []
1188
 
        self._write_group_tokens = None
1189
 
        return ret
 
735
        self._ensure_real()
 
736
        return self._real_repository.suspend_write_group()
1190
737
 
1191
738
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1192
739
        self._ensure_real()
1253
800
    def find_text_key_references(self):
1254
801
        """Find the text key references within the repository.
1255
802
 
 
803
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
804
        revision_ids. Each altered file-ids has the exact revision_ids that
 
805
        altered it listed explicitly.
1256
806
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1257
807
            to whether they were referred to by the inventory of the
1258
808
            revision_id that they contain. The inventory texts from all present
1276
826
        """Private method for using with old (< 1.2) servers to fallback."""
1277
827
        if revision_id is None:
1278
828
            revision_id = ''
1279
 
        elif _mod_revision.is_null(revision_id):
 
829
        elif revision.is_null(revision_id):
1280
830
            return {}
1281
831
 
1282
832
        path = self.bzrdir._path_for_remote_call(self._client)
1306
856
        return RemoteStreamSource(self, to_format)
1307
857
 
1308
858
    @needs_read_lock
1309
 
    def get_file_graph(self):
1310
 
        return graph.Graph(self.texts)
1311
 
 
1312
 
    @needs_read_lock
1313
859
    def has_revision(self, revision_id):
1314
860
        """True if this repository has a copy of the revision."""
1315
861
        # Copy of bzrlib.repository.Repository.has_revision
1332
878
    def _has_same_fallbacks(self, other_repo):
1333
879
        """Returns true if the repositories have the same fallbacks."""
1334
880
        # XXX: copied from Repository; it should be unified into a base class
1335
 
        # <https://bugs.launchpad.net/bzr/+bug/401622>
 
881
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
1336
882
        my_fb = self._fallback_repositories
1337
883
        other_fb = other_repo._fallback_repositories
1338
884
        if len(my_fb) != len(other_fb):
1354
900
        parents_provider = self._make_parents_provider(other_repository)
1355
901
        return graph.Graph(parents_provider)
1356
902
 
1357
 
    @needs_read_lock
1358
 
    def get_known_graph_ancestry(self, revision_ids):
1359
 
        """Return the known graph for a set of revision ids and their ancestors.
1360
 
        """
1361
 
        st = static_tuple.StaticTuple
1362
 
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
1363
 
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1364
 
        return graph.GraphThunkIdsToKeys(known_graph)
1365
 
 
1366
903
    def gather_stats(self, revid=None, committers=None):
1367
904
        """See Repository.gather_stats()."""
1368
905
        path = self.bzrdir._path_for_remote_call(self._client)
1369
906
        # revid can be None to indicate no revisions, not just NULL_REVISION
1370
 
        if revid is None or _mod_revision.is_null(revid):
 
907
        if revid is None or revision.is_null(revid):
1371
908
            fmt_revid = ''
1372
909
        else:
1373
910
            fmt_revid = revid
1402
939
 
1403
940
    def get_physical_lock_status(self):
1404
941
        """See Repository.get_physical_lock_status()."""
1405
 
        path = self.bzrdir._path_for_remote_call(self._client)
1406
 
        try:
1407
 
            response = self._call('Repository.get_physical_lock_status', path)
1408
 
        except errors.UnknownSmartMethod:
1409
 
            self._ensure_real()
1410
 
            return self._real_repository.get_physical_lock_status()
1411
 
        if response[0] not in ('yes', 'no'):
1412
 
            raise errors.UnexpectedSmartServerResponse(response)
1413
 
        return (response[0] == 'yes')
 
942
        # should be an API call to the server.
 
943
        self._ensure_real()
 
944
        return self._real_repository.get_physical_lock_status()
1414
945
 
1415
946
    def is_in_write_group(self):
1416
947
        """Return True if there is an open write group.
1417
948
 
1418
949
        write groups are only applicable locally for the smart server..
1419
950
        """
1420
 
        if self._write_group_tokens is not None:
1421
 
            return True
1422
951
        if self._real_repository:
1423
952
            return self._real_repository.is_in_write_group()
1424
953
 
1442
971
        pass
1443
972
 
1444
973
    def lock_read(self):
1445
 
        """Lock the repository for read operations.
1446
 
 
1447
 
        :return: A bzrlib.lock.LogicalLockResult.
1448
 
        """
1449
974
        # wrong eventually - want a local lock cache context
1450
975
        if not self._lock_mode:
1451
976
            self._note_lock('r')
1458
983
                repo.lock_read()
1459
984
        else:
1460
985
            self._lock_count += 1
1461
 
        return lock.LogicalLockResult(self.unlock)
1462
986
 
1463
987
    def _remote_lock_write(self, token):
1464
988
        path = self.bzrdir._path_for_remote_call(self._client)
1504
1028
            raise errors.ReadOnlyError(self)
1505
1029
        else:
1506
1030
            self._lock_count += 1
1507
 
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
1031
        return self._lock_token or None
1508
1032
 
1509
1033
    def leave_lock_in_place(self):
1510
1034
        if not self._lock_token:
1559
1083
            self._real_repository.lock_write(self._lock_token)
1560
1084
        elif self._lock_mode == 'r':
1561
1085
            self._real_repository.lock_read()
1562
 
        if self._write_group_tokens is not None:
1563
 
            # if we are already in a write group, resume it
1564
 
            self._real_repository.resume_write_group(self._write_group_tokens)
1565
 
            self._write_group_tokens = None
1566
1086
 
1567
1087
    def start_write_group(self):
1568
1088
        """Start a write group on the decorated repository.
1572
1092
        for older plugins that don't use e.g. the CommitBuilder
1573
1093
        facility.
1574
1094
        """
1575
 
        if self._real_repository:
1576
 
            self._ensure_real()
1577
 
            return self._real_repository.start_write_group()
1578
 
        if not self.is_write_locked():
1579
 
            raise errors.NotWriteLocked(self)
1580
 
        if self._write_group_tokens is not None:
1581
 
            raise errors.BzrError('already in a write group')
1582
 
        path = self.bzrdir._path_for_remote_call(self._client)
1583
 
        try:
1584
 
            response = self._call('Repository.start_write_group', path,
1585
 
                self._lock_token)
1586
 
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1587
 
            self._ensure_real()
1588
 
            return self._real_repository.start_write_group()
1589
 
        if response[0] != 'ok':
1590
 
            raise errors.UnexpectedSmartServerResponse(response)
1591
 
        self._write_group_tokens = response[1]
 
1095
        self._ensure_real()
 
1096
        return self._real_repository.start_write_group()
1592
1097
 
1593
1098
    def _unlock(self, token):
1594
1099
        path = self.bzrdir._path_for_remote_call(self._client)
1621
1126
            # This is just to let the _real_repository stay up to date.
1622
1127
            if self._real_repository is not None:
1623
1128
                self._real_repository.unlock()
1624
 
            elif self._write_group_tokens is not None:
1625
 
                self.abort_write_group()
1626
1129
        finally:
1627
1130
            # The rpc-level lock should be released even if there was a
1628
1131
            # problem releasing the vfs-based lock.
1640
1143
 
1641
1144
    def break_lock(self):
1642
1145
        # should hand off to the network
1643
 
        path = self.bzrdir._path_for_remote_call(self._client)
1644
 
        try:
1645
 
            response = self._call("Repository.break_lock", path)
1646
 
        except errors.UnknownSmartMethod:
1647
 
            self._ensure_real()
1648
 
            return self._real_repository.break_lock()
1649
 
        if response != ('ok',):
1650
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1146
        self._ensure_real()
 
1147
        return self._real_repository.break_lock()
1651
1148
 
1652
1149
    def _get_tarball(self, compression):
1653
1150
        """Return a TemporaryFile containing a repository tarball.
1671
1168
            return t
1672
1169
        raise errors.UnexpectedSmartServerResponse(response)
1673
1170
 
1674
 
    @needs_read_lock
1675
1171
    def sprout(self, to_bzrdir, revision_id=None):
1676
 
        """Create a descendent repository for new development.
1677
 
 
1678
 
        Unlike clone, this does not copy the settings of the repository.
1679
 
        """
1680
 
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
1172
        # TODO: Option to control what format is created?
 
1173
        self._ensure_real()
 
1174
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
1175
                                                             shared=False)
1681
1176
        dest_repo.fetch(self, revision_id=revision_id)
1682
1177
        return dest_repo
1683
1178
 
1684
 
    def _create_sprouting_repo(self, a_bzrdir, shared):
1685
 
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1686
 
            # use target default format.
1687
 
            dest_repo = a_bzrdir.create_repository()
1688
 
        else:
1689
 
            # Most control formats need the repository to be specifically
1690
 
            # created, but on some old all-in-one formats it's not needed
1691
 
            try:
1692
 
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1693
 
            except errors.UninitializableFormat:
1694
 
                dest_repo = a_bzrdir.open_repository()
1695
 
        return dest_repo
1696
 
 
1697
1179
    ### These methods are just thin shims to the VFS object for now.
1698
1180
 
1699
 
    @needs_read_lock
1700
1181
    def revision_tree(self, revision_id):
1701
 
        revision_id = _mod_revision.ensure_null(revision_id)
1702
 
        if revision_id == _mod_revision.NULL_REVISION:
1703
 
            return InventoryRevisionTree(self,
1704
 
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
1705
 
        else:
1706
 
            return list(self.revision_trees([revision_id]))[0]
 
1182
        self._ensure_real()
 
1183
        return self._real_repository.revision_tree(revision_id)
1707
1184
 
1708
1185
    def get_serializer_format(self):
1709
 
        path = self.bzrdir._path_for_remote_call(self._client)
1710
 
        try:
1711
 
            response = self._call('VersionedFileRepository.get_serializer_format',
1712
 
                path)
1713
 
        except errors.UnknownSmartMethod:
1714
 
            self._ensure_real()
1715
 
            return self._real_repository.get_serializer_format()
1716
 
        if response[0] != 'ok':
1717
 
            raise errors.UnexpectedSmartServerResponse(response)
1718
 
        return response[1]
 
1186
        self._ensure_real()
 
1187
        return self._real_repository.get_serializer_format()
1719
1188
 
1720
1189
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1721
1190
                           timezone=None, committer=None, revprops=None,
1722
 
                           revision_id=None, lossy=False):
 
1191
                           revision_id=None):
1723
1192
        # FIXME: It ought to be possible to call this without immediately
1724
1193
        # triggering _ensure_real.  For now it's the easiest thing to do.
1725
1194
        self._ensure_real()
1726
1195
        real_repo = self._real_repository
1727
1196
        builder = real_repo.get_commit_builder(branch, parents,
1728
1197
                config, timestamp=timestamp, timezone=timezone,
1729
 
                committer=committer, revprops=revprops,
1730
 
                revision_id=revision_id, lossy=lossy)
 
1198
                committer=committer, revprops=revprops, revision_id=revision_id)
1731
1199
        return builder
1732
1200
 
1733
1201
    def add_fallback_repository(self, repository):
1741
1209
        # We need to accumulate additional repositories here, to pass them in
1742
1210
        # on various RPC's.
1743
1211
        #
1744
 
        # Make the check before we lock: this raises an exception.
1745
 
        self._check_fallback_repository(repository)
1746
1212
        if self.is_locked():
1747
1213
            # We will call fallback.unlock() when we transition to the unlocked
1748
1214
            # state, so always add a lock here. If a caller passes us a locked
1753
1219
        # _real_branch had its get_stacked_on_url method called), then the
1754
1220
        # repository to be added may already be in the _real_repositories list.
1755
1221
        if self._real_repository is not None:
1756
 
            fallback_locations = [repo.user_url for repo in
 
1222
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
1757
1223
                self._real_repository._fallback_repositories]
1758
 
            if repository.user_url not in fallback_locations:
 
1224
            if repository.bzrdir.root_transport.base not in fallback_locations:
1759
1225
                self._real_repository.add_fallback_repository(repository)
1760
1226
 
1761
 
    def _check_fallback_repository(self, repository):
1762
 
        """Check that this repository can fallback to repository safely.
1763
 
 
1764
 
        Raise an error if not.
1765
 
 
1766
 
        :param repository: A repository to fallback to.
1767
 
        """
1768
 
        return _mod_repository.InterRepository._assert_same_model(
1769
 
            self, repository)
1770
 
 
1771
1227
    def add_inventory(self, revid, inv, parents):
1772
1228
        self._ensure_real()
1773
1229
        return self._real_repository.add_inventory(revid, inv, parents)
1774
1230
 
1775
1231
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1776
 
            parents, basis_inv=None, propagate_caches=False):
 
1232
                               parents):
1777
1233
        self._ensure_real()
1778
1234
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1779
 
            delta, new_revision_id, parents, basis_inv=basis_inv,
1780
 
            propagate_caches=propagate_caches)
 
1235
            delta, new_revision_id, parents)
1781
1236
 
1782
1237
    def add_revision(self, rev_id, rev, inv=None, config=None):
1783
1238
        self._ensure_real()
1786
1241
 
1787
1242
    @needs_read_lock
1788
1243
    def get_inventory(self, revision_id):
1789
 
        return list(self.iter_inventories([revision_id]))[0]
 
1244
        self._ensure_real()
 
1245
        return self._real_repository.get_inventory(revision_id)
1790
1246
 
1791
1247
    def iter_inventories(self, revision_ids, ordering=None):
1792
1248
        self._ensure_real()
1794
1250
 
1795
1251
    @needs_read_lock
1796
1252
    def get_revision(self, revision_id):
1797
 
        return self.get_revisions([revision_id])[0]
 
1253
        self._ensure_real()
 
1254
        return self._real_repository.get_revision(revision_id)
1798
1255
 
1799
1256
    def get_transaction(self):
1800
1257
        self._ensure_real()
1802
1259
 
1803
1260
    @needs_read_lock
1804
1261
    def clone(self, a_bzrdir, revision_id=None):
1805
 
        dest_repo = self._create_sprouting_repo(
1806
 
            a_bzrdir, shared=self.is_shared())
1807
 
        self.copy_content_into(dest_repo, revision_id)
1808
 
        return dest_repo
 
1262
        self._ensure_real()
 
1263
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1809
1264
 
1810
1265
    def make_working_trees(self):
1811
1266
        """See Repository.make_working_trees"""
1812
 
        path = self.bzrdir._path_for_remote_call(self._client)
1813
 
        try:
1814
 
            response = self._call('Repository.make_working_trees', path)
1815
 
        except errors.UnknownSmartMethod:
1816
 
            self._ensure_real()
1817
 
            return self._real_repository.make_working_trees()
1818
 
        if response[0] not in ('yes', 'no'):
1819
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
1820
 
        return response[0] == 'yes'
 
1267
        self._ensure_real()
 
1268
        return self._real_repository.make_working_trees()
1821
1269
 
1822
1270
    def refresh_data(self):
1823
 
        """Re-read any data needed to synchronise with disk.
 
1271
        """Re-read any data needed to to synchronise with disk.
1824
1272
 
1825
1273
        This method is intended to be called after another repository instance
1826
1274
        (such as one used by a smart server) has inserted data into the
1827
 
        repository. On all repositories this will work outside of write groups.
1828
 
        Some repository formats (pack and newer for bzrlib native formats)
1829
 
        support refresh_data inside write groups. If called inside a write
1830
 
        group on a repository that does not support refreshing in a write group
1831
 
        IsInWriteGroupError will be raised.
 
1275
        repository. It may not be called during a write group, but may be
 
1276
        called at any other time.
1832
1277
        """
 
1278
        if self.is_in_write_group():
 
1279
            raise errors.InternalBzrError(
 
1280
                "May not refresh_data while in a write group.")
1833
1281
        if self._real_repository is not None:
1834
1282
            self._real_repository.refresh_data()
1835
1283
 
1842
1290
        included_keys = result_set.intersection(result_parents)
1843
1291
        start_keys = result_set.difference(included_keys)
1844
1292
        exclude_keys = result_parents.difference(result_set)
1845
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
 
1293
        result = graph.SearchResult(start_keys, exclude_keys,
1846
1294
            len(result_set), result_set)
1847
1295
        return result
1848
1296
 
1849
1297
    @needs_read_lock
1850
 
    def search_missing_revision_ids(self, other,
1851
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1852
 
            find_ghosts=True, revision_ids=None, if_present_ids=None,
1853
 
            limit=None):
 
1298
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1854
1299
        """Return the revision ids that other has that this does not.
1855
1300
 
1856
1301
        These are returned in topological order.
1857
1302
 
1858
1303
        revision_id: only return revision ids included by revision_id.
1859
1304
        """
1860
 
        if symbol_versioning.deprecated_passed(revision_id):
1861
 
            symbol_versioning.warn(
1862
 
                'search_missing_revision_ids(revision_id=...) was '
1863
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
1864
 
                DeprecationWarning, stacklevel=2)
1865
 
            if revision_ids is not None:
1866
 
                raise AssertionError(
1867
 
                    'revision_ids is mutually exclusive with revision_id')
1868
 
            if revision_id is not None:
1869
 
                revision_ids = [revision_id]
1870
 
        inter_repo = _mod_repository.InterRepository.get(other, self)
1871
 
        return inter_repo.search_missing_revision_ids(
1872
 
            find_ghosts=find_ghosts, revision_ids=revision_ids,
1873
 
            if_present_ids=if_present_ids, limit=limit)
 
1305
        return repository.InterRepository.get(
 
1306
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
1874
1307
 
1875
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1308
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1876
1309
            fetch_spec=None):
1877
1310
        # No base implementation to use as RemoteRepository is not a subclass
1878
1311
        # of Repository; so this is a copy of Repository.fetch().
1889
1322
            # check that last_revision is in 'from' and then return a
1890
1323
            # no-operation.
1891
1324
            if (revision_id is not None and
1892
 
                not _mod_revision.is_null(revision_id)):
 
1325
                not revision.is_null(revision_id)):
1893
1326
                self.get_revision(revision_id)
1894
1327
            return 0, []
1895
1328
        # if there is no specific appropriate InterRepository, this will get
1896
1329
        # the InterRepository base class, which raises an
1897
1330
        # IncompatibleRepositories when asked to fetch.
1898
 
        inter = _mod_repository.InterRepository.get(source, self)
1899
 
        if (fetch_spec is not None and
1900
 
            not getattr(inter, "supports_fetch_spec", False)):
1901
 
            raise errors.UnsupportedOperation(
1902
 
                "fetch_spec not supported for %r" % inter)
1903
 
        return inter.fetch(revision_id=revision_id,
 
1331
        inter = repository.InterRepository.get(source, self)
 
1332
        return inter.fetch(revision_id=revision_id, pb=pb,
1904
1333
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1905
1334
 
1906
1335
    def create_bundle(self, target, base, fileobj, format=None):
1908
1337
        self._real_repository.create_bundle(target, base, fileobj, format)
1909
1338
 
1910
1339
    @needs_read_lock
1911
 
    @symbol_versioning.deprecated_method(
1912
 
        symbol_versioning.deprecated_in((2, 4, 0)))
1913
1340
    def get_ancestry(self, revision_id, topo_sorted=True):
1914
1341
        self._ensure_real()
1915
1342
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
1923
1350
        return self._real_repository._get_versioned_file_checker(
1924
1351
            revisions, revision_versions_cache)
1925
1352
 
1926
 
    def _iter_files_bytes_rpc(self, desired_files, absent):
1927
 
        path = self.bzrdir._path_for_remote_call(self._client)
1928
 
        lines = []
1929
 
        identifiers = []
1930
 
        for (file_id, revid, identifier) in desired_files:
1931
 
            lines.append("%s\0%s" % (
1932
 
                osutils.safe_file_id(file_id),
1933
 
                osutils.safe_revision_id(revid)))
1934
 
            identifiers.append(identifier)
1935
 
        (response_tuple, response_handler) = (
1936
 
            self._call_with_body_bytes_expecting_body(
1937
 
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
1938
 
        if response_tuple != ('ok', ):
1939
 
            response_handler.cancel_read_body()
1940
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1941
 
        byte_stream = response_handler.read_streamed_body()
1942
 
        def decompress_stream(start, byte_stream, unused):
1943
 
            decompressor = zlib.decompressobj()
1944
 
            yield decompressor.decompress(start)
1945
 
            while decompressor.unused_data == "":
1946
 
                try:
1947
 
                    data = byte_stream.next()
1948
 
                except StopIteration:
1949
 
                    break
1950
 
                yield decompressor.decompress(data)
1951
 
            yield decompressor.flush()
1952
 
            unused.append(decompressor.unused_data)
1953
 
        unused = ""
1954
 
        while True:
1955
 
            while not "\n" in unused:
1956
 
                unused += byte_stream.next()
1957
 
            header, rest = unused.split("\n", 1)
1958
 
            args = header.split("\0")
1959
 
            if args[0] == "absent":
1960
 
                absent[identifiers[int(args[3])]] = (args[1], args[2])
1961
 
                unused = rest
1962
 
                continue
1963
 
            elif args[0] == "ok":
1964
 
                idx = int(args[1])
1965
 
            else:
1966
 
                raise errors.UnexpectedSmartServerResponse(args)
1967
 
            unused_chunks = []
1968
 
            yield (identifiers[idx],
1969
 
                decompress_stream(rest, byte_stream, unused_chunks))
1970
 
            unused = "".join(unused_chunks)
1971
 
 
1972
1353
    def iter_files_bytes(self, desired_files):
1973
1354
        """See Repository.iter_file_bytes.
1974
1355
        """
1975
 
        try:
1976
 
            absent = {}
1977
 
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
1978
 
                    desired_files, absent):
1979
 
                yield identifier, bytes_iterator
1980
 
            for fallback in self._fallback_repositories:
1981
 
                if not absent:
1982
 
                    break
1983
 
                desired_files = [(key[0], key[1], identifier) for
1984
 
                    (identifier, key) in absent.iteritems()]
1985
 
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
1986
 
                    del absent[identifier]
1987
 
                    yield identifier, bytes_iterator
1988
 
            if absent:
1989
 
                # There may be more missing items, but raise an exception
1990
 
                # for just one.
1991
 
                missing_identifier = absent.keys()[0]
1992
 
                missing_key = absent[missing_identifier]
1993
 
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
1994
 
                    file_id=missing_key[0])
1995
 
        except errors.UnknownSmartMethod:
1996
 
            self._ensure_real()
1997
 
            for (identifier, bytes_iterator) in (
1998
 
                self._real_repository.iter_files_bytes(desired_files)):
1999
 
                yield identifier, bytes_iterator
2000
 
 
2001
 
    def get_cached_parent_map(self, revision_ids):
2002
 
        """See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2003
 
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
 
1356
        self._ensure_real()
 
1357
        return self._real_repository.iter_files_bytes(desired_files)
2004
1358
 
2005
1359
    def get_parent_map(self, revision_ids):
2006
1360
        """See bzrlib.Graph.get_parent_map()."""
2065
1419
        if parents_map is None:
2066
1420
            # Repository is not locked, so there's no cache.
2067
1421
            parents_map = {}
2068
 
        if _DEFAULT_SEARCH_DEPTH <= 0:
2069
 
            (start_set, stop_keys,
2070
 
             key_count) = vf_search.search_result_from_parent_map(
2071
 
                parents_map, self._unstacked_provider.missing_keys)
2072
 
        else:
2073
 
            (start_set, stop_keys,
2074
 
             key_count) = vf_search.limited_search_result_from_parent_map(
2075
 
                parents_map, self._unstacked_provider.missing_keys,
2076
 
                keys, depth=_DEFAULT_SEARCH_DEPTH)
 
1422
        # start_set is all the keys in the cache
 
1423
        start_set = set(parents_map)
 
1424
        # result set is all the references to keys in the cache
 
1425
        result_parents = set()
 
1426
        for parents in parents_map.itervalues():
 
1427
            result_parents.update(parents)
 
1428
        stop_keys = result_parents.difference(start_set)
 
1429
        # We don't need to send ghosts back to the server as a position to
 
1430
        # stop either.
 
1431
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1432
        key_count = len(parents_map)
 
1433
        if (NULL_REVISION in result_parents
 
1434
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1435
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1436
            # in our cache of "missing" keys we need to increment our key count
 
1437
            # by 1, because the reconsitituted SearchResult on the server will
 
1438
            # still consider NULL_REVISION to be an included key.
 
1439
            key_count += 1
 
1440
        included_keys = start_set.intersection(result_parents)
 
1441
        start_set.difference_update(included_keys)
2077
1442
        recipe = ('manual', start_set, stop_keys, key_count)
2078
1443
        body = self._serialise_search_recipe(recipe)
2079
1444
        path = self.bzrdir._path_for_remote_call(self._client)
2128
1493
 
2129
1494
    @needs_read_lock
2130
1495
    def get_signature_text(self, revision_id):
2131
 
        path = self.bzrdir._path_for_remote_call(self._client)
2132
 
        try:
2133
 
            response_tuple, response_handler = self._call_expecting_body(
2134
 
                'Repository.get_revision_signature_text', path, revision_id)
2135
 
        except errors.UnknownSmartMethod:
2136
 
            self._ensure_real()
2137
 
            return self._real_repository.get_signature_text(revision_id)
2138
 
        except errors.NoSuchRevision, err:
2139
 
            for fallback in self._fallback_repositories:
2140
 
                try:
2141
 
                    return fallback.get_signature_text(revision_id)
2142
 
                except errors.NoSuchRevision:
2143
 
                    pass
2144
 
            raise err
2145
 
        else:
2146
 
            if response_tuple[0] != 'ok':
2147
 
                raise errors.UnexpectedSmartServerResponse(response_tuple)
2148
 
            return response_handler.read_body_bytes()
 
1496
        self._ensure_real()
 
1497
        return self._real_repository.get_signature_text(revision_id)
2149
1498
 
2150
1499
    @needs_read_lock
2151
1500
    def _get_inventory_xml(self, revision_id):
2152
1501
        self._ensure_real()
2153
1502
        return self._real_repository._get_inventory_xml(revision_id)
2154
1503
 
 
1504
    def _deserialise_inventory(self, revision_id, xml):
 
1505
        self._ensure_real()
 
1506
        return self._real_repository._deserialise_inventory(revision_id, xml)
 
1507
 
2155
1508
    def reconcile(self, other=None, thorough=False):
2156
1509
        self._ensure_real()
2157
1510
        return self._real_repository.reconcile(other=other, thorough=thorough)
2158
1511
 
2159
1512
    def all_revision_ids(self):
2160
 
        path = self.bzrdir._path_for_remote_call(self._client)
2161
 
        try:
2162
 
            response_tuple, response_handler = self._call_expecting_body(
2163
 
                "Repository.all_revision_ids", path)
2164
 
        except errors.UnknownSmartMethod:
2165
 
            self._ensure_real()
2166
 
            return self._real_repository.all_revision_ids()
2167
 
        if response_tuple != ("ok", ):
2168
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2169
 
        revids = set(response_handler.read_body_bytes().splitlines())
2170
 
        for fallback in self._fallback_repositories:
2171
 
            revids.update(set(fallback.all_revision_ids()))
2172
 
        return list(revids)
 
1513
        self._ensure_real()
 
1514
        return self._real_repository.all_revision_ids()
2173
1515
 
2174
1516
    @needs_read_lock
2175
1517
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2179
1521
 
2180
1522
    @needs_read_lock
2181
1523
    def get_revision_delta(self, revision_id, specific_fileids=None):
2182
 
        r = self.get_revision(revision_id)
2183
 
        return list(self.get_deltas_for_revisions([r],
2184
 
            specific_fileids=specific_fileids))[0]
 
1524
        self._ensure_real()
 
1525
        return self._real_repository.get_revision_delta(revision_id,
 
1526
            specific_fileids=specific_fileids)
2185
1527
 
2186
1528
    @needs_read_lock
2187
1529
    def revision_trees(self, revision_ids):
2188
 
        inventories = self.iter_inventories(revision_ids)
2189
 
        for inv in inventories:
2190
 
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
1530
        self._ensure_real()
 
1531
        return self._real_repository.revision_trees(revision_ids)
2191
1532
 
2192
1533
    @needs_read_lock
2193
1534
    def get_revision_reconcile(self, revision_id):
2201
1542
            callback_refs=callback_refs, check_repo=check_repo)
2202
1543
 
2203
1544
    def copy_content_into(self, destination, revision_id=None):
2204
 
        """Make a complete copy of the content in self into destination.
2205
 
 
2206
 
        This is a destructive operation! Do not use it on existing
2207
 
        repositories.
2208
 
        """
2209
 
        interrepo = _mod_repository.InterRepository.get(self, destination)
2210
 
        return interrepo.copy_content(revision_id)
 
1545
        self._ensure_real()
 
1546
        return self._real_repository.copy_content_into(
 
1547
            destination, revision_id=revision_id)
2211
1548
 
2212
1549
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2213
1550
        # get a tarball of the remote repository, and copy from that into the
2214
1551
        # destination
 
1552
        from bzrlib import osutils
2215
1553
        import tarfile
2216
1554
        # TODO: Maybe a progress bar while streaming the tarball?
2217
 
        note(gettext("Copying repository content as tarball..."))
 
1555
        note("Copying repository content as tarball...")
2218
1556
        tar_file = self._get_tarball('bz2')
2219
1557
        if tar_file is None:
2220
1558
            return None
2225
1563
            tmpdir = osutils.mkdtemp()
2226
1564
            try:
2227
1565
                _extract_tar(tar, tmpdir)
2228
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
1566
                tmp_bzrdir = BzrDir.open(tmpdir)
2229
1567
                tmp_repo = tmp_bzrdir.open_repository()
2230
1568
                tmp_repo.copy_content_into(destination, revision_id)
2231
1569
            finally:
2247
1585
        return self._real_repository.inventories
2248
1586
 
2249
1587
    @needs_write_lock
2250
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
1588
    def pack(self, hint=None):
2251
1589
        """Compress the data within the repository.
 
1590
 
 
1591
        This is not currently implemented within the smart server.
2252
1592
        """
2253
 
        if hint is None:
2254
 
            body = ""
2255
 
        else:
2256
 
            body = "".join([l+"\n" for l in hint])
2257
 
        path = self.bzrdir._path_for_remote_call(self._client)
2258
 
        try:
2259
 
            response, handler = self._call_with_body_bytes_expecting_body(
2260
 
                'Repository.pack', (path, self._lock_token,
2261
 
                    str(clean_obsolete_packs)), body)
2262
 
        except errors.UnknownSmartMethod:
2263
 
            self._ensure_real()
2264
 
            return self._real_repository.pack(hint=hint,
2265
 
                clean_obsolete_packs=clean_obsolete_packs)
2266
 
        handler.cancel_read_body()
2267
 
        if response != ('ok', ):
2268
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1593
        self._ensure_real()
 
1594
        return self._real_repository.pack(hint=hint)
2269
1595
 
2270
1596
    @property
2271
1597
    def revisions(self):
2272
1598
        """Decorate the real repository for now.
2273
1599
 
 
1600
        In the short term this should become a real object to intercept graph
 
1601
        lookups.
 
1602
 
2274
1603
        In the long term a full blown network facility is needed.
2275
1604
        """
2276
1605
        self._ensure_real()
2304
1633
 
2305
1634
    @needs_write_lock
2306
1635
    def sign_revision(self, revision_id, gpg_strategy):
2307
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
2308
 
        plaintext = testament.as_short_text()
2309
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1636
        self._ensure_real()
 
1637
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2310
1638
 
2311
1639
    @property
2312
1640
    def texts(self):
2318
1646
        self._ensure_real()
2319
1647
        return self._real_repository.texts
2320
1648
 
2321
 
    def _iter_revisions_rpc(self, revision_ids):
2322
 
        body = "\n".join(revision_ids)
2323
 
        path = self.bzrdir._path_for_remote_call(self._client)
2324
 
        response_tuple, response_handler = (
2325
 
            self._call_with_body_bytes_expecting_body(
2326
 
            "Repository.iter_revisions", (path, ), body))
2327
 
        if response_tuple[0] != "ok":
2328
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2329
 
        serializer_format = response_tuple[1]
2330
 
        serializer = serializer_format_registry.get(serializer_format)
2331
 
        byte_stream = response_handler.read_streamed_body()
2332
 
        decompressor = zlib.decompressobj()
2333
 
        chunks = []
2334
 
        for bytes in byte_stream:
2335
 
            chunks.append(decompressor.decompress(bytes))
2336
 
            if decompressor.unused_data != "":
2337
 
                chunks.append(decompressor.flush())
2338
 
                yield serializer.read_revision_from_string("".join(chunks))
2339
 
                unused = decompressor.unused_data
2340
 
                decompressor = zlib.decompressobj()
2341
 
                chunks = [decompressor.decompress(unused)]
2342
 
        chunks.append(decompressor.flush())
2343
 
        text = "".join(chunks)
2344
 
        if text != "":
2345
 
            yield serializer.read_revision_from_string("".join(chunks))
2346
 
 
2347
1649
    @needs_read_lock
2348
1650
    def get_revisions(self, revision_ids):
2349
 
        if revision_ids is None:
2350
 
            revision_ids = self.all_revision_ids()
2351
 
        else:
2352
 
            for rev_id in revision_ids:
2353
 
                if not rev_id or not isinstance(rev_id, basestring):
2354
 
                    raise errors.InvalidRevisionId(
2355
 
                        revision_id=rev_id, branch=self)
2356
 
        try:
2357
 
            missing = set(revision_ids)
2358
 
            revs = {}
2359
 
            for rev in self._iter_revisions_rpc(revision_ids):
2360
 
                missing.remove(rev.revision_id)
2361
 
                revs[rev.revision_id] = rev
2362
 
        except errors.UnknownSmartMethod:
2363
 
            self._ensure_real()
2364
 
            return self._real_repository.get_revisions(revision_ids)
2365
 
        for fallback in self._fallback_repositories:
2366
 
            if not missing:
2367
 
                break
2368
 
            for revid in list(missing):
2369
 
                # XXX JRV 2011-11-20: It would be nice if there was a
2370
 
                # public method on Repository that could be used to query
2371
 
                # for revision objects *without* failing completely if one
2372
 
                # was missing. There is VersionedFileRepository._iter_revisions,
2373
 
                # but unfortunately that's private and not provided by
2374
 
                # all repository implementations.
2375
 
                try:
2376
 
                    revs[revid] = fallback.get_revision(revid)
2377
 
                except errors.NoSuchRevision:
2378
 
                    pass
2379
 
                else:
2380
 
                    missing.remove(revid)
2381
 
        if missing:
2382
 
            raise errors.NoSuchRevision(self, list(missing)[0])
2383
 
        return [revs[revid] for revid in revision_ids]
 
1651
        self._ensure_real()
 
1652
        return self._real_repository.get_revisions(revision_ids)
2384
1653
 
2385
1654
    def supports_rich_root(self):
2386
1655
        return self._format.rich_root_data
2387
1656
 
2388
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2389
1657
    def iter_reverse_revision_history(self, revision_id):
2390
1658
        self._ensure_real()
2391
1659
        return self._real_repository.iter_reverse_revision_history(revision_id)
2394
1662
    def _serializer(self):
2395
1663
        return self._format._serializer
2396
1664
 
2397
 
    @needs_write_lock
2398
1665
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2399
 
        signature = gpg_strategy.sign(plaintext)
2400
 
        self.add_signature_text(revision_id, signature)
 
1666
        self._ensure_real()
 
1667
        return self._real_repository.store_revision_signature(
 
1668
            gpg_strategy, plaintext, revision_id)
2401
1669
 
2402
1670
    def add_signature_text(self, revision_id, signature):
2403
 
        if self._real_repository:
2404
 
            # If there is a real repository the write group will
2405
 
            # be in the real repository as well, so use that:
2406
 
            self._ensure_real()
2407
 
            return self._real_repository.add_signature_text(
2408
 
                revision_id, signature)
2409
 
        path = self.bzrdir._path_for_remote_call(self._client)
2410
 
        response, handler = self._call_with_body_bytes_expecting_body(
2411
 
            'Repository.add_signature_text', (path, self._lock_token,
2412
 
                revision_id) + tuple(self._write_group_tokens), signature)
2413
 
        handler.cancel_read_body()
2414
 
        self.refresh_data()
2415
 
        if response[0] != 'ok':
2416
 
            raise errors.UnexpectedSmartServerResponse(response)
2417
 
        self._write_group_tokens = response[1:]
 
1671
        self._ensure_real()
 
1672
        return self._real_repository.add_signature_text(revision_id, signature)
2418
1673
 
2419
1674
    def has_signature_for_revision_id(self, revision_id):
2420
 
        path = self.bzrdir._path_for_remote_call(self._client)
2421
 
        try:
2422
 
            response = self._call('Repository.has_signature_for_revision_id',
2423
 
                path, revision_id)
2424
 
        except errors.UnknownSmartMethod:
2425
 
            self._ensure_real()
2426
 
            return self._real_repository.has_signature_for_revision_id(
2427
 
                revision_id)
2428
 
        if response[0] not in ('yes', 'no'):
2429
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
2430
 
        if response[0] == 'yes':
2431
 
            return True
2432
 
        for fallback in self._fallback_repositories:
2433
 
            if fallback.has_signature_for_revision_id(revision_id):
2434
 
                return True
2435
 
        return False
2436
 
 
2437
 
    @needs_read_lock
2438
 
    def verify_revision_signature(self, revision_id, gpg_strategy):
2439
 
        if not self.has_signature_for_revision_id(revision_id):
2440
 
            return gpg.SIGNATURE_NOT_SIGNED, None
2441
 
        signature = self.get_signature_text(revision_id)
2442
 
 
2443
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
2444
 
        plaintext = testament.as_short_text()
2445
 
 
2446
 
        return gpg_strategy.verify(signature, plaintext)
 
1675
        self._ensure_real()
 
1676
        return self._real_repository.has_signature_for_revision_id(revision_id)
2447
1677
 
2448
1678
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2449
1679
        self._ensure_real()
2450
1680
        return self._real_repository.item_keys_introduced_by(revision_ids,
2451
1681
            _files_pb=_files_pb)
2452
1682
 
 
1683
    def revision_graph_can_have_wrong_parents(self):
 
1684
        # The answer depends on the remote repo format.
 
1685
        self._ensure_real()
 
1686
        return self._real_repository.revision_graph_can_have_wrong_parents()
 
1687
 
2453
1688
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2454
1689
        self._ensure_real()
2455
1690
        return self._real_repository._find_inconsistent_revision_parents(
2463
1698
        providers = [self._unstacked_provider]
2464
1699
        if other is not None:
2465
1700
            providers.insert(0, other)
2466
 
        return graph.StackedParentsProvider(_LazyListJoin(
2467
 
            providers, self._fallback_repositories))
 
1701
        providers.extend(r._make_parents_provider() for r in
 
1702
                         self._fallback_repositories)
 
1703
        return graph.StackedParentsProvider(providers)
2468
1704
 
2469
1705
    def _serialise_search_recipe(self, recipe):
2470
1706
        """Serialise a graph search recipe.
2478
1714
        return '\n'.join((start_keys, stop_keys, count))
2479
1715
 
2480
1716
    def _serialise_search_result(self, search_result):
2481
 
        parts = search_result.get_network_struct()
 
1717
        if isinstance(search_result, graph.PendingAncestryResult):
 
1718
            parts = ['ancestry-of']
 
1719
            parts.extend(search_result.heads)
 
1720
        else:
 
1721
            recipe = search_result.get_recipe()
 
1722
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
2482
1723
        return '\n'.join(parts)
2483
1724
 
2484
1725
    def autopack(self):
2494
1735
            raise errors.UnexpectedSmartServerResponse(response)
2495
1736
 
2496
1737
 
2497
 
class RemoteStreamSink(vf_repository.StreamSink):
 
1738
class RemoteStreamSink(repository.StreamSink):
2498
1739
 
2499
1740
    def _insert_real(self, stream, src_format, resume_tokens):
2500
1741
        self.target_repo._ensure_real()
2601
1842
        self._last_substream and self._last_stream so that the stream can be
2602
1843
        resumed by _resume_stream_with_vfs.
2603
1844
        """
2604
 
 
 
1845
                    
2605
1846
        stream_iter = iter(stream)
2606
1847
        for substream_kind, substream in stream_iter:
2607
1848
            if substream_kind == 'inventory-deltas':
2610
1851
                return
2611
1852
            else:
2612
1853
                yield substream_kind, substream
2613
 
 
2614
 
 
2615
 
class RemoteStreamSource(vf_repository.StreamSource):
 
1854
            
 
1855
 
 
1856
class RemoteStreamSource(repository.StreamSource):
2616
1857
    """Stream data from a remote server."""
2617
1858
 
2618
1859
    def get_stream(self, search):
2639
1880
 
2640
1881
    def _real_stream(self, repo, search):
2641
1882
        """Get a stream for search from repo.
2642
 
 
2643
 
        This never called RemoteStreamSource.get_stream, and is a helper
2644
 
        for RemoteStreamSource._get_stream to allow getting a stream
 
1883
        
 
1884
        This never called RemoteStreamSource.get_stream, and is a heler
 
1885
        for RemoteStreamSource._get_stream to allow getting a stream 
2645
1886
        reliably whether fallback back because of old servers or trying
2646
1887
        to stream from a non-RemoteRepository (which the stacked support
2647
1888
        code will do).
2678
1919
        candidate_verbs = [
2679
1920
            ('Repository.get_stream_1.19', (1, 19)),
2680
1921
            ('Repository.get_stream', (1, 13))]
2681
 
 
2682
1922
        found_verb = False
2683
1923
        for verb, version in candidate_verbs:
2684
1924
            if medium._is_remote_before(version):
2688
1928
                    verb, args, search_bytes)
2689
1929
            except errors.UnknownSmartMethod:
2690
1930
                medium._remember_remote_is_before(version)
2691
 
            except errors.UnknownErrorFromSmartServer, e:
2692
 
                if isinstance(search, vf_search.EverythingResult):
2693
 
                    error_verb = e.error_from_smart_server.error_verb
2694
 
                    if error_verb == 'BadSearch':
2695
 
                        # Pre-2.4 servers don't support this sort of search.
2696
 
                        # XXX: perhaps falling back to VFS on BadSearch is a
2697
 
                        # good idea in general?  It might provide a little bit
2698
 
                        # of protection against client-side bugs.
2699
 
                        medium._remember_remote_is_before((2, 4))
2700
 
                        break
2701
 
                raise
2702
1931
            else:
2703
1932
                response_tuple, response_handler = response
2704
1933
                found_verb = True
2708
1937
        if response_tuple[0] != 'ok':
2709
1938
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2710
1939
        byte_stream = response_handler.read_streamed_body()
2711
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2712
 
            self._record_counter)
 
1940
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
2713
1941
        if src_format.network_name() != repo._format.network_name():
2714
1942
            raise AssertionError(
2715
1943
                "Mismatched RemoteRepository and stream src %r, %r" % (
2787
2015
 
2788
2016
    def _ensure_real(self):
2789
2017
        if self._custom_format is None:
2790
 
            try:
2791
 
                self._custom_format = branch.network_format_registry.get(
2792
 
                    self._network_name)
2793
 
            except KeyError:
2794
 
                raise errors.UnknownFormatError(kind='branch',
2795
 
                    format=self._network_name)
 
2018
            self._custom_format = branch.network_format_registry.get(
 
2019
                self._network_name)
2796
2020
 
2797
2021
    def get_format_description(self):
2798
2022
        self._ensure_real()
2801
2025
    def network_name(self):
2802
2026
        return self._network_name
2803
2027
 
2804
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2805
 
        return a_bzrdir.open_branch(name=name, 
2806
 
            ignore_fallbacks=ignore_fallbacks)
 
2028
    def open(self, a_bzrdir, ignore_fallbacks=False):
 
2029
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2807
2030
 
2808
 
    def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
 
2031
    def _vfs_initialize(self, a_bzrdir):
2809
2032
        # Initialisation when using a local bzrdir object, or a non-vfs init
2810
2033
        # method is not available on the server.
2811
2034
        # self._custom_format is always set - the start of initialize ensures
2812
2035
        # that.
2813
2036
        if isinstance(a_bzrdir, RemoteBzrDir):
2814
2037
            a_bzrdir._ensure_real()
2815
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2816
 
                name, append_revisions_only=append_revisions_only)
 
2038
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2817
2039
        else:
2818
2040
            # We assume the bzrdir is parameterised; it may not be.
2819
 
            result = self._custom_format.initialize(a_bzrdir, name,
2820
 
                append_revisions_only=append_revisions_only)
 
2041
            result = self._custom_format.initialize(a_bzrdir)
2821
2042
        if (isinstance(a_bzrdir, RemoteBzrDir) and
2822
2043
            not isinstance(result, RemoteBranch)):
2823
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2824
 
                                  name=name)
 
2044
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2825
2045
        return result
2826
2046
 
2827
 
    def initialize(self, a_bzrdir, name=None, repository=None,
2828
 
                   append_revisions_only=None):
 
2047
    def initialize(self, a_bzrdir):
2829
2048
        # 1) get the network name to use.
2830
2049
        if self._custom_format:
2831
2050
            network_name = self._custom_format.network_name()
2832
2051
        else:
2833
2052
            # Select the current bzrlib default and ask for that.
2834
 
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
 
2053
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
2835
2054
            reference_format = reference_bzrdir_format.get_branch_format()
2836
2055
            self._custom_format = reference_format
2837
2056
            network_name = reference_format.network_name()
2838
2057
        # Being asked to create on a non RemoteBzrDir:
2839
2058
        if not isinstance(a_bzrdir, RemoteBzrDir):
2840
 
            return self._vfs_initialize(a_bzrdir, name=name,
2841
 
                append_revisions_only=append_revisions_only)
 
2059
            return self._vfs_initialize(a_bzrdir)
2842
2060
        medium = a_bzrdir._client._medium
2843
2061
        if medium._is_remote_before((1, 13)):
2844
 
            return self._vfs_initialize(a_bzrdir, name=name,
2845
 
                append_revisions_only=append_revisions_only)
 
2062
            return self._vfs_initialize(a_bzrdir)
2846
2063
        # Creating on a remote bzr dir.
2847
2064
        # 2) try direct creation via RPC
2848
2065
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2849
 
        if name is not None:
2850
 
            # XXX JRV20100304: Support creating colocated branches
2851
 
            raise errors.NoColocatedBranchSupport(self)
2852
2066
        verb = 'BzrDir.create_branch'
2853
2067
        try:
2854
2068
            response = a_bzrdir._call(verb, path, network_name)
2855
2069
        except errors.UnknownSmartMethod:
2856
2070
            # Fallback - use vfs methods
2857
2071
            medium._remember_remote_is_before((1, 13))
2858
 
            return self._vfs_initialize(a_bzrdir, name=name,
2859
 
                    append_revisions_only=append_revisions_only)
 
2072
            return self._vfs_initialize(a_bzrdir)
2860
2073
        if response[0] != 'ok':
2861
2074
            raise errors.UnexpectedSmartServerResponse(response)
2862
2075
        # Turn the response into a RemoteRepository object.
2863
2076
        format = RemoteBranchFormat(network_name=response[1])
2864
2077
        repo_format = response_tuple_to_repo_format(response[3:])
2865
 
        repo_path = response[2]
2866
 
        if repository is not None:
2867
 
            remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2868
 
            url_diff = urlutils.relative_url(repository.user_url,
2869
 
                    remote_repo_url)
2870
 
            if url_diff != '.':
2871
 
                raise AssertionError(
2872
 
                    'repository.user_url %r does not match URL from server '
2873
 
                    'response (%r + %r)'
2874
 
                    % (repository.user_url, a_bzrdir.user_url, repo_path))
2875
 
            remote_repo = repository
 
2078
        if response[2] == '':
 
2079
            repo_bzrdir = a_bzrdir
2876
2080
        else:
2877
 
            if repo_path == '':
2878
 
                repo_bzrdir = a_bzrdir
2879
 
            else:
2880
 
                repo_bzrdir = RemoteBzrDir(
2881
 
                    a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2882
 
                    a_bzrdir._client)
2883
 
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2081
            repo_bzrdir = RemoteBzrDir(
 
2082
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2083
                a_bzrdir._client)
 
2084
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2884
2085
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2885
 
            format=format, setup_stacking=False, name=name)
2886
 
        if append_revisions_only:
2887
 
            remote_branch.set_append_revisions_only(append_revisions_only)
 
2086
            format=format, setup_stacking=False)
2888
2087
        # XXX: We know this is a new branch, so it must have revno 0, revid
2889
2088
        # NULL_REVISION. Creating the branch locked would make this be unable
2890
2089
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2909
2108
        self._ensure_real()
2910
2109
        return self._custom_format.supports_set_append_revisions_only()
2911
2110
 
2912
 
    def _use_default_local_heads_to_fetch(self):
2913
 
        # If the branch format is a metadir format *and* its heads_to_fetch
2914
 
        # implementation is not overridden vs the base class, we can use the
2915
 
        # base class logic rather than use the heads_to_fetch RPC.  This is
2916
 
        # usually cheaper in terms of net round trips, as the last-revision and
2917
 
        # tags info fetched is cached and would be fetched anyway.
2918
 
        self._ensure_real()
2919
 
        if isinstance(self._custom_format, branch.BranchFormatMetadir):
2920
 
            branch_class = self._custom_format._branch_class()
2921
 
            heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2922
 
            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2923
 
                return True
2924
 
        return False
2925
 
 
2926
 
 
2927
 
class RemoteBranchStore(config.IniFileStore):
2928
 
    """Branch store which attempts to use HPSS calls to retrieve branch store.
2929
 
 
2930
 
    Note that this is specific to bzr-based formats.
2931
 
    """
2932
 
 
2933
 
    def __init__(self, branch):
2934
 
        super(RemoteBranchStore, self).__init__()
2935
 
        self.branch = branch
2936
 
        self.id = "branch"
2937
 
        self._real_store = None
2938
 
 
2939
 
    def lock_write(self, token=None):
2940
 
        return self.branch.lock_write(token)
2941
 
 
2942
 
    def unlock(self):
2943
 
        return self.branch.unlock()
2944
 
 
2945
 
    @needs_write_lock
2946
 
    def save(self):
2947
 
        # We need to be able to override the undecorated implementation
2948
 
        self.save_without_locking()
2949
 
 
2950
 
    def save_without_locking(self):
2951
 
        super(RemoteBranchStore, self).save()
2952
 
 
2953
 
    def external_url(self):
2954
 
        return self.branch.user_url
2955
 
 
2956
 
    def _load_content(self):
2957
 
        path = self.branch._remote_path()
2958
 
        try:
2959
 
            response, handler = self.branch._call_expecting_body(
2960
 
                'Branch.get_config_file', path)
2961
 
        except errors.UnknownSmartMethod:
2962
 
            self._ensure_real()
2963
 
            return self._real_store._load_content()
2964
 
        if len(response) and response[0] != 'ok':
2965
 
            raise errors.UnexpectedSmartServerResponse(response)
2966
 
        return handler.read_body_bytes()
2967
 
 
2968
 
    def _save_content(self, content):
2969
 
        path = self.branch._remote_path()
2970
 
        try:
2971
 
            response, handler = self.branch._call_with_body_bytes_expecting_body(
2972
 
                'Branch.put_config_file', (path,
2973
 
                    self.branch._lock_token, self.branch._repo_lock_token),
2974
 
                content)
2975
 
        except errors.UnknownSmartMethod:
2976
 
            self._ensure_real()
2977
 
            return self._real_store._save_content(content)
2978
 
        handler.cancel_read_body()
2979
 
        if response != ('ok', ):
2980
 
            raise errors.UnexpectedSmartServerResponse(response)
2981
 
 
2982
 
    def _ensure_real(self):
2983
 
        self.branch._ensure_real()
2984
 
        if self._real_store is None:
2985
 
            self._real_store = config.BranchStore(self.branch)
2986
 
 
2987
2111
 
2988
2112
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2989
2113
    """Branch stored on a server accessed by HPSS RPC.
2992
2116
    """
2993
2117
 
2994
2118
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2995
 
        _client=None, format=None, setup_stacking=True, name=None,
2996
 
        possible_transports=None):
 
2119
        _client=None, format=None, setup_stacking=True):
2997
2120
        """Create a RemoteBranch instance.
2998
2121
 
2999
2122
        :param real_branch: An optional local implementation of the branch
3005
2128
        :param setup_stacking: If True make an RPC call to determine the
3006
2129
            stacked (or not) status of the branch. If False assume the branch
3007
2130
            is not stacked.
3008
 
        :param name: Colocated branch name
3009
2131
        """
3010
2132
        # We intentionally don't call the parent class's __init__, because it
3011
2133
        # will try to assign to self.tags, which is a property in this subclass.
3030
2152
            self._real_branch = None
3031
2153
        # Fill out expected attributes of branch for bzrlib API users.
3032
2154
        self._clear_cached_state()
3033
 
        # TODO: deprecate self.base in favor of user_url
3034
 
        self.base = self.bzrdir.user_url
3035
 
        self._name = name
 
2155
        self.base = self.bzrdir.root_transport.base
3036
2156
        self._control_files = None
3037
2157
        self._lock_mode = None
3038
2158
        self._lock_token = None
3064
2184
            hook(self)
3065
2185
        self._is_stacked = False
3066
2186
        if setup_stacking:
3067
 
            self._setup_stacking(possible_transports)
 
2187
            self._setup_stacking()
3068
2188
 
3069
 
    def _setup_stacking(self, possible_transports):
 
2189
    def _setup_stacking(self):
3070
2190
        # configure stacking into the remote repository, by reading it from
3071
2191
        # the vfs branch.
3072
2192
        try:
3075
2195
            errors.UnstackableRepositoryFormat), e:
3076
2196
            return
3077
2197
        self._is_stacked = True
3078
 
        if possible_transports is None:
3079
 
            possible_transports = []
3080
 
        else:
3081
 
            possible_transports = list(possible_transports)
3082
 
        possible_transports.append(self.bzrdir.root_transport)
3083
 
        self._activate_fallback_location(fallback_url,
3084
 
            possible_transports=possible_transports)
 
2198
        self._activate_fallback_location(fallback_url)
3085
2199
 
3086
2200
    def _get_config(self):
3087
2201
        return RemoteBranchConfig(self)
3088
2202
 
3089
 
    def _get_config_store(self):
3090
 
        return RemoteBranchStore(self)
3091
 
 
3092
2203
    def _get_real_transport(self):
3093
2204
        # if we try vfs access, return the real branch's vfs transport
3094
2205
        self._ensure_real()
3112
2223
                    'to use vfs implementation')
3113
2224
            self.bzrdir._ensure_real()
3114
2225
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3115
 
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
2226
                ignore_fallbacks=self._real_ignore_fallbacks)
3116
2227
            if self.repository._real_repository is None:
3117
2228
                # Give the remote repository the matching real repo.
3118
2229
                real_repo = self._real_branch.repository
3157
2268
                self.bzrdir, self._client)
3158
2269
        return self._control_files
3159
2270
 
3160
 
    def _get_checkout_format(self, lightweight=False):
 
2271
    def _get_checkout_format(self):
3161
2272
        self._ensure_real()
3162
 
        if lightweight:
3163
 
            format = RemoteBzrDirFormat()
3164
 
            self.bzrdir._format._supply_sub_formats_to(format)
3165
 
            format.workingtree_format = self._real_branch._get_checkout_format(
3166
 
                lightweight=lightweight).workingtree_format
3167
 
            return format
3168
 
        else:
3169
 
            return self._real_branch._get_checkout_format(lightweight=False)
 
2273
        return self._real_branch._get_checkout_format()
3170
2274
 
3171
2275
    def get_physical_lock_status(self):
3172
2276
        """See Branch.get_physical_lock_status()."""
3173
 
        try:
3174
 
            response = self._client.call('Branch.get_physical_lock_status',
3175
 
                self._remote_path())
3176
 
        except errors.UnknownSmartMethod:
3177
 
            self._ensure_real()
3178
 
            return self._real_branch.get_physical_lock_status()
3179
 
        if response[0] not in ('yes', 'no'):
3180
 
            raise errors.UnexpectedSmartServerResponse(response)
3181
 
        return (response[0] == 'yes')
 
2277
        # should be an API call to the server, as branches must be lockable.
 
2278
        self._ensure_real()
 
2279
        return self._real_branch.get_physical_lock_status()
3182
2280
 
3183
2281
    def get_stacked_on_url(self):
3184
2282
        """Get the URL this branch is stacked against.
3211
2309
            self._is_stacked = False
3212
2310
        else:
3213
2311
            self._is_stacked = True
3214
 
 
 
2312
        
3215
2313
    def _vfs_get_tags_bytes(self):
3216
2314
        self._ensure_real()
3217
2315
        return self._real_branch._get_tags_bytes()
3218
2316
 
3219
 
    @needs_read_lock
3220
2317
    def _get_tags_bytes(self):
3221
 
        if self._tags_bytes is None:
3222
 
            self._tags_bytes = self._get_tags_bytes_via_hpss()
3223
 
        return self._tags_bytes
3224
 
 
3225
 
    def _get_tags_bytes_via_hpss(self):
3226
2318
        medium = self._client._medium
3227
2319
        if medium._is_remote_before((1, 13)):
3228
2320
            return self._vfs_get_tags_bytes()
3238
2330
        return self._real_branch._set_tags_bytes(bytes)
3239
2331
 
3240
2332
    def _set_tags_bytes(self, bytes):
3241
 
        if self.is_locked():
3242
 
            self._tags_bytes = bytes
3243
2333
        medium = self._client._medium
3244
2334
        if medium._is_remote_before((1, 18)):
3245
2335
            self._vfs_set_tags_bytes(bytes)
3254
2344
            self._vfs_set_tags_bytes(bytes)
3255
2345
 
3256
2346
    def lock_read(self):
3257
 
        """Lock the branch for read operations.
3258
 
 
3259
 
        :return: A bzrlib.lock.LogicalLockResult.
3260
 
        """
3261
2347
        self.repository.lock_read()
3262
2348
        if not self._lock_mode:
3263
2349
            self._note_lock('r')
3267
2353
                self._real_branch.lock_read()
3268
2354
        else:
3269
2355
            self._lock_count += 1
3270
 
        return lock.LogicalLockResult(self.unlock)
3271
2356
 
3272
2357
    def _remote_lock_write(self, token):
3273
2358
        if token is None:
3274
2359
            branch_token = repo_token = ''
3275
2360
        else:
3276
2361
            branch_token = token
3277
 
            repo_token = self.repository.lock_write().repository_token
 
2362
            repo_token = self.repository.lock_write()
3278
2363
            self.repository.unlock()
3279
2364
        err_context = {'token': token}
3280
 
        try:
3281
 
            response = self._call(
3282
 
                'Branch.lock_write', self._remote_path(), branch_token,
3283
 
                repo_token or '', **err_context)
3284
 
        except errors.LockContention, e:
3285
 
            # The LockContention from the server doesn't have any
3286
 
            # information about the lock_url. We re-raise LockContention
3287
 
            # with valid lock_url.
3288
 
            raise errors.LockContention('(remote lock)',
3289
 
                self.repository.base.split('.bzr/')[0])
 
2365
        response = self._call(
 
2366
            'Branch.lock_write', self._remote_path(), branch_token,
 
2367
            repo_token or '', **err_context)
3290
2368
        if response[0] != 'ok':
3291
2369
            raise errors.UnexpectedSmartServerResponse(response)
3292
2370
        ok, branch_token, repo_token = response
3313
2391
            self._lock_mode = 'w'
3314
2392
            self._lock_count = 1
3315
2393
        elif self._lock_mode == 'r':
3316
 
            raise errors.ReadOnlyError(self)
 
2394
            raise errors.ReadOnlyTransaction
3317
2395
        else:
3318
2396
            if token is not None:
3319
2397
                # A token was given to lock_write, and we're relocking, so
3324
2402
            self._lock_count += 1
3325
2403
            # Re-lock the repository too.
3326
2404
            self.repository.lock_write(self._repo_lock_token)
3327
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
2405
        return self._lock_token or None
3328
2406
 
3329
2407
    def _unlock(self, branch_token, repo_token):
3330
2408
        err_context = {'token': str((branch_token, repo_token))}
3368
2446
            self.repository.unlock()
3369
2447
 
3370
2448
    def break_lock(self):
3371
 
        try:
3372
 
            response = self._call(
3373
 
                'Branch.break_lock', self._remote_path())
3374
 
        except errors.UnknownSmartMethod:
3375
 
            self._ensure_real()
3376
 
            return self._real_branch.break_lock()
3377
 
        if response != ('ok',):
3378
 
            raise errors.UnexpectedSmartServerResponse(response)
 
2449
        self._ensure_real()
 
2450
        return self._real_branch.break_lock()
3379
2451
 
3380
2452
    def leave_lock_in_place(self):
3381
2453
        if not self._lock_token:
3405
2477
            missing_parent = parent_map[missing_parent]
3406
2478
        raise errors.RevisionNotPresent(missing_parent, self.repository)
3407
2479
 
3408
 
    def _read_last_revision_info(self):
 
2480
    def _last_revision_info(self):
3409
2481
        response = self._call('Branch.last_revision_info', self._remote_path())
3410
2482
        if response[0] != 'ok':
3411
2483
            raise SmartProtocolError('unexpected response code %s' % (response,))
3474
2546
            raise errors.UnexpectedSmartServerResponse(response)
3475
2547
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3476
2548
 
3477
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3478
2549
    @needs_write_lock
3479
2550
    def set_revision_history(self, rev_history):
3480
 
        """See Branch.set_revision_history."""
3481
 
        self._set_revision_history(rev_history)
3482
 
 
3483
 
    @needs_write_lock
3484
 
    def _set_revision_history(self, rev_history):
3485
2551
        # Send just the tip revision of the history; the server will generate
3486
2552
        # the full history from that.  If the revision doesn't exist in this
3487
2553
        # branch, NoSuchRevision will be raised.
3545
2611
            _override_hook_target=self, **kwargs)
3546
2612
 
3547
2613
    @needs_read_lock
3548
 
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
 
2614
    def push(self, target, overwrite=False, stop_revision=None):
3549
2615
        self._ensure_real()
3550
2616
        return self._real_branch.push(
3551
 
            target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
 
2617
            target, overwrite=overwrite, stop_revision=stop_revision,
3552
2618
            _override_hook_source_branch=self)
3553
2619
 
3554
2620
    def is_locked(self):
3555
2621
        return self._lock_count >= 1
3556
2622
 
3557
2623
    @needs_read_lock
3558
 
    def revision_id_to_dotted_revno(self, revision_id):
3559
 
        """Given a revision id, return its dotted revno.
3560
 
 
3561
 
        :return: a tuple like (1,) or (400,1,3).
3562
 
        """
3563
 
        try:
3564
 
            response = self._call('Branch.revision_id_to_revno',
3565
 
                self._remote_path(), revision_id)
3566
 
        except errors.UnknownSmartMethod:
3567
 
            self._ensure_real()
3568
 
            return self._real_branch.revision_id_to_dotted_revno(revision_id)
3569
 
        if response[0] == 'ok':
3570
 
            return tuple([int(x) for x in response[1:]])
3571
 
        else:
3572
 
            raise errors.UnexpectedSmartServerResponse(response)
3573
 
 
3574
 
    @needs_read_lock
3575
2624
    def revision_id_to_revno(self, revision_id):
3576
 
        """Given a revision id on the branch mainline, return its revno.
3577
 
 
3578
 
        :return: an integer
3579
 
        """
3580
 
        try:
3581
 
            response = self._call('Branch.revision_id_to_revno',
3582
 
                self._remote_path(), revision_id)
3583
 
        except errors.UnknownSmartMethod:
3584
 
            self._ensure_real()
3585
 
            return self._real_branch.revision_id_to_revno(revision_id)
3586
 
        if response[0] == 'ok':
3587
 
            if len(response) == 2:
3588
 
                return int(response[1])
3589
 
            raise NoSuchRevision(self, revision_id)
3590
 
        else:
3591
 
            raise errors.UnexpectedSmartServerResponse(response)
 
2625
        self._ensure_real()
 
2626
        return self._real_branch.revision_id_to_revno(revision_id)
3592
2627
 
3593
2628
    @needs_write_lock
3594
2629
    def set_last_revision_info(self, revno, revision_id):
3595
2630
        # XXX: These should be returned by the set_last_revision_info verb
3596
2631
        old_revno, old_revid = self.last_revision_info()
3597
2632
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
3598
 
        if not revision_id or not isinstance(revision_id, basestring):
3599
 
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
2633
        revision_id = ensure_null(revision_id)
3600
2634
        try:
3601
2635
            response = self._call('Branch.set_last_revision_info',
3602
2636
                self._remote_path(), self._lock_token, self._repo_lock_token,
3631
2665
            except errors.UnknownSmartMethod:
3632
2666
                medium._remember_remote_is_before((1, 6))
3633
2667
        self._clear_cached_state_of_remote_branch_only()
3634
 
        self._set_revision_history(self._lefthand_history(revision_id,
 
2668
        self.set_revision_history(self._lefthand_history(revision_id,
3635
2669
            last_rev=last_rev,other_branch=other_branch))
3636
2670
 
3637
2671
    def set_push_location(self, location):
3638
2672
        self._ensure_real()
3639
2673
        return self._real_branch.set_push_location(location)
3640
2674
 
3641
 
    def heads_to_fetch(self):
3642
 
        if self._format._use_default_local_heads_to_fetch():
3643
 
            # We recognise this format, and its heads-to-fetch implementation
3644
 
            # is the default one (tip + tags).  In this case it's cheaper to
3645
 
            # just use the default implementation rather than a special RPC as
3646
 
            # the tip and tags data is cached.
3647
 
            return branch.Branch.heads_to_fetch(self)
3648
 
        medium = self._client._medium
3649
 
        if medium._is_remote_before((2, 4)):
3650
 
            return self._vfs_heads_to_fetch()
3651
 
        try:
3652
 
            return self._rpc_heads_to_fetch()
3653
 
        except errors.UnknownSmartMethod:
3654
 
            medium._remember_remote_is_before((2, 4))
3655
 
            return self._vfs_heads_to_fetch()
3656
 
 
3657
 
    def _rpc_heads_to_fetch(self):
3658
 
        response = self._call('Branch.heads_to_fetch', self._remote_path())
3659
 
        if len(response) != 2:
3660
 
            raise errors.UnexpectedSmartServerResponse(response)
3661
 
        must_fetch, if_present_fetch = response
3662
 
        return set(must_fetch), set(if_present_fetch)
3663
 
 
3664
 
    def _vfs_heads_to_fetch(self):
3665
 
        self._ensure_real()
3666
 
        return self._real_branch.heads_to_fetch()
3667
 
 
3668
2675
 
3669
2676
class RemoteConfig(object):
3670
2677
    """A Config that reads and writes from smart verbs.
3684
2691
        """
3685
2692
        try:
3686
2693
            configobj = self._get_configobj()
3687
 
            section_obj = None
3688
2694
            if section is None:
3689
2695
                section_obj = configobj
3690
2696
            else:
3691
2697
                try:
3692
2698
                    section_obj = configobj[section]
3693
2699
                except KeyError:
3694
 
                    pass
3695
 
            if section_obj is None:
3696
 
                value = default
3697
 
            else:
3698
 
                value = section_obj.get(name, default)
 
2700
                    return default
 
2701
            return section_obj.get(name, default)
3699
2702
        except errors.UnknownSmartMethod:
3700
 
            value = self._vfs_get_option(name, section, default)
3701
 
        for hook in config.OldConfigHooks['get']:
3702
 
            hook(self, name, value)
3703
 
        return value
 
2703
            return self._vfs_get_option(name, section, default)
3704
2704
 
3705
2705
    def _response_to_configobj(self, response):
3706
2706
        if len(response[0]) and response[0][0] != 'ok':
3707
2707
            raise errors.UnexpectedSmartServerResponse(response)
3708
2708
        lines = response[1].read_body_bytes().splitlines()
3709
 
        conf = config.ConfigObj(lines, encoding='utf-8')
3710
 
        for hook in config.OldConfigHooks['load']:
3711
 
            hook(self)
3712
 
        return conf
 
2709
        return config.ConfigObj(lines, encoding='utf-8')
3713
2710
 
3714
2711
 
3715
2712
class RemoteBranchConfig(RemoteConfig):
3734
2731
        medium = self._branch._client._medium
3735
2732
        if medium._is_remote_before((1, 14)):
3736
2733
            return self._vfs_set_option(value, name, section)
3737
 
        if isinstance(value, dict):
3738
 
            if medium._is_remote_before((2, 2)):
3739
 
                return self._vfs_set_option(value, name, section)
3740
 
            return self._set_config_option_dict(value, name, section)
3741
 
        else:
3742
 
            return self._set_config_option(value, name, section)
3743
 
 
3744
 
    def _set_config_option(self, value, name, section):
3745
2734
        try:
3746
2735
            path = self._branch._remote_path()
3747
2736
            response = self._branch._client.call('Branch.set_config_option',
3748
2737
                path, self._branch._lock_token, self._branch._repo_lock_token,
3749
2738
                value.encode('utf8'), name, section or '')
3750
2739
        except errors.UnknownSmartMethod:
3751
 
            medium = self._branch._client._medium
3752
2740
            medium._remember_remote_is_before((1, 14))
3753
2741
            return self._vfs_set_option(value, name, section)
3754
2742
        if response != ():
3755
2743
            raise errors.UnexpectedSmartServerResponse(response)
3756
2744
 
3757
 
    def _serialize_option_dict(self, option_dict):
3758
 
        utf8_dict = {}
3759
 
        for key, value in option_dict.items():
3760
 
            if isinstance(key, unicode):
3761
 
                key = key.encode('utf8')
3762
 
            if isinstance(value, unicode):
3763
 
                value = value.encode('utf8')
3764
 
            utf8_dict[key] = value
3765
 
        return bencode.bencode(utf8_dict)
3766
 
 
3767
 
    def _set_config_option_dict(self, value, name, section):
3768
 
        try:
3769
 
            path = self._branch._remote_path()
3770
 
            serialised_dict = self._serialize_option_dict(value)
3771
 
            response = self._branch._client.call(
3772
 
                'Branch.set_config_option_dict',
3773
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
3774
 
                serialised_dict, name, section or '')
3775
 
        except errors.UnknownSmartMethod:
3776
 
            medium = self._branch._client._medium
3777
 
            medium._remember_remote_is_before((2, 2))
3778
 
            return self._vfs_set_option(value, name, section)
3779
 
        if response != ():
3780
 
            raise errors.UnexpectedSmartServerResponse(response)
3781
 
 
3782
2745
    def _real_object(self):
3783
2746
        self._branch._ensure_real()
3784
2747
        return self._branch._real_branch
3823
2786
        return self._bzrdir._real_bzrdir
3824
2787
 
3825
2788
 
 
2789
 
3826
2790
def _extract_tar(tar, to_dir):
3827
2791
    """Extract all the contents of a tarfile object.
3828
2792
 
3832
2796
        tar.extract(tarinfo, to_dir)
3833
2797
 
3834
2798
 
3835
 
error_translators = registry.Registry()
3836
 
no_context_error_translators = registry.Registry()
3837
 
 
3838
 
 
3839
2799
def _translate_error(err, **context):
3840
2800
    """Translate an ErrorFromSmartServer into a more useful error.
3841
2801
 
3870
2830
                    'Missing key %r in context %r', key_err.args[0], context)
3871
2831
                raise err
3872
2832
 
3873
 
    try:
3874
 
        translator = error_translators.get(err.error_verb)
3875
 
    except KeyError:
3876
 
        pass
3877
 
    else:
3878
 
        raise translator(err, find, get_path)
3879
 
    try:
3880
 
        translator = no_context_error_translators.get(err.error_verb)
3881
 
    except KeyError:
3882
 
        raise errors.UnknownErrorFromSmartServer(err)
3883
 
    else:
3884
 
        raise translator(err)
3885
 
 
3886
 
 
3887
 
error_translators.register('NoSuchRevision',
3888
 
    lambda err, find, get_path: NoSuchRevision(
3889
 
        find('branch'), err.error_args[0]))
3890
 
error_translators.register('nosuchrevision',
3891
 
    lambda err, find, get_path: NoSuchRevision(
3892
 
        find('repository'), err.error_args[0]))
3893
 
 
3894
 
def _translate_nobranch_error(err, find, get_path):
3895
 
    if len(err.error_args) >= 1:
3896
 
        extra = err.error_args[0]
3897
 
    else:
3898
 
        extra = None
3899
 
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
3900
 
        detail=extra)
3901
 
 
3902
 
error_translators.register('nobranch', _translate_nobranch_error)
3903
 
error_translators.register('norepository',
3904
 
    lambda err, find, get_path: errors.NoRepositoryPresent(
3905
 
        find('bzrdir')))
3906
 
error_translators.register('UnlockableTransport',
3907
 
    lambda err, find, get_path: errors.UnlockableTransport(
3908
 
        find('bzrdir').root_transport))
3909
 
error_translators.register('TokenMismatch',
3910
 
    lambda err, find, get_path: errors.TokenMismatch(
3911
 
        find('token'), '(remote token)'))
3912
 
error_translators.register('Diverged',
3913
 
    lambda err, find, get_path: errors.DivergedBranches(
3914
 
        find('branch'), find('other_branch')))
3915
 
error_translators.register('NotStacked',
3916
 
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
3917
 
 
3918
 
def _translate_PermissionDenied(err, find, get_path):
3919
 
    path = get_path()
3920
 
    if len(err.error_args) >= 2:
3921
 
        extra = err.error_args[1]
3922
 
    else:
3923
 
        extra = None
3924
 
    return errors.PermissionDenied(path, extra=extra)
3925
 
 
3926
 
error_translators.register('PermissionDenied', _translate_PermissionDenied)
3927
 
error_translators.register('ReadError',
3928
 
    lambda err, find, get_path: errors.ReadError(get_path()))
3929
 
error_translators.register('NoSuchFile',
3930
 
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
3931
 
error_translators.register('UnsuspendableWriteGroup',
3932
 
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
3933
 
        repository=find('repository')))
3934
 
error_translators.register('UnresumableWriteGroup',
3935
 
    lambda err, find, get_path: errors.UnresumableWriteGroup(
3936
 
        repository=find('repository'), write_groups=err.error_args[0],
3937
 
        reason=err.error_args[1]))
3938
 
no_context_error_translators.register('IncompatibleRepositories',
3939
 
    lambda err: errors.IncompatibleRepositories(
3940
 
        err.error_args[0], err.error_args[1], err.error_args[2]))
3941
 
no_context_error_translators.register('LockContention',
3942
 
    lambda err: errors.LockContention('(remote lock)'))
3943
 
no_context_error_translators.register('LockFailed',
3944
 
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
3945
 
no_context_error_translators.register('TipChangeRejected',
3946
 
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
3947
 
no_context_error_translators.register('UnstackableBranchFormat',
3948
 
    lambda err: errors.UnstackableBranchFormat(*err.error_args))
3949
 
no_context_error_translators.register('UnstackableRepositoryFormat',
3950
 
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
3951
 
no_context_error_translators.register('FileExists',
3952
 
    lambda err: errors.FileExists(err.error_args[0]))
3953
 
no_context_error_translators.register('DirectoryNotEmpty',
3954
 
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
3955
 
 
3956
 
def _translate_short_readv_error(err):
3957
 
    args = err.error_args
3958
 
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
3959
 
        int(args[3]))
3960
 
 
3961
 
no_context_error_translators.register('ShortReadvError',
3962
 
    _translate_short_readv_error)
3963
 
 
3964
 
def _translate_unicode_error(err):
 
2833
    if err.error_verb == 'IncompatibleRepositories':
 
2834
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2835
            err.error_args[1], err.error_args[2])
 
2836
    elif err.error_verb == 'NoSuchRevision':
 
2837
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2838
    elif err.error_verb == 'nosuchrevision':
 
2839
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2840
    elif err.error_verb == 'nobranch':
 
2841
        if len(err.error_args) >= 1:
 
2842
            extra = err.error_args[0]
 
2843
        else:
 
2844
            extra = None
 
2845
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2846
            detail=extra)
 
2847
    elif err.error_verb == 'norepository':
 
2848
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2849
    elif err.error_verb == 'LockContention':
 
2850
        raise errors.LockContention('(remote lock)')
 
2851
    elif err.error_verb == 'UnlockableTransport':
 
2852
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2853
    elif err.error_verb == 'LockFailed':
 
2854
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2855
    elif err.error_verb == 'TokenMismatch':
 
2856
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2857
    elif err.error_verb == 'Diverged':
 
2858
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2859
    elif err.error_verb == 'TipChangeRejected':
 
2860
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2861
    elif err.error_verb == 'UnstackableBranchFormat':
 
2862
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2863
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2864
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2865
    elif err.error_verb == 'NotStacked':
 
2866
        raise errors.NotStacked(branch=find('branch'))
 
2867
    elif err.error_verb == 'PermissionDenied':
 
2868
        path = get_path()
 
2869
        if len(err.error_args) >= 2:
 
2870
            extra = err.error_args[1]
 
2871
        else:
 
2872
            extra = None
 
2873
        raise errors.PermissionDenied(path, extra=extra)
 
2874
    elif err.error_verb == 'ReadError':
 
2875
        path = get_path()
 
2876
        raise errors.ReadError(path)
 
2877
    elif err.error_verb == 'NoSuchFile':
 
2878
        path = get_path()
 
2879
        raise errors.NoSuchFile(path)
 
2880
    elif err.error_verb == 'FileExists':
 
2881
        raise errors.FileExists(err.error_args[0])
 
2882
    elif err.error_verb == 'DirectoryNotEmpty':
 
2883
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2884
    elif err.error_verb == 'ShortReadvError':
 
2885
        args = err.error_args
 
2886
        raise errors.ShortReadvError(
 
2887
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2888
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3965
2889
        encoding = str(err.error_args[0]) # encoding must always be a string
3966
2890
        val = err.error_args[1]
3967
2891
        start = int(err.error_args[2])
3975
2899
            raise UnicodeDecodeError(encoding, val, start, end, reason)
3976
2900
        elif err.error_verb == 'UnicodeEncodeError':
3977
2901
            raise UnicodeEncodeError(encoding, val, start, end, reason)
3978
 
 
3979
 
no_context_error_translators.register('UnicodeEncodeError',
3980
 
    _translate_unicode_error)
3981
 
no_context_error_translators.register('UnicodeDecodeError',
3982
 
    _translate_unicode_error)
3983
 
no_context_error_translators.register('ReadOnlyError',
3984
 
    lambda err: errors.TransportNotPossible('readonly transport'))
3985
 
no_context_error_translators.register('MemoryError',
3986
 
    lambda err: errors.BzrError("remote server out of memory\n"
3987
 
        "Retry non-remotely, or contact the server admin for details."))
3988
 
no_context_error_translators.register('RevisionNotPresent',
3989
 
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
3990
 
 
3991
 
no_context_error_translators.register('BzrCheckError',
3992
 
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
3993
 
 
 
2902
    elif err.error_verb == 'ReadOnlyError':
 
2903
        raise errors.TransportNotPossible('readonly transport')
 
2904
    raise errors.UnknownErrorFromSmartServer(err)