/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/remote.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-10 16:40:42 UTC
  • mfrom: (6653.6.7 rename-controldir)
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610164042-zrxqgy2htyduvke2
MergeĀ rename-controldirĀ branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2012 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import bz2
 
20
import zlib
18
21
 
19
 
from bzrlib import (
 
22
from . import (
20
23
    bencode,
21
24
    branch,
22
 
    bzrdir,
23
 
    config,
 
25
    config as _mod_config,
 
26
    controldir,
24
27
    debug,
25
28
    errors,
 
29
    gpg,
26
30
    graph,
27
31
    lock,
28
32
    lockdir,
29
 
    repository,
 
33
    osutils,
 
34
    registry,
30
35
    repository as _mod_repository,
31
 
    revision,
32
36
    revision as _mod_revision,
33
37
    static_tuple,
34
 
    symbol_versioning,
35
 
)
36
 
from bzrlib.branch import BranchReferenceFormat
37
 
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
 
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
 
from bzrlib.errors import (
 
38
    testament as _mod_testament,
 
39
    urlutils,
 
40
    )
 
41
from .bzr import (
 
42
    branch as bzrbranch,
 
43
    bzrdir as _mod_bzrdir,
 
44
    inventory_delta,
 
45
    vf_repository,
 
46
    vf_search,
 
47
    )
 
48
from .bzr.branch import BranchReferenceFormat
 
49
from .branch import BranchWriteLockResult
 
50
from .decorators import needs_read_lock, needs_write_lock, only_raises
 
51
from .errors import (
40
52
    NoSuchRevision,
41
53
    SmartProtocolError,
42
54
    )
43
 
from bzrlib.lockable_files import LockableFiles
44
 
from bzrlib.smart import client, vfs, repository as smart_repo
45
 
from bzrlib.revision import ensure_null, NULL_REVISION
46
 
from bzrlib.trace import mutter, note, warning
 
55
from .i18n import gettext
 
56
from .bzr.inventory import Inventory
 
57
from .lockable_files import LockableFiles
 
58
from .sixish import (
 
59
    viewitems,
 
60
    viewvalues,
 
61
    )
 
62
from .smart import client, vfs, repository as smart_repo
 
63
from .smart.client import _SmartClient
 
64
from .revision import NULL_REVISION
 
65
from .revisiontree import InventoryRevisionTree
 
66
from .repository import RepositoryWriteLockResult, _LazyListJoin
 
67
from .serializer import format_registry as serializer_format_registry
 
68
from .trace import mutter, note, warning, log_exception_quietly
 
69
from .bzr.versionedfile import FulltextContentFactory
 
70
 
 
71
 
 
72
_DEFAULT_SEARCH_DEPTH = 100
47
73
 
48
74
 
49
75
class _RpcHelper(object):
52
78
    def _call(self, method, *args, **err_context):
53
79
        try:
54
80
            return self._client.call(method, *args)
55
 
        except errors.ErrorFromSmartServer, err:
 
81
        except errors.ErrorFromSmartServer as err:
56
82
            self._translate_error(err, **err_context)
57
83
 
58
84
    def _call_expecting_body(self, method, *args, **err_context):
59
85
        try:
60
86
            return self._client.call_expecting_body(method, *args)
61
 
        except errors.ErrorFromSmartServer, err:
 
87
        except errors.ErrorFromSmartServer as err:
62
88
            self._translate_error(err, **err_context)
63
89
 
64
90
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
65
91
        try:
66
92
            return self._client.call_with_body_bytes(method, args, body_bytes)
67
 
        except errors.ErrorFromSmartServer, err:
 
93
        except errors.ErrorFromSmartServer as err:
68
94
            self._translate_error(err, **err_context)
69
95
 
70
96
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
72
98
        try:
73
99
            return self._client.call_with_body_bytes_expecting_body(
74
100
                method, args, body_bytes)
75
 
        except errors.ErrorFromSmartServer, err:
 
101
        except errors.ErrorFromSmartServer as err:
76
102
            self._translate_error(err, **err_context)
77
103
 
78
104
 
86
112
    return format
87
113
 
88
114
 
89
 
# Note: RemoteBzrDirFormat is in bzrdir.py
90
 
 
91
 
class RemoteBzrDir(BzrDir, _RpcHelper):
 
115
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.remote
 
116
# does not have to be imported unless a remote format is involved.
 
117
 
 
118
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
 
119
    """Format representing bzrdirs accessed via a smart server"""
 
120
 
 
121
    supports_workingtrees = False
 
122
 
 
123
    colocated_branches = False
 
124
 
 
125
    def __init__(self):
 
126
        _mod_bzrdir.BzrDirMetaFormat1.__init__(self)
 
127
        # XXX: It's a bit ugly that the network name is here, because we'd
 
128
        # like to believe that format objects are stateless or at least
 
129
        # immutable,  However, we do at least avoid mutating the name after
 
130
        # it's returned.  See <https://bugs.launchpad.net/bzr/+bug/504102>
 
131
        self._network_name = None
 
132
 
 
133
    def __repr__(self):
 
134
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
135
            self._network_name)
 
136
 
 
137
    def get_format_description(self):
 
138
        if self._network_name:
 
139
            try:
 
140
                real_format = controldir.network_format_registry.get(
 
141
                        self._network_name)
 
142
            except KeyError:
 
143
                pass
 
144
            else:
 
145
                return 'Remote: ' + real_format.get_format_description()
 
146
        return 'bzr remote bzrdir'
 
147
 
 
148
    def get_format_string(self):
 
149
        raise NotImplementedError(self.get_format_string)
 
150
 
 
151
    def network_name(self):
 
152
        if self._network_name:
 
153
            return self._network_name
 
154
        else:
 
155
            raise AssertionError("No network name set.")
 
156
 
 
157
    def initialize_on_transport(self, transport):
 
158
        try:
 
159
            # hand off the request to the smart server
 
160
            client_medium = transport.get_smart_medium()
 
161
        except errors.NoSmartMedium:
 
162
            # TODO: lookup the local format from a server hint.
 
163
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
164
            return local_dir_format.initialize_on_transport(transport)
 
165
        client = _SmartClient(client_medium)
 
166
        path = client.remote_path_from_transport(transport)
 
167
        try:
 
168
            response = client.call('BzrDirFormat.initialize', path)
 
169
        except errors.ErrorFromSmartServer as err:
 
170
            _translate_error(err, path=path)
 
171
        if response[0] != 'ok':
 
172
            raise errors.SmartProtocolError('unexpected response code %s' % (response,))
 
173
        format = RemoteBzrDirFormat()
 
174
        self._supply_sub_formats_to(format)
 
175
        return RemoteBzrDir(transport, format)
 
176
 
 
177
    def parse_NoneTrueFalse(self, arg):
 
178
        if not arg:
 
179
            return None
 
180
        if arg == 'False':
 
181
            return False
 
182
        if arg == 'True':
 
183
            return True
 
184
        raise AssertionError("invalid arg %r" % arg)
 
185
 
 
186
    def _serialize_NoneTrueFalse(self, arg):
 
187
        if arg is False:
 
188
            return 'False'
 
189
        if arg:
 
190
            return 'True'
 
191
        return ''
 
192
 
 
193
    def _serialize_NoneString(self, arg):
 
194
        return arg or ''
 
195
 
 
196
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
 
197
        create_prefix=False, force_new_repo=False, stacked_on=None,
 
198
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
 
199
        shared_repo=False):
 
200
        try:
 
201
            # hand off the request to the smart server
 
202
            client_medium = transport.get_smart_medium()
 
203
        except errors.NoSmartMedium:
 
204
            do_vfs = True
 
205
        else:
 
206
            # Decline to open it if the server doesn't support our required
 
207
            # version (3) so that the VFS-based transport will do it.
 
208
            if client_medium.should_probe():
 
209
                try:
 
210
                    server_version = client_medium.protocol_version()
 
211
                    if server_version != '2':
 
212
                        do_vfs = True
 
213
                    else:
 
214
                        do_vfs = False
 
215
                except errors.SmartProtocolError:
 
216
                    # Apparently there's no usable smart server there, even though
 
217
                    # the medium supports the smart protocol.
 
218
                    do_vfs = True
 
219
            else:
 
220
                do_vfs = False
 
221
        if not do_vfs:
 
222
            client = _SmartClient(client_medium)
 
223
            path = client.remote_path_from_transport(transport)
 
224
            if client_medium._is_remote_before((1, 16)):
 
225
                do_vfs = True
 
226
        if do_vfs:
 
227
            # TODO: lookup the local format from a server hint.
 
228
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
229
            self._supply_sub_formats_to(local_dir_format)
 
230
            return local_dir_format.initialize_on_transport_ex(transport,
 
231
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
232
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
233
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
234
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
235
                vfs_only=True)
 
236
        return self._initialize_on_transport_ex_rpc(client, path, transport,
 
237
            use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
238
            stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
 
239
 
 
240
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
 
241
        use_existing_dir, create_prefix, force_new_repo, stacked_on,
 
242
        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
 
243
        args = []
 
244
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
 
245
        args.append(self._serialize_NoneTrueFalse(create_prefix))
 
246
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
 
247
        args.append(self._serialize_NoneString(stacked_on))
 
248
        # stack_on_pwd is often/usually our transport
 
249
        if stack_on_pwd:
 
250
            try:
 
251
                stack_on_pwd = transport.relpath(stack_on_pwd)
 
252
                if not stack_on_pwd:
 
253
                    stack_on_pwd = '.'
 
254
            except errors.PathNotChild:
 
255
                pass
 
256
        args.append(self._serialize_NoneString(stack_on_pwd))
 
257
        args.append(self._serialize_NoneString(repo_format_name))
 
258
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
 
259
        args.append(self._serialize_NoneTrueFalse(shared_repo))
 
260
        request_network_name = self._network_name or \
 
261
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
 
262
        try:
 
263
            response = client.call('BzrDirFormat.initialize_ex_1.16',
 
264
                request_network_name, path, *args)
 
265
        except errors.UnknownSmartMethod:
 
266
            client._medium._remember_remote_is_before((1,16))
 
267
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
 
268
            self._supply_sub_formats_to(local_dir_format)
 
269
            return local_dir_format.initialize_on_transport_ex(transport,
 
270
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
 
271
                force_new_repo=force_new_repo, stacked_on=stacked_on,
 
272
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
 
273
                make_working_trees=make_working_trees, shared_repo=shared_repo,
 
274
                vfs_only=True)
 
275
        except errors.ErrorFromSmartServer as err:
 
276
            _translate_error(err, path=path)
 
277
        repo_path = response[0]
 
278
        bzrdir_name = response[6]
 
279
        require_stacking = response[7]
 
280
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
 
281
        format = RemoteBzrDirFormat()
 
282
        format._network_name = bzrdir_name
 
283
        self._supply_sub_formats_to(format)
 
284
        bzrdir = RemoteBzrDir(transport, format, _client=client)
 
285
        if repo_path:
 
286
            repo_format = response_tuple_to_repo_format(response[1:])
 
287
            if repo_path == '.':
 
288
                repo_path = ''
 
289
            if repo_path:
 
290
                repo_bzrdir_format = RemoteBzrDirFormat()
 
291
                repo_bzrdir_format._network_name = response[5]
 
292
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
 
293
                    repo_bzrdir_format)
 
294
            else:
 
295
                repo_bzr = bzrdir
 
296
            final_stack = response[8] or None
 
297
            final_stack_pwd = response[9] or None
 
298
            if final_stack_pwd:
 
299
                final_stack_pwd = urlutils.join(
 
300
                    transport.base, final_stack_pwd)
 
301
            remote_repo = RemoteRepository(repo_bzr, repo_format)
 
302
            if len(response) > 10:
 
303
                # Updated server verb that locks remotely.
 
304
                repo_lock_token = response[10] or None
 
305
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
 
306
                if repo_lock_token:
 
307
                    remote_repo.dont_leave_lock_in_place()
 
308
            else:
 
309
                remote_repo.lock_write()
 
310
            policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
 
311
                final_stack_pwd, require_stacking)
 
312
            policy.acquire_repository()
 
313
        else:
 
314
            remote_repo = None
 
315
            policy = None
 
316
        bzrdir._format.set_branch_format(self.get_branch_format())
 
317
        if require_stacking:
 
318
            # The repo has already been created, but we need to make sure that
 
319
            # we'll make a stackable branch.
 
320
            bzrdir._format.require_stacking(_skip_repo=True)
 
321
        return remote_repo, bzrdir, require_stacking, policy
 
322
 
 
323
    def _open(self, transport):
 
324
        return RemoteBzrDir(transport, self)
 
325
 
 
326
    def __eq__(self, other):
 
327
        if not isinstance(other, RemoteBzrDirFormat):
 
328
            return False
 
329
        return self.get_format_description() == other.get_format_description()
 
330
 
 
331
    def __return_repository_format(self):
 
332
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
 
333
        # repository format has been asked for, tell the RemoteRepositoryFormat
 
334
        # that it should use that for init() etc.
 
335
        result = RemoteRepositoryFormat()
 
336
        custom_format = getattr(self, '_repository_format', None)
 
337
        if custom_format:
 
338
            if isinstance(custom_format, RemoteRepositoryFormat):
 
339
                return custom_format
 
340
            else:
 
341
                # We will use the custom format to create repositories over the
 
342
                # wire; expose its details like rich_root_data for code to
 
343
                # query
 
344
                result._custom_format = custom_format
 
345
        return result
 
346
 
 
347
    def get_branch_format(self):
 
348
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
 
349
        if not isinstance(result, RemoteBranchFormat):
 
350
            new_result = RemoteBranchFormat()
 
351
            new_result._custom_format = result
 
352
            # cache the result
 
353
            self.set_branch_format(new_result)
 
354
            result = new_result
 
355
        return result
 
356
 
 
357
    repository_format = property(__return_repository_format,
 
358
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
 
359
 
 
360
 
 
361
class RemoteControlStore(_mod_config.IniFileStore):
 
362
    """Control store which attempts to use HPSS calls to retrieve control store.
 
363
 
 
364
    Note that this is specific to bzr-based formats.
 
365
    """
 
366
 
 
367
    def __init__(self, bzrdir):
 
368
        super(RemoteControlStore, self).__init__()
 
369
        self.controldir = bzrdir
 
370
        self._real_store = None
 
371
 
 
372
    def lock_write(self, token=None):
 
373
        self._ensure_real()
 
374
        return self._real_store.lock_write(token)
 
375
 
 
376
    def unlock(self):
 
377
        self._ensure_real()
 
378
        return self._real_store.unlock()
 
379
 
 
380
    @needs_write_lock
 
381
    def save(self):
 
382
        # We need to be able to override the undecorated implementation
 
383
        self.save_without_locking()
 
384
 
 
385
    def save_without_locking(self):
 
386
        super(RemoteControlStore, self).save()
 
387
 
 
388
    def _ensure_real(self):
 
389
        self.controldir._ensure_real()
 
390
        if self._real_store is None:
 
391
            self._real_store = _mod_config.ControlStore(self.controldir)
 
392
 
 
393
    def external_url(self):
 
394
        return urlutils.join(self.branch.user_url, 'control.conf')
 
395
 
 
396
    def _load_content(self):
 
397
        medium = self.controldir._client._medium
 
398
        path = self.controldir._path_for_remote_call(self.controldir._client)
 
399
        try:
 
400
            response, handler = self.controldir._call_expecting_body(
 
401
                'BzrDir.get_config_file', path)
 
402
        except errors.UnknownSmartMethod:
 
403
            self._ensure_real()
 
404
            return self._real_store._load_content()
 
405
        if len(response) and response[0] != 'ok':
 
406
            raise errors.UnexpectedSmartServerResponse(response)
 
407
        return handler.read_body_bytes()
 
408
 
 
409
    def _save_content(self, content):
 
410
        # FIXME JRV 2011-11-22: Ideally this should use a
 
411
        # HPSS call too, but at the moment it is not possible
 
412
        # to write lock control directories.
 
413
        self._ensure_real()
 
414
        return self._real_store._save_content(content)
 
415
 
 
416
 
 
417
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
92
418
    """Control directory on a remote server, accessed via bzr:// or similar."""
93
419
 
94
420
    def __init__(self, transport, format, _client=None, _force_probe=False):
97
423
        :param _client: Private parameter for testing. Disables probing and the
98
424
            use of a real bzrdir.
99
425
        """
100
 
        BzrDir.__init__(self, transport, format)
 
426
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
101
427
        # this object holds a delegated bzrdir that uses file-level operations
102
428
        # to talk to the other side
103
429
        self._real_bzrdir = None
163
489
                import traceback
164
490
                warning('VFS BzrDir access triggered\n%s',
165
491
                    ''.join(traceback.format_stack()))
166
 
            self._real_bzrdir = BzrDir.open_from_transport(
167
 
                self.root_transport, _server_formats=False)
 
492
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
 
493
                self.root_transport, probers=[_mod_bzrdir.BzrProber])
168
494
            self._format._network_name = \
169
495
                self._real_bzrdir._format.network_name()
170
496
 
175
501
        # Prevent aliasing problems in the next_open_branch_result cache.
176
502
        # See create_branch for rationale.
177
503
        self._next_open_branch_result = None
178
 
        return BzrDir.break_lock(self)
 
504
        return _mod_bzrdir.BzrDir.break_lock(self)
 
505
 
 
506
    def _vfs_checkout_metadir(self):
 
507
        self._ensure_real()
 
508
        return self._real_bzrdir.checkout_metadir()
 
509
 
 
510
    def checkout_metadir(self):
 
511
        """Retrieve the controldir format to use for checkouts of this one.
 
512
        """
 
513
        medium = self._client._medium
 
514
        if medium._is_remote_before((2, 5)):
 
515
            return self._vfs_checkout_metadir()
 
516
        path = self._path_for_remote_call(self._client)
 
517
        try:
 
518
            response = self._client.call('BzrDir.checkout_metadir',
 
519
                path)
 
520
        except errors.UnknownSmartMethod:
 
521
            medium._remember_remote_is_before((2, 5))
 
522
            return self._vfs_checkout_metadir()
 
523
        if len(response) != 3:
 
524
            raise errors.UnexpectedSmartServerResponse(response)
 
525
        control_name, repo_name, branch_name = response
 
526
        try:
 
527
            format = controldir.network_format_registry.get(control_name)
 
528
        except KeyError:
 
529
            raise errors.UnknownFormatError(kind='control',
 
530
                format=control_name)
 
531
        if repo_name:
 
532
            try:
 
533
                repo_format = _mod_repository.network_format_registry.get(
 
534
                    repo_name)
 
535
            except KeyError:
 
536
                raise errors.UnknownFormatError(kind='repository',
 
537
                    format=repo_name)
 
538
            format.repository_format = repo_format
 
539
        if branch_name:
 
540
            try:
 
541
                format.set_branch_format(
 
542
                    branch.network_format_registry.get(branch_name))
 
543
            except KeyError:
 
544
                raise errors.UnknownFormatError(kind='branch',
 
545
                    format=branch_name)
 
546
        return format
179
547
 
180
548
    def _vfs_cloning_metadir(self, require_stacking=False):
181
549
        self._ensure_real()
197
565
        except errors.UnknownSmartMethod:
198
566
            medium._remember_remote_is_before((1, 13))
199
567
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
 
        except errors.UnknownErrorFromSmartServer, err:
 
568
        except errors.UnknownErrorFromSmartServer as err:
201
569
            if err.error_tuple != ('BranchReference',):
202
570
                raise
203
571
            # We need to resolve the branch reference to determine the
205
573
            # referenced branch (and bzrdir, etc) but only when the caller
206
574
            # didn't already resolve the branch reference.
207
575
            referenced_branch = self.open_branch()
208
 
            return referenced_branch.bzrdir.cloning_metadir()
 
576
            return referenced_branch.controldir.cloning_metadir()
209
577
        if len(response) != 3:
210
578
            raise errors.UnexpectedSmartServerResponse(response)
211
579
        control_name, repo_name, branch_info = response
212
580
        if len(branch_info) != 2:
213
581
            raise errors.UnexpectedSmartServerResponse(response)
214
582
        branch_ref, branch_name = branch_info
215
 
        format = bzrdir.network_format_registry.get(control_name)
 
583
        try:
 
584
            format = controldir.network_format_registry.get(control_name)
 
585
        except KeyError:
 
586
            raise errors.UnknownFormatError(kind='control', format=control_name)
 
587
 
216
588
        if repo_name:
217
 
            format.repository_format = repository.network_format_registry.get(
218
 
                repo_name)
 
589
            try:
 
590
                format.repository_format = _mod_repository.network_format_registry.get(
 
591
                    repo_name)
 
592
            except KeyError:
 
593
                raise errors.UnknownFormatError(kind='repository',
 
594
                    format=repo_name)
219
595
        if branch_ref == 'ref':
220
596
            # XXX: we need possible_transports here to avoid reopening the
221
597
            # connection to the referenced location
222
 
            ref_bzrdir = BzrDir.open(branch_name)
 
598
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
223
599
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
600
            format.set_branch_format(branch_format)
225
601
        elif branch_ref == 'branch':
226
602
            if branch_name:
227
 
                format.set_branch_format(
228
 
                    branch.network_format_registry.get(branch_name))
 
603
                try:
 
604
                    branch_format = branch.network_format_registry.get(
 
605
                        branch_name)
 
606
                except KeyError:
 
607
                    raise errors.UnknownFormatError(kind='branch',
 
608
                        format=branch_name)
 
609
                format.set_branch_format(branch_format)
229
610
        else:
230
611
            raise errors.UnexpectedSmartServerResponse(response)
231
612
        return format
241
622
 
242
623
    def destroy_repository(self):
243
624
        """See BzrDir.destroy_repository"""
244
 
        self._ensure_real()
245
 
        self._real_bzrdir.destroy_repository()
 
625
        path = self._path_for_remote_call(self._client)
 
626
        try:
 
627
            response = self._call('BzrDir.destroy_repository', path)
 
628
        except errors.UnknownSmartMethod:
 
629
            self._ensure_real()
 
630
            self._real_bzrdir.destroy_repository()
 
631
            return
 
632
        if response[0] != 'ok':
 
633
            raise SmartProtocolError('unexpected response code %s' % (response,))
246
634
 
247
 
    def create_branch(self, name=None):
 
635
    def create_branch(self, name=None, repository=None,
 
636
                      append_revisions_only=None):
 
637
        if name is None:
 
638
            name = self._get_selected_branch()
 
639
        if name != "":
 
640
            raise errors.NoColocatedBranchSupport(self)
248
641
        # as per meta1 formats - just delegate to the format object which may
249
642
        # be parameterised.
250
643
        real_branch = self._format.get_branch_format().initialize(self,
251
 
            name=name)
 
644
            name=name, repository=repository,
 
645
            append_revisions_only=append_revisions_only)
252
646
        if not isinstance(real_branch, RemoteBranch):
253
 
            result = RemoteBranch(self, self.find_repository(), real_branch,
254
 
                                  name=name)
 
647
            if not isinstance(repository, RemoteRepository):
 
648
                raise AssertionError(
 
649
                    'need a RemoteRepository to use with RemoteBranch, got %r'
 
650
                    % (repository,))
 
651
            result = RemoteBranch(self, repository, real_branch, name=name)
255
652
        else:
256
653
            result = real_branch
257
654
        # BzrDir.clone_on_transport() uses the result of create_branch but does
265
662
 
266
663
    def destroy_branch(self, name=None):
267
664
        """See BzrDir.destroy_branch"""
268
 
        self._ensure_real()
269
 
        self._real_bzrdir.destroy_branch(name=name)
 
665
        if name is None:
 
666
            name = self._get_selected_branch()
 
667
        if name != "":
 
668
            raise errors.NoColocatedBranchSupport(self)
 
669
        path = self._path_for_remote_call(self._client)
 
670
        try:
 
671
            if name != "":
 
672
                args = (name, )
 
673
            else:
 
674
                args = ()
 
675
            response = self._call('BzrDir.destroy_branch', path, *args)
 
676
        except errors.UnknownSmartMethod:
 
677
            self._ensure_real()
 
678
            self._real_bzrdir.destroy_branch(name=name)
 
679
            self._next_open_branch_result = None
 
680
            return
270
681
        self._next_open_branch_result = None
 
682
        if response[0] != 'ok':
 
683
            raise SmartProtocolError('unexpected response code %s' % (response,))
271
684
 
272
 
    def create_workingtree(self, revision_id=None, from_branch=None):
 
685
    def create_workingtree(self, revision_id=None, from_branch=None,
 
686
        accelerator_tree=None, hardlink=False):
273
687
        raise errors.NotLocalUrl(self.transport.base)
274
688
 
275
 
    def find_branch_format(self):
 
689
    def find_branch_format(self, name=None):
276
690
        """Find the branch 'format' for this bzrdir.
277
691
 
278
692
        This might be a synthetic object for e.g. RemoteBranch and SVN.
279
693
        """
280
 
        b = self.open_branch()
 
694
        b = self.open_branch(name=name)
281
695
        return b._format
282
696
 
283
 
    def get_branch_reference(self):
 
697
    def get_branches(self, possible_transports=None, ignore_fallbacks=False):
 
698
        path = self._path_for_remote_call(self._client)
 
699
        try:
 
700
            response, handler = self._call_expecting_body(
 
701
                'BzrDir.get_branches', path)
 
702
        except errors.UnknownSmartMethod:
 
703
            self._ensure_real()
 
704
            return self._real_bzrdir.get_branches()
 
705
        if response[0] != "success":
 
706
            raise errors.UnexpectedSmartServerResponse(response)
 
707
        body = bencode.bdecode(handler.read_body_bytes())
 
708
        ret = {}
 
709
        for name, value in viewitems(body):
 
710
            ret[name] = self._open_branch(name, value[0], value[1],
 
711
                possible_transports=possible_transports,
 
712
                ignore_fallbacks=ignore_fallbacks)
 
713
        return ret
 
714
 
 
715
    def set_branch_reference(self, target_branch, name=None):
 
716
        """See BzrDir.set_branch_reference()."""
 
717
        if name is None:
 
718
            name = self._get_selected_branch()
 
719
        if name != "":
 
720
            raise errors.NoColocatedBranchSupport(self)
 
721
        self._ensure_real()
 
722
        return self._real_bzrdir.set_branch_reference(target_branch, name=name)
 
723
 
 
724
    def get_branch_reference(self, name=None):
284
725
        """See BzrDir.get_branch_reference()."""
 
726
        if name is None:
 
727
            name = self._get_selected_branch()
 
728
        if name != "":
 
729
            raise errors.NoColocatedBranchSupport(self)
285
730
        response = self._get_branch_reference()
286
731
        if response[0] == 'ref':
287
732
            return response[1]
318
763
            raise errors.UnexpectedSmartServerResponse(response)
319
764
        return response
320
765
 
321
 
    def _get_tree_branch(self):
 
766
    def _get_tree_branch(self, name=None):
322
767
        """See BzrDir._get_tree_branch()."""
323
 
        return None, self.open_branch()
 
768
        return None, self.open_branch(name=name)
324
769
 
325
 
    def open_branch(self, name=None, unsupported=False,
326
 
                    ignore_fallbacks=False):
327
 
        if unsupported:
328
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
329
 
        if self._next_open_branch_result is not None:
330
 
            # See create_branch for details.
331
 
            result = self._next_open_branch_result
332
 
            self._next_open_branch_result = None
333
 
            return result
334
 
        response = self._get_branch_reference()
335
 
        if response[0] == 'ref':
 
770
    def _open_branch(self, name, kind, location_or_format,
 
771
                     ignore_fallbacks=False, possible_transports=None):
 
772
        if kind == 'ref':
336
773
            # a branch reference, use the existing BranchReference logic.
337
774
            format = BranchReferenceFormat()
338
775
            return format.open(self, name=name, _found=True,
339
 
                location=response[1], ignore_fallbacks=ignore_fallbacks)
340
 
        branch_format_name = response[1]
 
776
                location=location_or_format, ignore_fallbacks=ignore_fallbacks,
 
777
                possible_transports=possible_transports)
 
778
        branch_format_name = location_or_format
341
779
        if not branch_format_name:
342
780
            branch_format_name = None
343
781
        format = RemoteBranchFormat(network_name=branch_format_name)
344
782
        return RemoteBranch(self, self.find_repository(), format=format,
345
 
            setup_stacking=not ignore_fallbacks, name=name)
 
783
            setup_stacking=not ignore_fallbacks, name=name,
 
784
            possible_transports=possible_transports)
 
785
 
 
786
    def open_branch(self, name=None, unsupported=False,
 
787
                    ignore_fallbacks=False, possible_transports=None):
 
788
        if name is None:
 
789
            name = self._get_selected_branch()
 
790
        if name != "":
 
791
            raise errors.NoColocatedBranchSupport(self)
 
792
        if unsupported:
 
793
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
794
        if self._next_open_branch_result is not None:
 
795
            # See create_branch for details.
 
796
            result = self._next_open_branch_result
 
797
            self._next_open_branch_result = None
 
798
            return result
 
799
        response = self._get_branch_reference()
 
800
        return self._open_branch(name, response[0], response[1],
 
801
            possible_transports=possible_transports,
 
802
            ignore_fallbacks=ignore_fallbacks)
346
803
 
347
804
    def _open_repo_v1(self, path):
348
805
        verb = 'BzrDir.find_repository'
411
868
 
412
869
    def has_workingtree(self):
413
870
        if self._has_working_tree is None:
414
 
            self._ensure_real()
415
 
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
871
            path = self._path_for_remote_call(self._client)
 
872
            try:
 
873
                response = self._call('BzrDir.has_workingtree', path)
 
874
            except errors.UnknownSmartMethod:
 
875
                self._ensure_real()
 
876
                self._has_working_tree = self._real_bzrdir.has_workingtree()
 
877
            else:
 
878
                if response[0] not in ('yes', 'no'):
 
879
                    raise SmartProtocolError('unexpected response code %s' % (response,))
 
880
                self._has_working_tree = (response[0] == 'yes')
416
881
        return self._has_working_tree
417
882
 
418
883
    def open_workingtree(self, recommend_upgrade=True):
423
888
 
424
889
    def _path_for_remote_call(self, client):
425
890
        """Return the path to be used for this bzrdir in a remote call."""
426
 
        return client.remote_path_from_transport(self.root_transport)
 
891
        return urlutils.split_segment_parameters_raw(
 
892
            client.remote_path_from_transport(self.root_transport))[0]
427
893
 
428
894
    def get_branch_transport(self, branch_format, name=None):
429
895
        self._ensure_real()
441
907
        """Upgrading of remote bzrdirs is not supported yet."""
442
908
        return False
443
909
 
444
 
    def needs_format_conversion(self, format=None):
 
910
    def needs_format_conversion(self, format):
445
911
        """Upgrading of remote bzrdirs is not supported yet."""
446
 
        if format is None:
447
 
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
448
 
                % 'needs_format_conversion(format=None)')
449
912
        return False
450
913
 
451
 
    def clone(self, url, revision_id=None, force_new_repo=False,
452
 
              preserve_stacking=False):
453
 
        self._ensure_real()
454
 
        return self._real_bzrdir.clone(url, revision_id=revision_id,
455
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
456
 
 
457
914
    def _get_config(self):
458
915
        return RemoteBzrDirConfig(self)
459
916
 
460
 
 
461
 
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
917
    def _get_config_store(self):
 
918
        return RemoteControlStore(self)
 
919
 
 
920
 
 
921
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
462
922
    """Format for repositories accessed over a _SmartClient.
463
923
 
464
924
    Instances of this repository are represented by RemoteRepository
479
939
    """
480
940
 
481
941
    _matchingbzrdir = RemoteBzrDirFormat()
 
942
    supports_full_versioned_files = True
 
943
    supports_leaving_lock = True
482
944
 
483
945
    def __init__(self):
484
 
        repository.RepositoryFormat.__init__(self)
 
946
        _mod_repository.RepositoryFormat.__init__(self)
485
947
        self._custom_format = None
486
948
        self._network_name = None
487
949
        self._creating_bzrdir = None
 
950
        self._revision_graph_can_have_wrong_parents = None
488
951
        self._supports_chks = None
489
952
        self._supports_external_lookups = None
490
953
        self._supports_tree_reference = None
 
954
        self._supports_funky_characters = None
 
955
        self._supports_nesting_repositories = None
491
956
        self._rich_root_data = None
492
957
 
493
958
    def __repr__(self):
522
987
        return self._supports_external_lookups
523
988
 
524
989
    @property
 
990
    def supports_funky_characters(self):
 
991
        if self._supports_funky_characters is None:
 
992
            self._ensure_real()
 
993
            self._supports_funky_characters = \
 
994
                self._custom_format.supports_funky_characters
 
995
        return self._supports_funky_characters
 
996
 
 
997
    @property
 
998
    def supports_nesting_repositories(self):
 
999
        if self._supports_nesting_repositories is None:
 
1000
            self._ensure_real()
 
1001
            self._supports_nesting_repositories = \
 
1002
                self._custom_format.supports_nesting_repositories
 
1003
        return self._supports_nesting_repositories
 
1004
 
 
1005
    @property
525
1006
    def supports_tree_reference(self):
526
1007
        if self._supports_tree_reference is None:
527
1008
            self._ensure_real()
529
1010
                self._custom_format.supports_tree_reference
530
1011
        return self._supports_tree_reference
531
1012
 
532
 
    def _vfs_initialize(self, a_bzrdir, shared):
 
1013
    @property
 
1014
    def revision_graph_can_have_wrong_parents(self):
 
1015
        if self._revision_graph_can_have_wrong_parents is None:
 
1016
            self._ensure_real()
 
1017
            self._revision_graph_can_have_wrong_parents = \
 
1018
                self._custom_format.revision_graph_can_have_wrong_parents
 
1019
        return self._revision_graph_can_have_wrong_parents
 
1020
 
 
1021
    def _vfs_initialize(self, a_controldir, shared):
533
1022
        """Helper for common code in initialize."""
534
1023
        if self._custom_format:
535
1024
            # Custom format requested
536
 
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
1025
            result = self._custom_format.initialize(a_controldir, shared=shared)
537
1026
        elif self._creating_bzrdir is not None:
538
1027
            # Use the format that the repository we were created to back
539
1028
            # has.
540
1029
            prior_repo = self._creating_bzrdir.open_repository()
541
1030
            prior_repo._ensure_real()
542
1031
            result = prior_repo._real_repository._format.initialize(
543
 
                a_bzrdir, shared=shared)
 
1032
                a_controldir, shared=shared)
544
1033
        else:
545
1034
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
1035
            # support remote initialization.
547
1036
            # We delegate to a real object at this point (as RemoteBzrDir
548
1037
            # delegate to the repository format which would lead to infinite
549
 
            # recursion if we just called a_bzrdir.create_repository.
550
 
            a_bzrdir._ensure_real()
551
 
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
1038
            # recursion if we just called a_controldir.create_repository.
 
1039
            a_controldir._ensure_real()
 
1040
            result = a_controldir._real_bzrdir.create_repository(shared=shared)
552
1041
        if not isinstance(result, RemoteRepository):
553
 
            return self.open(a_bzrdir)
 
1042
            return self.open(a_controldir)
554
1043
        else:
555
1044
            return result
556
1045
 
557
 
    def initialize(self, a_bzrdir, shared=False):
 
1046
    def initialize(self, a_controldir, shared=False):
558
1047
        # Being asked to create on a non RemoteBzrDir:
559
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
560
 
            return self._vfs_initialize(a_bzrdir, shared)
561
 
        medium = a_bzrdir._client._medium
 
1048
        if not isinstance(a_controldir, RemoteBzrDir):
 
1049
            return self._vfs_initialize(a_controldir, shared)
 
1050
        medium = a_controldir._client._medium
562
1051
        if medium._is_remote_before((1, 13)):
563
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1052
            return self._vfs_initialize(a_controldir, shared)
564
1053
        # Creating on a remote bzr dir.
565
1054
        # 1) get the network name to use.
566
1055
        if self._custom_format:
568
1057
        elif self._network_name:
569
1058
            network_name = self._network_name
570
1059
        else:
571
 
            # Select the current bzrlib default and ask for that.
572
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
1060
            # Select the current breezy default and ask for that.
 
1061
            reference_bzrdir_format = controldir.format_registry.get('default')()
573
1062
            reference_format = reference_bzrdir_format.repository_format
574
1063
            network_name = reference_format.network_name()
575
1064
        # 2) try direct creation via RPC
576
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
1065
        path = a_controldir._path_for_remote_call(a_controldir._client)
577
1066
        verb = 'BzrDir.create_repository'
578
1067
        if shared:
579
1068
            shared_str = 'True'
580
1069
        else:
581
1070
            shared_str = 'False'
582
1071
        try:
583
 
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
1072
            response = a_controldir._call(verb, path, network_name, shared_str)
584
1073
        except errors.UnknownSmartMethod:
585
1074
            # Fallback - use vfs methods
586
1075
            medium._remember_remote_is_before((1, 13))
587
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1076
            return self._vfs_initialize(a_controldir, shared)
588
1077
        else:
589
1078
            # Turn the response into a RemoteRepository object.
590
1079
            format = response_tuple_to_repo_format(response[1:])
591
1080
            # Used to support creating a real format instance when needed.
592
 
            format._creating_bzrdir = a_bzrdir
593
 
            remote_repo = RemoteRepository(a_bzrdir, format)
 
1081
            format._creating_bzrdir = a_controldir
 
1082
            remote_repo = RemoteRepository(a_controldir, format)
594
1083
            format._creating_repo = remote_repo
595
1084
            return remote_repo
596
1085
 
597
 
    def open(self, a_bzrdir):
598
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
599
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
 
        return a_bzrdir.open_repository()
 
1086
    def open(self, a_controldir):
 
1087
        if not isinstance(a_controldir, RemoteBzrDir):
 
1088
            raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
 
1089
        return a_controldir.open_repository()
601
1090
 
602
1091
    def _ensure_real(self):
603
1092
        if self._custom_format is None:
604
 
            self._custom_format = repository.network_format_registry.get(
605
 
                self._network_name)
 
1093
            try:
 
1094
                self._custom_format = _mod_repository.network_format_registry.get(
 
1095
                    self._network_name)
 
1096
            except KeyError:
 
1097
                raise errors.UnknownFormatError(kind='repository',
 
1098
                    format=self._network_name)
606
1099
 
607
1100
    @property
608
1101
    def _fetch_order(self):
643
1136
        return self._custom_format._serializer
644
1137
 
645
1138
 
646
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
647
 
    bzrdir.ControlComponent):
 
1139
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
 
1140
        lock._RelockDebugMixin):
648
1141
    """Repository accessed over rpc.
649
1142
 
650
1143
    For the moment most operations are performed using local transport-backed
666
1159
            self._real_repository = real_repository
667
1160
        else:
668
1161
            self._real_repository = None
669
 
        self.bzrdir = remote_bzrdir
 
1162
        self.controldir = remote_bzrdir
670
1163
        if _client is None:
671
1164
            self._client = remote_bzrdir._client
672
1165
        else:
674
1167
        self._format = format
675
1168
        self._lock_mode = None
676
1169
        self._lock_token = None
 
1170
        self._write_group_tokens = None
677
1171
        self._lock_count = 0
678
1172
        self._leave_lock = False
679
1173
        # Cache of revision parents; misses are cached during read locks, and
689
1183
        self._reconcile_does_inventory_gc = False
690
1184
        self._reconcile_fixes_text_parents = False
691
1185
        self._reconcile_backsup_inventory = False
692
 
        self.base = self.bzrdir.transport.base
 
1186
        self.base = self.controldir.transport.base
693
1187
        # Additional places to query for data.
694
1188
        self._fallback_repositories = []
695
1189
 
696
1190
    @property
697
1191
    def user_transport(self):
698
 
        return self.bzrdir.user_transport
 
1192
        return self.controldir.user_transport
699
1193
 
700
1194
    @property
701
1195
    def control_transport(self):
702
1196
        # XXX: Normally you shouldn't directly get at the remote repository
703
1197
        # transport, but I'm not sure it's worth making this method
704
1198
        # optional -- mbp 2010-04-21
705
 
        return self.bzrdir.get_repository_transport(None)
706
 
        
 
1199
        return self.controldir.get_repository_transport(None)
 
1200
 
707
1201
    def __str__(self):
708
1202
        return "%s(%s)" % (self.__class__.__name__, self.base)
709
1203
 
719
1213
 
720
1214
        :param suppress_errors: see Repository.abort_write_group.
721
1215
        """
722
 
        self._ensure_real()
723
 
        return self._real_repository.abort_write_group(
724
 
            suppress_errors=suppress_errors)
 
1216
        if self._real_repository:
 
1217
            self._ensure_real()
 
1218
            return self._real_repository.abort_write_group(
 
1219
                suppress_errors=suppress_errors)
 
1220
        if not self.is_in_write_group():
 
1221
            if suppress_errors:
 
1222
                mutter('(suppressed) not in write group')
 
1223
                return
 
1224
            raise errors.BzrError("not in write group")
 
1225
        path = self.controldir._path_for_remote_call(self._client)
 
1226
        try:
 
1227
            response = self._call('Repository.abort_write_group', path,
 
1228
                self._lock_token, self._write_group_tokens)
 
1229
        except Exception as exc:
 
1230
            self._write_group = None
 
1231
            if not suppress_errors:
 
1232
                raise
 
1233
            mutter('abort_write_group failed')
 
1234
            log_exception_quietly()
 
1235
            note(gettext('bzr: ERROR (ignored): %s'), exc)
 
1236
        else:
 
1237
            if response != ('ok', ):
 
1238
                raise errors.UnexpectedSmartServerResponse(response)
 
1239
            self._write_group_tokens = None
725
1240
 
726
1241
    @property
727
1242
    def chk_bytes(self):
741
1256
        for older plugins that don't use e.g. the CommitBuilder
742
1257
        facility.
743
1258
        """
744
 
        self._ensure_real()
745
 
        return self._real_repository.commit_write_group()
 
1259
        if self._real_repository:
 
1260
            self._ensure_real()
 
1261
            return self._real_repository.commit_write_group()
 
1262
        if not self.is_in_write_group():
 
1263
            raise errors.BzrError("not in write group")
 
1264
        path = self.controldir._path_for_remote_call(self._client)
 
1265
        response = self._call('Repository.commit_write_group', path,
 
1266
            self._lock_token, self._write_group_tokens)
 
1267
        if response != ('ok', ):
 
1268
            raise errors.UnexpectedSmartServerResponse(response)
 
1269
        self._write_group_tokens = None
 
1270
        # Refresh data after writing to the repository.
 
1271
        self.refresh_data()
746
1272
 
747
1273
    def resume_write_group(self, tokens):
748
 
        self._ensure_real()
749
 
        return self._real_repository.resume_write_group(tokens)
 
1274
        if self._real_repository:
 
1275
            return self._real_repository.resume_write_group(tokens)
 
1276
        path = self.controldir._path_for_remote_call(self._client)
 
1277
        try:
 
1278
            response = self._call('Repository.check_write_group', path,
 
1279
               self._lock_token, tokens)
 
1280
        except errors.UnknownSmartMethod:
 
1281
            self._ensure_real()
 
1282
            return self._real_repository.resume_write_group(tokens)
 
1283
        if response != ('ok', ):
 
1284
            raise errors.UnexpectedSmartServerResponse(response)
 
1285
        self._write_group_tokens = tokens
750
1286
 
751
1287
    def suspend_write_group(self):
752
 
        self._ensure_real()
753
 
        return self._real_repository.suspend_write_group()
 
1288
        if self._real_repository:
 
1289
            return self._real_repository.suspend_write_group()
 
1290
        ret = self._write_group_tokens or []
 
1291
        self._write_group_tokens = None
 
1292
        return ret
754
1293
 
755
1294
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
756
1295
        self._ensure_real()
764
1303
 
765
1304
    def get_rev_id_for_revno(self, revno, known_pair):
766
1305
        """See Repository.get_rev_id_for_revno."""
767
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1306
        path = self.controldir._path_for_remote_call(self._client)
768
1307
        try:
769
1308
            if self._client._medium._is_remote_before((1, 17)):
770
1309
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
807
1346
                warning('VFS Repository access triggered\n%s',
808
1347
                    ''.join(traceback.format_stack()))
809
1348
            self._unstacked_provider.missing_keys.clear()
810
 
            self.bzrdir._ensure_real()
 
1349
            self.controldir._ensure_real()
811
1350
            self._set_real_repository(
812
 
                self.bzrdir._real_bzrdir.open_repository())
 
1351
                self.controldir._real_bzrdir.open_repository())
813
1352
 
814
1353
    def _translate_error(self, err, **context):
815
 
        self.bzrdir._translate_error(err, repository=self, **context)
 
1354
        self.controldir._translate_error(err, repository=self, **context)
816
1355
 
817
1356
    def find_text_key_references(self):
818
1357
        """Find the text key references within the repository.
819
1358
 
820
 
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
821
 
        revision_ids. Each altered file-ids has the exact revision_ids that
822
 
        altered it listed explicitly.
823
1359
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
1360
            to whether they were referred to by the inventory of the
825
1361
            revision_id that they contain. The inventory texts from all present
843
1379
        """Private method for using with old (< 1.2) servers to fallback."""
844
1380
        if revision_id is None:
845
1381
            revision_id = ''
846
 
        elif revision.is_null(revision_id):
 
1382
        elif _mod_revision.is_null(revision_id):
847
1383
            return {}
848
1384
 
849
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1385
        path = self.controldir._path_for_remote_call(self._client)
850
1386
        response = self._call_expecting_body(
851
1387
            'Repository.get_revision_graph', path, revision_id)
852
1388
        response_tuple, response_handler = response
873
1409
        return RemoteStreamSource(self, to_format)
874
1410
 
875
1411
    @needs_read_lock
 
1412
    def get_file_graph(self):
 
1413
        return graph.Graph(self.texts)
 
1414
 
 
1415
    @needs_read_lock
876
1416
    def has_revision(self, revision_id):
877
1417
        """True if this repository has a copy of the revision."""
878
 
        # Copy of bzrlib.repository.Repository.has_revision
 
1418
        # Copy of breezy.repository.Repository.has_revision
879
1419
        return revision_id in self.has_revisions((revision_id,))
880
1420
 
881
1421
    @needs_read_lock
885
1425
        :param revision_ids: An iterable of revision_ids.
886
1426
        :return: A set of the revision_ids that were present.
887
1427
        """
888
 
        # Copy of bzrlib.repository.Repository.has_revisions
 
1428
        # Copy of breezy.repository.Repository.has_revisions
889
1429
        parent_map = self.get_parent_map(revision_ids)
890
1430
        result = set(parent_map)
891
1431
        if _mod_revision.NULL_REVISION in revision_ids:
895
1435
    def _has_same_fallbacks(self, other_repo):
896
1436
        """Returns true if the repositories have the same fallbacks."""
897
1437
        # XXX: copied from Repository; it should be unified into a base class
898
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
1438
        # <https://bugs.launchpad.net/bzr/+bug/401622>
899
1439
        my_fb = self._fallback_repositories
900
1440
        other_fb = other_repo._fallback_repositories
901
1441
        if len(my_fb) != len(other_fb):
910
1450
        # one; unfortunately the tests rely on slightly different behaviour at
911
1451
        # present -- mbp 20090710
912
1452
        return (self.__class__ is other.__class__ and
913
 
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
1453
                self.controldir.transport.base == other.controldir.transport.base)
914
1454
 
915
1455
    def get_graph(self, other_repository=None):
916
1456
        """Return the graph for this repository format"""
928
1468
 
929
1469
    def gather_stats(self, revid=None, committers=None):
930
1470
        """See Repository.gather_stats()."""
931
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1471
        path = self.controldir._path_for_remote_call(self._client)
932
1472
        # revid can be None to indicate no revisions, not just NULL_REVISION
933
 
        if revid is None or revision.is_null(revid):
 
1473
        if revid is None or _mod_revision.is_null(revid):
934
1474
            fmt_revid = ''
935
1475
        else:
936
1476
            fmt_revid = revid
953
1493
                result[key] = int(val_text)
954
1494
            elif key in ('firstrev', 'latestrev'):
955
1495
                values = val_text.split(' ')[1:]
956
 
                result[key] = (float(values[0]), long(values[1]))
 
1496
                result[key] = (float(values[0]), int(values[1]))
957
1497
 
958
1498
        return result
959
1499
 
965
1505
 
966
1506
    def get_physical_lock_status(self):
967
1507
        """See Repository.get_physical_lock_status()."""
968
 
        # should be an API call to the server.
969
 
        self._ensure_real()
970
 
        return self._real_repository.get_physical_lock_status()
 
1508
        path = self.controldir._path_for_remote_call(self._client)
 
1509
        try:
 
1510
            response = self._call('Repository.get_physical_lock_status', path)
 
1511
        except errors.UnknownSmartMethod:
 
1512
            self._ensure_real()
 
1513
            return self._real_repository.get_physical_lock_status()
 
1514
        if response[0] not in ('yes', 'no'):
 
1515
            raise errors.UnexpectedSmartServerResponse(response)
 
1516
        return (response[0] == 'yes')
971
1517
 
972
1518
    def is_in_write_group(self):
973
1519
        """Return True if there is an open write group.
974
1520
 
975
1521
        write groups are only applicable locally for the smart server..
976
1522
        """
 
1523
        if self._write_group_tokens is not None:
 
1524
            return True
977
1525
        if self._real_repository:
978
1526
            return self._real_repository.is_in_write_group()
979
1527
 
982
1530
 
983
1531
    def is_shared(self):
984
1532
        """See Repository.is_shared()."""
985
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1533
        path = self.controldir._path_for_remote_call(self._client)
986
1534
        response = self._call('Repository.is_shared', path)
987
1535
        if response[0] not in ('yes', 'no'):
988
1536
            raise SmartProtocolError('unexpected response code %s' % (response,))
997
1545
        pass
998
1546
 
999
1547
    def lock_read(self):
 
1548
        """Lock the repository for read operations.
 
1549
 
 
1550
        :return: A breezy.lock.LogicalLockResult.
 
1551
        """
1000
1552
        # wrong eventually - want a local lock cache context
1001
1553
        if not self._lock_mode:
1002
1554
            self._note_lock('r')
1009
1561
                repo.lock_read()
1010
1562
        else:
1011
1563
            self._lock_count += 1
 
1564
        return lock.LogicalLockResult(self.unlock)
1012
1565
 
1013
1566
    def _remote_lock_write(self, token):
1014
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1567
        path = self.controldir._path_for_remote_call(self._client)
1015
1568
        if token is None:
1016
1569
            token = ''
1017
1570
        err_context = {'token': token}
1054
1607
            raise errors.ReadOnlyError(self)
1055
1608
        else:
1056
1609
            self._lock_count += 1
1057
 
        return self._lock_token or None
 
1610
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1058
1611
 
1059
1612
    def leave_lock_in_place(self):
1060
1613
        if not self._lock_token:
1109
1662
            self._real_repository.lock_write(self._lock_token)
1110
1663
        elif self._lock_mode == 'r':
1111
1664
            self._real_repository.lock_read()
 
1665
        if self._write_group_tokens is not None:
 
1666
            # if we are already in a write group, resume it
 
1667
            self._real_repository.resume_write_group(self._write_group_tokens)
 
1668
            self._write_group_tokens = None
1112
1669
 
1113
1670
    def start_write_group(self):
1114
1671
        """Start a write group on the decorated repository.
1118
1675
        for older plugins that don't use e.g. the CommitBuilder
1119
1676
        facility.
1120
1677
        """
1121
 
        self._ensure_real()
1122
 
        return self._real_repository.start_write_group()
 
1678
        if self._real_repository:
 
1679
            self._ensure_real()
 
1680
            return self._real_repository.start_write_group()
 
1681
        if not self.is_write_locked():
 
1682
            raise errors.NotWriteLocked(self)
 
1683
        if self._write_group_tokens is not None:
 
1684
            raise errors.BzrError('already in a write group')
 
1685
        path = self.controldir._path_for_remote_call(self._client)
 
1686
        try:
 
1687
            response = self._call('Repository.start_write_group', path,
 
1688
                self._lock_token)
 
1689
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
 
1690
            self._ensure_real()
 
1691
            return self._real_repository.start_write_group()
 
1692
        if response[0] != 'ok':
 
1693
            raise errors.UnexpectedSmartServerResponse(response)
 
1694
        self._write_group_tokens = response[1]
1123
1695
 
1124
1696
    def _unlock(self, token):
1125
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1697
        path = self.controldir._path_for_remote_call(self._client)
1126
1698
        if not token:
1127
1699
            # with no token the remote repository is not persistently locked.
1128
1700
            return
1152
1724
            # This is just to let the _real_repository stay up to date.
1153
1725
            if self._real_repository is not None:
1154
1726
                self._real_repository.unlock()
 
1727
            elif self._write_group_tokens is not None:
 
1728
                self.abort_write_group()
1155
1729
        finally:
1156
1730
            # The rpc-level lock should be released even if there was a
1157
1731
            # problem releasing the vfs-based lock.
1169
1743
 
1170
1744
    def break_lock(self):
1171
1745
        # should hand off to the network
1172
 
        self._ensure_real()
1173
 
        return self._real_repository.break_lock()
 
1746
        path = self.controldir._path_for_remote_call(self._client)
 
1747
        try:
 
1748
            response = self._call("Repository.break_lock", path)
 
1749
        except errors.UnknownSmartMethod:
 
1750
            self._ensure_real()
 
1751
            return self._real_repository.break_lock()
 
1752
        if response != ('ok',):
 
1753
            raise errors.UnexpectedSmartServerResponse(response)
1174
1754
 
1175
1755
    def _get_tarball(self, compression):
1176
1756
        """Return a TemporaryFile containing a repository tarball.
1178
1758
        Returns None if the server does not support sending tarballs.
1179
1759
        """
1180
1760
        import tempfile
1181
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1761
        path = self.controldir._path_for_remote_call(self._client)
1182
1762
        try:
1183
1763
            response, protocol = self._call_expecting_body(
1184
1764
                'Repository.tarball', path, compression)
1194
1774
            return t
1195
1775
        raise errors.UnexpectedSmartServerResponse(response)
1196
1776
 
 
1777
    @needs_read_lock
1197
1778
    def sprout(self, to_bzrdir, revision_id=None):
1198
 
        # TODO: Option to control what format is created?
1199
 
        self._ensure_real()
1200
 
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
1201
 
                                                             shared=False)
 
1779
        """Create a descendent repository for new development.
 
1780
 
 
1781
        Unlike clone, this does not copy the settings of the repository.
 
1782
        """
 
1783
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1202
1784
        dest_repo.fetch(self, revision_id=revision_id)
1203
1785
        return dest_repo
1204
1786
 
 
1787
    def _create_sprouting_repo(self, a_controldir, shared):
 
1788
        if not isinstance(a_controldir._format, self.controldir._format.__class__):
 
1789
            # use target default format.
 
1790
            dest_repo = a_controldir.create_repository()
 
1791
        else:
 
1792
            # Most control formats need the repository to be specifically
 
1793
            # created, but on some old all-in-one formats it's not needed
 
1794
            try:
 
1795
                dest_repo = self._format.initialize(a_controldir, shared=shared)
 
1796
            except errors.UninitializableFormat:
 
1797
                dest_repo = a_controldir.open_repository()
 
1798
        return dest_repo
 
1799
 
1205
1800
    ### These methods are just thin shims to the VFS object for now.
1206
1801
 
 
1802
    @needs_read_lock
1207
1803
    def revision_tree(self, revision_id):
1208
 
        self._ensure_real()
1209
 
        return self._real_repository.revision_tree(revision_id)
 
1804
        revision_id = _mod_revision.ensure_null(revision_id)
 
1805
        if revision_id == _mod_revision.NULL_REVISION:
 
1806
            return InventoryRevisionTree(self,
 
1807
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
1808
        else:
 
1809
            return list(self.revision_trees([revision_id]))[0]
1210
1810
 
1211
1811
    def get_serializer_format(self):
1212
 
        self._ensure_real()
1213
 
        return self._real_repository.get_serializer_format()
 
1812
        path = self.controldir._path_for_remote_call(self._client)
 
1813
        try:
 
1814
            response = self._call('VersionedFileRepository.get_serializer_format',
 
1815
                path)
 
1816
        except errors.UnknownSmartMethod:
 
1817
            self._ensure_real()
 
1818
            return self._real_repository.get_serializer_format()
 
1819
        if response[0] != 'ok':
 
1820
            raise errors.UnexpectedSmartServerResponse(response)
 
1821
        return response[1]
1214
1822
 
1215
1823
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
1824
                           timezone=None, committer=None, revprops=None,
1217
 
                           revision_id=None):
1218
 
        # FIXME: It ought to be possible to call this without immediately
1219
 
        # triggering _ensure_real.  For now it's the easiest thing to do.
1220
 
        self._ensure_real()
1221
 
        real_repo = self._real_repository
1222
 
        builder = real_repo.get_commit_builder(branch, parents,
1223
 
                config, timestamp=timestamp, timezone=timezone,
1224
 
                committer=committer, revprops=revprops, revision_id=revision_id)
1225
 
        return builder
 
1825
                           revision_id=None, lossy=False):
 
1826
        """Obtain a CommitBuilder for this repository.
 
1827
 
 
1828
        :param branch: Branch to commit to.
 
1829
        :param parents: Revision ids of the parents of the new revision.
 
1830
        :param config: Configuration to use.
 
1831
        :param timestamp: Optional timestamp recorded for commit.
 
1832
        :param timezone: Optional timezone for timestamp.
 
1833
        :param committer: Optional committer to set for commit.
 
1834
        :param revprops: Optional dictionary of revision properties.
 
1835
        :param revision_id: Optional revision id.
 
1836
        :param lossy: Whether to discard data that can not be natively
 
1837
            represented, when pushing to a foreign VCS
 
1838
        """
 
1839
        if self._fallback_repositories and not self._format.supports_chks:
 
1840
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1841
                " in pre-2a formats. See "
 
1842
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
1843
        if self._format.rich_root_data:
 
1844
            commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
 
1845
        else:
 
1846
            commit_builder_kls = vf_repository.VersionedFileCommitBuilder
 
1847
        result = commit_builder_kls(self, parents, config,
 
1848
            timestamp, timezone, committer, revprops, revision_id,
 
1849
            lossy)
 
1850
        self.start_write_group()
 
1851
        return result
1226
1852
 
1227
1853
    def add_fallback_repository(self, repository):
1228
1854
        """Add a repository to use for looking up data not held locally.
1235
1861
        # We need to accumulate additional repositories here, to pass them in
1236
1862
        # on various RPC's.
1237
1863
        #
 
1864
        # Make the check before we lock: this raises an exception.
 
1865
        self._check_fallback_repository(repository)
1238
1866
        if self.is_locked():
1239
1867
            # We will call fallback.unlock() when we transition to the unlocked
1240
1868
            # state, so always add a lock here. If a caller passes us a locked
1241
1869
            # repository, they are responsible for unlocking it later.
1242
1870
            repository.lock_read()
1243
 
        self._check_fallback_repository(repository)
1244
1871
        self._fallback_repositories.append(repository)
1245
1872
        # If self._real_repository was parameterised already (e.g. because a
1246
1873
        # _real_branch had its get_stacked_on_url method called), then the
1272
1899
            delta, new_revision_id, parents, basis_inv=basis_inv,
1273
1900
            propagate_caches=propagate_caches)
1274
1901
 
1275
 
    def add_revision(self, rev_id, rev, inv=None, config=None):
1276
 
        self._ensure_real()
1277
 
        return self._real_repository.add_revision(
1278
 
            rev_id, rev, inv=inv, config=config)
 
1902
    def add_revision(self, revision_id, rev, inv=None):
 
1903
        _mod_revision.check_not_reserved_id(revision_id)
 
1904
        key = (revision_id,)
 
1905
        # check inventory present
 
1906
        if not self.inventories.get_parent_map([key]):
 
1907
            if inv is None:
 
1908
                raise errors.WeaveRevisionNotPresent(revision_id,
 
1909
                                                     self.inventories)
 
1910
            else:
 
1911
                # yes, this is not suitable for adding with ghosts.
 
1912
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
 
1913
                                                        rev.parent_ids)
 
1914
        else:
 
1915
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
 
1916
        self._add_revision(rev)
 
1917
 
 
1918
    def _add_revision(self, rev):
 
1919
        if self._real_repository is not None:
 
1920
            return self._real_repository._add_revision(rev)
 
1921
        text = self._serializer.write_revision_to_string(rev)
 
1922
        key = (rev.revision_id,)
 
1923
        parents = tuple((parent,) for parent in rev.parent_ids)
 
1924
        self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
 
1925
            [('revisions', [FulltextContentFactory(key, parents, None, text)])],
 
1926
            self._format, self._write_group_tokens)
1279
1927
 
1280
1928
    @needs_read_lock
1281
1929
    def get_inventory(self, revision_id):
 
1930
        return list(self.iter_inventories([revision_id]))[0]
 
1931
 
 
1932
    def _iter_inventories_rpc(self, revision_ids, ordering):
 
1933
        if ordering is None:
 
1934
            ordering = 'unordered'
 
1935
        path = self.controldir._path_for_remote_call(self._client)
 
1936
        body = "\n".join(revision_ids)
 
1937
        response_tuple, response_handler = (
 
1938
            self._call_with_body_bytes_expecting_body(
 
1939
                "VersionedFileRepository.get_inventories",
 
1940
                (path, ordering), body))
 
1941
        if response_tuple[0] != "ok":
 
1942
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1943
        deserializer = inventory_delta.InventoryDeltaDeserializer()
 
1944
        byte_stream = response_handler.read_streamed_body()
 
1945
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
 
1946
        if decoded is None:
 
1947
            # no results whatsoever
 
1948
            return
 
1949
        src_format, stream = decoded
 
1950
        if src_format.network_name() != self._format.network_name():
 
1951
            raise AssertionError(
 
1952
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
1953
                src_format.network_name(), self._format.network_name()))
 
1954
        # ignore the src format, it's not really relevant
 
1955
        prev_inv = Inventory(root_id=None,
 
1956
            revision_id=_mod_revision.NULL_REVISION)
 
1957
        # there should be just one substream, with inventory deltas
 
1958
        substream_kind, substream = next(stream)
 
1959
        if substream_kind != "inventory-deltas":
 
1960
            raise AssertionError(
 
1961
                 "Unexpected stream %r received" % substream_kind)
 
1962
        for record in substream:
 
1963
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
 
1964
                deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
 
1965
            if parent_id != prev_inv.revision_id:
 
1966
                raise AssertionError("invalid base %r != %r" % (parent_id,
 
1967
                    prev_inv.revision_id))
 
1968
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
 
1969
            yield inv, inv.revision_id
 
1970
            prev_inv = inv
 
1971
 
 
1972
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
1282
1973
        self._ensure_real()
1283
 
        return self._real_repository.get_inventory(revision_id)
 
1974
        return self._real_repository._iter_inventories(revision_ids, ordering)
1284
1975
 
1285
1976
    def iter_inventories(self, revision_ids, ordering=None):
1286
 
        self._ensure_real()
1287
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1977
        """Get many inventories by revision_ids.
 
1978
 
 
1979
        This will buffer some or all of the texts used in constructing the
 
1980
        inventories in memory, but will only parse a single inventory at a
 
1981
        time.
 
1982
 
 
1983
        :param revision_ids: The expected revision ids of the inventories.
 
1984
        :param ordering: optional ordering, e.g. 'topological'.  If not
 
1985
            specified, the order of revision_ids will be preserved (by
 
1986
            buffering if necessary).
 
1987
        :return: An iterator of inventories.
 
1988
        """
 
1989
        if ((None in revision_ids)
 
1990
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
1991
            raise ValueError('cannot get null revision inventory')
 
1992
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
1993
            if inv is None:
 
1994
                raise errors.NoSuchRevision(self, revid)
 
1995
            yield inv
 
1996
 
 
1997
    def _iter_inventories(self, revision_ids, ordering=None):
 
1998
        if len(revision_ids) == 0:
 
1999
            return
 
2000
        missing = set(revision_ids)
 
2001
        if ordering is None:
 
2002
            order_as_requested = True
 
2003
            invs = {}
 
2004
            order = list(revision_ids)
 
2005
            order.reverse()
 
2006
            next_revid = order.pop()
 
2007
        else:
 
2008
            order_as_requested = False
 
2009
            if ordering != 'unordered' and self._fallback_repositories:
 
2010
                raise ValueError('unsupported ordering %r' % ordering)
 
2011
        iter_inv_fns = [self._iter_inventories_rpc] + [
 
2012
            fallback._iter_inventories for fallback in
 
2013
            self._fallback_repositories]
 
2014
        try:
 
2015
            for iter_inv in iter_inv_fns:
 
2016
                request = [revid for revid in revision_ids if revid in missing]
 
2017
                for inv, revid in iter_inv(request, ordering):
 
2018
                    if inv is None:
 
2019
                        continue
 
2020
                    missing.remove(inv.revision_id)
 
2021
                    if ordering != 'unordered':
 
2022
                        invs[revid] = inv
 
2023
                    else:
 
2024
                        yield inv, revid
 
2025
                if order_as_requested:
 
2026
                    # Yield as many results as we can while preserving order.
 
2027
                    while next_revid in invs:
 
2028
                        inv = invs.pop(next_revid)
 
2029
                        yield inv, inv.revision_id
 
2030
                        try:
 
2031
                            next_revid = order.pop()
 
2032
                        except IndexError:
 
2033
                            # We still want to fully consume the stream, just
 
2034
                            # in case it is not actually finished at this point
 
2035
                            next_revid = None
 
2036
                            break
 
2037
        except errors.UnknownSmartMethod:
 
2038
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
 
2039
                yield inv, revid
 
2040
            return
 
2041
        # Report missing
 
2042
        if order_as_requested:
 
2043
            if next_revid is not None:
 
2044
                yield None, next_revid
 
2045
            while order:
 
2046
                revid = order.pop()
 
2047
                yield invs.get(revid), revid
 
2048
        else:
 
2049
            while missing:
 
2050
                yield None, missing.pop()
1288
2051
 
1289
2052
    @needs_read_lock
1290
2053
    def get_revision(self, revision_id):
1291
 
        self._ensure_real()
1292
 
        return self._real_repository.get_revision(revision_id)
 
2054
        return self.get_revisions([revision_id])[0]
1293
2055
 
1294
2056
    def get_transaction(self):
1295
2057
        self._ensure_real()
1296
2058
        return self._real_repository.get_transaction()
1297
2059
 
1298
2060
    @needs_read_lock
1299
 
    def clone(self, a_bzrdir, revision_id=None):
1300
 
        self._ensure_real()
1301
 
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
2061
    def clone(self, a_controldir, revision_id=None):
 
2062
        dest_repo = self._create_sprouting_repo(
 
2063
            a_controldir, shared=self.is_shared())
 
2064
        self.copy_content_into(dest_repo, revision_id)
 
2065
        return dest_repo
1302
2066
 
1303
2067
    def make_working_trees(self):
1304
2068
        """See Repository.make_working_trees"""
1305
 
        self._ensure_real()
1306
 
        return self._real_repository.make_working_trees()
 
2069
        path = self.controldir._path_for_remote_call(self._client)
 
2070
        try:
 
2071
            response = self._call('Repository.make_working_trees', path)
 
2072
        except errors.UnknownSmartMethod:
 
2073
            self._ensure_real()
 
2074
            return self._real_repository.make_working_trees()
 
2075
        if response[0] not in ('yes', 'no'):
 
2076
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2077
        return response[0] == 'yes'
1307
2078
 
1308
2079
    def refresh_data(self):
1309
 
        """Re-read any data needed to to synchronise with disk.
 
2080
        """Re-read any data needed to synchronise with disk.
1310
2081
 
1311
2082
        This method is intended to be called after another repository instance
1312
2083
        (such as one used by a smart server) has inserted data into the
1313
 
        repository. It may not be called during a write group, but may be
1314
 
        called at any other time.
 
2084
        repository. On all repositories this will work outside of write groups.
 
2085
        Some repository formats (pack and newer for breezy native formats)
 
2086
        support refresh_data inside write groups. If called inside a write
 
2087
        group on a repository that does not support refreshing in a write group
 
2088
        IsInWriteGroupError will be raised.
1315
2089
        """
1316
 
        if self.is_in_write_group():
1317
 
            raise errors.InternalBzrError(
1318
 
                "May not refresh_data while in a write group.")
1319
2090
        if self._real_repository is not None:
1320
2091
            self._real_repository.refresh_data()
 
2092
        # Refresh the parents cache for this object
 
2093
        self._unstacked_provider.disable_cache()
 
2094
        self._unstacked_provider.enable_cache()
1321
2095
 
1322
2096
    def revision_ids_to_search_result(self, result_set):
1323
2097
        """Convert a set of revision ids to a graph SearchResult."""
1324
2098
        result_parents = set()
1325
 
        for parents in self.get_graph().get_parent_map(
1326
 
            result_set).itervalues():
 
2099
        for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
1327
2100
            result_parents.update(parents)
1328
2101
        included_keys = result_set.intersection(result_parents)
1329
2102
        start_keys = result_set.difference(included_keys)
1330
2103
        exclude_keys = result_parents.difference(result_set)
1331
 
        result = graph.SearchResult(start_keys, exclude_keys,
 
2104
        result = vf_search.SearchResult(start_keys, exclude_keys,
1332
2105
            len(result_set), result_set)
1333
2106
        return result
1334
2107
 
1335
2108
    @needs_read_lock
1336
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
2109
    def search_missing_revision_ids(self, other,
 
2110
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
2111
            limit=None):
1337
2112
        """Return the revision ids that other has that this does not.
1338
2113
 
1339
2114
        These are returned in topological order.
1340
2115
 
1341
2116
        revision_id: only return revision ids included by revision_id.
1342
2117
        """
1343
 
        return repository.InterRepository.get(
1344
 
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
2118
        inter_repo = _mod_repository.InterRepository.get(other, self)
 
2119
        return inter_repo.search_missing_revision_ids(
 
2120
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
2121
            if_present_ids=if_present_ids, limit=limit)
1345
2122
 
1346
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
2123
    def fetch(self, source, revision_id=None, find_ghosts=False,
1347
2124
            fetch_spec=None):
1348
2125
        # No base implementation to use as RemoteRepository is not a subclass
1349
2126
        # of Repository; so this is a copy of Repository.fetch().
1360
2137
            # check that last_revision is in 'from' and then return a
1361
2138
            # no-operation.
1362
2139
            if (revision_id is not None and
1363
 
                not revision.is_null(revision_id)):
 
2140
                not _mod_revision.is_null(revision_id)):
1364
2141
                self.get_revision(revision_id)
1365
2142
            return 0, []
1366
2143
        # if there is no specific appropriate InterRepository, this will get
1367
2144
        # the InterRepository base class, which raises an
1368
2145
        # IncompatibleRepositories when asked to fetch.
1369
 
        inter = repository.InterRepository.get(source, self)
1370
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
2146
        inter = _mod_repository.InterRepository.get(source, self)
 
2147
        if (fetch_spec is not None and
 
2148
            not getattr(inter, "supports_fetch_spec", False)):
 
2149
            raise errors.UnsupportedOperation(
 
2150
                "fetch_spec not supported for %r" % inter)
 
2151
        return inter.fetch(revision_id=revision_id,
1371
2152
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1372
2153
 
1373
2154
    def create_bundle(self, target, base, fileobj, format=None):
1374
2155
        self._ensure_real()
1375
2156
        self._real_repository.create_bundle(target, base, fileobj, format)
1376
2157
 
1377
 
    @needs_read_lock
1378
 
    def get_ancestry(self, revision_id, topo_sorted=True):
1379
 
        self._ensure_real()
1380
 
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
1381
 
 
1382
2158
    def fileids_altered_by_revision_ids(self, revision_ids):
1383
2159
        self._ensure_real()
1384
2160
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1388
2164
        return self._real_repository._get_versioned_file_checker(
1389
2165
            revisions, revision_versions_cache)
1390
2166
 
 
2167
    def _iter_files_bytes_rpc(self, desired_files, absent):
 
2168
        path = self.controldir._path_for_remote_call(self._client)
 
2169
        lines = []
 
2170
        identifiers = []
 
2171
        for (file_id, revid, identifier) in desired_files:
 
2172
            lines.append("%s\0%s" % (
 
2173
                osutils.safe_file_id(file_id),
 
2174
                osutils.safe_revision_id(revid)))
 
2175
            identifiers.append(identifier)
 
2176
        (response_tuple, response_handler) = (
 
2177
            self._call_with_body_bytes_expecting_body(
 
2178
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
 
2179
        if response_tuple != ('ok', ):
 
2180
            response_handler.cancel_read_body()
 
2181
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2182
        byte_stream = response_handler.read_streamed_body()
 
2183
        def decompress_stream(start, byte_stream, unused):
 
2184
            decompressor = zlib.decompressobj()
 
2185
            yield decompressor.decompress(start)
 
2186
            while decompressor.unused_data == "":
 
2187
                try:
 
2188
                    data = next(byte_stream)
 
2189
                except StopIteration:
 
2190
                    break
 
2191
                yield decompressor.decompress(data)
 
2192
            yield decompressor.flush()
 
2193
            unused.append(decompressor.unused_data)
 
2194
        unused = ""
 
2195
        while True:
 
2196
            while not "\n" in unused:
 
2197
                unused += next(byte_stream)
 
2198
            header, rest = unused.split("\n", 1)
 
2199
            args = header.split("\0")
 
2200
            if args[0] == "absent":
 
2201
                absent[identifiers[int(args[3])]] = (args[1], args[2])
 
2202
                unused = rest
 
2203
                continue
 
2204
            elif args[0] == "ok":
 
2205
                idx = int(args[1])
 
2206
            else:
 
2207
                raise errors.UnexpectedSmartServerResponse(args)
 
2208
            unused_chunks = []
 
2209
            yield (identifiers[idx],
 
2210
                decompress_stream(rest, byte_stream, unused_chunks))
 
2211
            unused = "".join(unused_chunks)
 
2212
 
1391
2213
    def iter_files_bytes(self, desired_files):
1392
2214
        """See Repository.iter_file_bytes.
1393
2215
        """
1394
 
        self._ensure_real()
1395
 
        return self._real_repository.iter_files_bytes(desired_files)
 
2216
        try:
 
2217
            absent = {}
 
2218
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
 
2219
                    desired_files, absent):
 
2220
                yield identifier, bytes_iterator
 
2221
            for fallback in self._fallback_repositories:
 
2222
                if not absent:
 
2223
                    break
 
2224
                desired_files = [(key[0], key[1], identifier)
 
2225
                    for identifier, key in viewitems(absent)]
 
2226
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
 
2227
                    del absent[identifier]
 
2228
                    yield identifier, bytes_iterator
 
2229
            if absent:
 
2230
                # There may be more missing items, but raise an exception
 
2231
                # for just one.
 
2232
                missing_identifier = next(iter(absent))
 
2233
                missing_key = absent[missing_identifier]
 
2234
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
 
2235
                    file_id=missing_key[0])
 
2236
        except errors.UnknownSmartMethod:
 
2237
            self._ensure_real()
 
2238
            for (identifier, bytes_iterator) in (
 
2239
                self._real_repository.iter_files_bytes(desired_files)):
 
2240
                yield identifier, bytes_iterator
 
2241
 
 
2242
    def get_cached_parent_map(self, revision_ids):
 
2243
        """See breezy.CachingParentsProvider.get_cached_parent_map"""
 
2244
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
1396
2245
 
1397
2246
    def get_parent_map(self, revision_ids):
1398
 
        """See bzrlib.Graph.get_parent_map()."""
 
2247
        """See breezy.Graph.get_parent_map()."""
1399
2248
        return self._make_parents_provider().get_parent_map(revision_ids)
1400
2249
 
1401
2250
    def _get_parent_map_rpc(self, keys):
1420
2269
            # There is one other "bug" which is that ghosts in
1421
2270
            # get_revision_graph() are not returned at all. But we won't worry
1422
2271
            # about that for now.
1423
 
            for node_id, parent_ids in rg.iteritems():
 
2272
            for node_id, parent_ids in viewitems(rg):
1424
2273
                if parent_ids == ():
1425
2274
                    rg[node_id] = (NULL_REVISION,)
1426
2275
            rg[NULL_REVISION] = ()
1457
2306
        if parents_map is None:
1458
2307
            # Repository is not locked, so there's no cache.
1459
2308
            parents_map = {}
1460
 
        # start_set is all the keys in the cache
1461
 
        start_set = set(parents_map)
1462
 
        # result set is all the references to keys in the cache
1463
 
        result_parents = set()
1464
 
        for parents in parents_map.itervalues():
1465
 
            result_parents.update(parents)
1466
 
        stop_keys = result_parents.difference(start_set)
1467
 
        # We don't need to send ghosts back to the server as a position to
1468
 
        # stop either.
1469
 
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
1470
 
        key_count = len(parents_map)
1471
 
        if (NULL_REVISION in result_parents
1472
 
            and NULL_REVISION in self._unstacked_provider.missing_keys):
1473
 
            # If we pruned NULL_REVISION from the stop_keys because it's also
1474
 
            # in our cache of "missing" keys we need to increment our key count
1475
 
            # by 1, because the reconsitituted SearchResult on the server will
1476
 
            # still consider NULL_REVISION to be an included key.
1477
 
            key_count += 1
1478
 
        included_keys = start_set.intersection(result_parents)
1479
 
        start_set.difference_update(included_keys)
 
2309
        if _DEFAULT_SEARCH_DEPTH <= 0:
 
2310
            (start_set, stop_keys,
 
2311
             key_count) = vf_search.search_result_from_parent_map(
 
2312
                parents_map, self._unstacked_provider.missing_keys)
 
2313
        else:
 
2314
            (start_set, stop_keys,
 
2315
             key_count) = vf_search.limited_search_result_from_parent_map(
 
2316
                parents_map, self._unstacked_provider.missing_keys,
 
2317
                keys, depth=_DEFAULT_SEARCH_DEPTH)
1480
2318
        recipe = ('manual', start_set, stop_keys, key_count)
1481
2319
        body = self._serialise_search_recipe(recipe)
1482
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2320
        path = self.controldir._path_for_remote_call(self._client)
1483
2321
        for key in keys:
1484
 
            if type(key) is not str:
 
2322
            if not isinstance(key, str):
1485
2323
                raise ValueError(
1486
2324
                    "key %r not a plain string" % (key,))
1487
2325
        verb = 'Repository.get_parent_map'
1531
2369
 
1532
2370
    @needs_read_lock
1533
2371
    def get_signature_text(self, revision_id):
1534
 
        self._ensure_real()
1535
 
        return self._real_repository.get_signature_text(revision_id)
 
2372
        path = self.controldir._path_for_remote_call(self._client)
 
2373
        try:
 
2374
            response_tuple, response_handler = self._call_expecting_body(
 
2375
                'Repository.get_revision_signature_text', path, revision_id)
 
2376
        except errors.UnknownSmartMethod:
 
2377
            self._ensure_real()
 
2378
            return self._real_repository.get_signature_text(revision_id)
 
2379
        except errors.NoSuchRevision as err:
 
2380
            for fallback in self._fallback_repositories:
 
2381
                try:
 
2382
                    return fallback.get_signature_text(revision_id)
 
2383
                except errors.NoSuchRevision:
 
2384
                    pass
 
2385
            raise err
 
2386
        else:
 
2387
            if response_tuple[0] != 'ok':
 
2388
                raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2389
            return response_handler.read_body_bytes()
1536
2390
 
1537
2391
    @needs_read_lock
1538
2392
    def _get_inventory_xml(self, revision_id):
 
2393
        # This call is used by older working tree formats,
 
2394
        # which stored a serialized basis inventory.
1539
2395
        self._ensure_real()
1540
2396
        return self._real_repository._get_inventory_xml(revision_id)
1541
2397
 
 
2398
    @needs_write_lock
1542
2399
    def reconcile(self, other=None, thorough=False):
1543
 
        self._ensure_real()
1544
 
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
2400
        from .reconcile import RepoReconciler
 
2401
        path = self.controldir._path_for_remote_call(self._client)
 
2402
        try:
 
2403
            response, handler = self._call_expecting_body(
 
2404
                'Repository.reconcile', path, self._lock_token)
 
2405
        except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
 
2406
            self._ensure_real()
 
2407
            return self._real_repository.reconcile(other=other, thorough=thorough)
 
2408
        if response != ('ok', ):
 
2409
            raise errors.UnexpectedSmartServerResponse(response)
 
2410
        body = handler.read_body_bytes()
 
2411
        result = RepoReconciler(self)
 
2412
        for line in body.split('\n'):
 
2413
            if not line:
 
2414
                continue
 
2415
            key, val_text = line.split(':')
 
2416
            if key == "garbage_inventories":
 
2417
                result.garbage_inventories = int(val_text)
 
2418
            elif key == "inconsistent_parents":
 
2419
                result.inconsistent_parents = int(val_text)
 
2420
            else:
 
2421
                mutter("unknown reconcile key %r" % key)
 
2422
        return result
1545
2423
 
1546
2424
    def all_revision_ids(self):
1547
 
        self._ensure_real()
1548
 
        return self._real_repository.all_revision_ids()
 
2425
        path = self.controldir._path_for_remote_call(self._client)
 
2426
        try:
 
2427
            response_tuple, response_handler = self._call_expecting_body(
 
2428
                "Repository.all_revision_ids", path)
 
2429
        except errors.UnknownSmartMethod:
 
2430
            self._ensure_real()
 
2431
            return self._real_repository.all_revision_ids()
 
2432
        if response_tuple != ("ok", ):
 
2433
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2434
        revids = set(response_handler.read_body_bytes().splitlines())
 
2435
        for fallback in self._fallback_repositories:
 
2436
            revids.update(set(fallback.all_revision_ids()))
 
2437
        return list(revids)
 
2438
 
 
2439
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2440
        """Return Tree for a revision on this branch with only some files.
 
2441
 
 
2442
        :param revision_ids: a sequence of revision-ids;
 
2443
          a revision-id may not be None or 'null:'
 
2444
        :param file_ids: if not None, the result is filtered
 
2445
          so that only those file-ids, their parents and their
 
2446
          children are included.
 
2447
        """
 
2448
        inventories = self.iter_inventories(revision_ids)
 
2449
        for inv in inventories:
 
2450
            # Should we introduce a FilteredRevisionTree class rather
 
2451
            # than pre-filter the inventory here?
 
2452
            filtered_inv = inv.filter(file_ids)
 
2453
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
1549
2454
 
1550
2455
    @needs_read_lock
1551
2456
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1552
 
        self._ensure_real()
1553
 
        return self._real_repository.get_deltas_for_revisions(revisions,
1554
 
            specific_fileids=specific_fileids)
 
2457
        medium = self._client._medium
 
2458
        if medium._is_remote_before((1, 2)):
 
2459
            self._ensure_real()
 
2460
            for delta in self._real_repository.get_deltas_for_revisions(
 
2461
                    revisions, specific_fileids):
 
2462
                yield delta
 
2463
            return
 
2464
        # Get the revision-ids of interest
 
2465
        required_trees = set()
 
2466
        for revision in revisions:
 
2467
            required_trees.add(revision.revision_id)
 
2468
            required_trees.update(revision.parent_ids[:1])
 
2469
 
 
2470
        # Get the matching filtered trees. Note that it's more
 
2471
        # efficient to pass filtered trees to changes_from() rather
 
2472
        # than doing the filtering afterwards. changes_from() could
 
2473
        # arguably do the filtering itself but it's path-based, not
 
2474
        # file-id based, so filtering before or afterwards is
 
2475
        # currently easier.
 
2476
        if specific_fileids is None:
 
2477
            trees = dict((t.get_revision_id(), t) for
 
2478
                t in self.revision_trees(required_trees))
 
2479
        else:
 
2480
            trees = dict((t.get_revision_id(), t) for
 
2481
                t in self._filtered_revision_trees(required_trees,
 
2482
                specific_fileids))
 
2483
 
 
2484
        # Calculate the deltas
 
2485
        for revision in revisions:
 
2486
            if not revision.parent_ids:
 
2487
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
2488
            else:
 
2489
                old_tree = trees[revision.parent_ids[0]]
 
2490
            yield trees[revision.revision_id].changes_from(old_tree)
1555
2491
 
1556
2492
    @needs_read_lock
1557
2493
    def get_revision_delta(self, revision_id, specific_fileids=None):
1558
 
        self._ensure_real()
1559
 
        return self._real_repository.get_revision_delta(revision_id,
1560
 
            specific_fileids=specific_fileids)
 
2494
        r = self.get_revision(revision_id)
 
2495
        return list(self.get_deltas_for_revisions([r],
 
2496
            specific_fileids=specific_fileids))[0]
1561
2497
 
1562
2498
    @needs_read_lock
1563
2499
    def revision_trees(self, revision_ids):
1564
 
        self._ensure_real()
1565
 
        return self._real_repository.revision_trees(revision_ids)
 
2500
        inventories = self.iter_inventories(revision_ids)
 
2501
        for inv in inventories:
 
2502
            yield InventoryRevisionTree(self, inv, inv.revision_id)
1566
2503
 
1567
2504
    @needs_read_lock
1568
2505
    def get_revision_reconcile(self, revision_id):
1576
2513
            callback_refs=callback_refs, check_repo=check_repo)
1577
2514
 
1578
2515
    def copy_content_into(self, destination, revision_id=None):
1579
 
        self._ensure_real()
1580
 
        return self._real_repository.copy_content_into(
1581
 
            destination, revision_id=revision_id)
 
2516
        """Make a complete copy of the content in self into destination.
 
2517
 
 
2518
        This is a destructive operation! Do not use it on existing
 
2519
        repositories.
 
2520
        """
 
2521
        interrepo = _mod_repository.InterRepository.get(self, destination)
 
2522
        return interrepo.copy_content(revision_id)
1582
2523
 
1583
2524
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1584
2525
        # get a tarball of the remote repository, and copy from that into the
1585
2526
        # destination
1586
 
        from bzrlib import osutils
1587
2527
        import tarfile
1588
2528
        # TODO: Maybe a progress bar while streaming the tarball?
1589
 
        note("Copying repository content as tarball...")
 
2529
        note(gettext("Copying repository content as tarball..."))
1590
2530
        tar_file = self._get_tarball('bz2')
1591
2531
        if tar_file is None:
1592
2532
            return None
1597
2537
            tmpdir = osutils.mkdtemp()
1598
2538
            try:
1599
2539
                _extract_tar(tar, tmpdir)
1600
 
                tmp_bzrdir = BzrDir.open(tmpdir)
 
2540
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1601
2541
                tmp_repo = tmp_bzrdir.open_repository()
1602
2542
                tmp_repo.copy_content_into(destination, revision_id)
1603
2543
            finally:
1621
2561
    @needs_write_lock
1622
2562
    def pack(self, hint=None, clean_obsolete_packs=False):
1623
2563
        """Compress the data within the repository.
1624
 
 
1625
 
        This is not currently implemented within the smart server.
1626
2564
        """
1627
 
        self._ensure_real()
1628
 
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
2565
        if hint is None:
 
2566
            body = ""
 
2567
        else:
 
2568
            body = "".join([l+"\n" for l in hint])
 
2569
        path = self.controldir._path_for_remote_call(self._client)
 
2570
        try:
 
2571
            response, handler = self._call_with_body_bytes_expecting_body(
 
2572
                'Repository.pack', (path, self._lock_token,
 
2573
                    str(clean_obsolete_packs)), body)
 
2574
        except errors.UnknownSmartMethod:
 
2575
            self._ensure_real()
 
2576
            return self._real_repository.pack(hint=hint,
 
2577
                clean_obsolete_packs=clean_obsolete_packs)
 
2578
        handler.cancel_read_body()
 
2579
        if response != ('ok', ):
 
2580
            raise errors.UnexpectedSmartServerResponse(response)
1629
2581
 
1630
2582
    @property
1631
2583
    def revisions(self):
1632
2584
        """Decorate the real repository for now.
1633
2585
 
1634
 
        In the short term this should become a real object to intercept graph
1635
 
        lookups.
1636
 
 
1637
2586
        In the long term a full blown network facility is needed.
1638
2587
        """
1639
2588
        self._ensure_real()
1644
2593
            new_value_str = "True"
1645
2594
        else:
1646
2595
            new_value_str = "False"
1647
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2596
        path = self.controldir._path_for_remote_call(self._client)
1648
2597
        try:
1649
2598
            response = self._call(
1650
2599
                'Repository.set_make_working_trees', path, new_value_str)
1667
2616
 
1668
2617
    @needs_write_lock
1669
2618
    def sign_revision(self, revision_id, gpg_strategy):
1670
 
        self._ensure_real()
1671
 
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
2619
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2620
        plaintext = testament.as_short_text()
 
2621
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1672
2622
 
1673
2623
    @property
1674
2624
    def texts(self):
1680
2630
        self._ensure_real()
1681
2631
        return self._real_repository.texts
1682
2632
 
 
2633
    def _iter_revisions_rpc(self, revision_ids):
 
2634
        body = "\n".join(revision_ids)
 
2635
        path = self.controldir._path_for_remote_call(self._client)
 
2636
        response_tuple, response_handler = (
 
2637
            self._call_with_body_bytes_expecting_body(
 
2638
            "Repository.iter_revisions", (path, ), body))
 
2639
        if response_tuple[0] != "ok":
 
2640
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2641
        serializer_format = response_tuple[1]
 
2642
        serializer = serializer_format_registry.get(serializer_format)
 
2643
        byte_stream = response_handler.read_streamed_body()
 
2644
        decompressor = zlib.decompressobj()
 
2645
        chunks = []
 
2646
        for bytes in byte_stream:
 
2647
            chunks.append(decompressor.decompress(bytes))
 
2648
            if decompressor.unused_data != "":
 
2649
                chunks.append(decompressor.flush())
 
2650
                yield serializer.read_revision_from_string("".join(chunks))
 
2651
                unused = decompressor.unused_data
 
2652
                decompressor = zlib.decompressobj()
 
2653
                chunks = [decompressor.decompress(unused)]
 
2654
        chunks.append(decompressor.flush())
 
2655
        text = "".join(chunks)
 
2656
        if text != "":
 
2657
            yield serializer.read_revision_from_string("".join(chunks))
 
2658
 
1683
2659
    @needs_read_lock
1684
2660
    def get_revisions(self, revision_ids):
1685
 
        self._ensure_real()
1686
 
        return self._real_repository.get_revisions(revision_ids)
 
2661
        if revision_ids is None:
 
2662
            revision_ids = self.all_revision_ids()
 
2663
        else:
 
2664
            for rev_id in revision_ids:
 
2665
                if not rev_id or not isinstance(rev_id, basestring):
 
2666
                    raise errors.InvalidRevisionId(
 
2667
                        revision_id=rev_id, branch=self)
 
2668
        try:
 
2669
            missing = set(revision_ids)
 
2670
            revs = {}
 
2671
            for rev in self._iter_revisions_rpc(revision_ids):
 
2672
                missing.remove(rev.revision_id)
 
2673
                revs[rev.revision_id] = rev
 
2674
        except errors.UnknownSmartMethod:
 
2675
            self._ensure_real()
 
2676
            return self._real_repository.get_revisions(revision_ids)
 
2677
        for fallback in self._fallback_repositories:
 
2678
            if not missing:
 
2679
                break
 
2680
            for revid in list(missing):
 
2681
                # XXX JRV 2011-11-20: It would be nice if there was a
 
2682
                # public method on Repository that could be used to query
 
2683
                # for revision objects *without* failing completely if one
 
2684
                # was missing. There is VersionedFileRepository._iter_revisions,
 
2685
                # but unfortunately that's private and not provided by
 
2686
                # all repository implementations.
 
2687
                try:
 
2688
                    revs[revid] = fallback.get_revision(revid)
 
2689
                except errors.NoSuchRevision:
 
2690
                    pass
 
2691
                else:
 
2692
                    missing.remove(revid)
 
2693
        if missing:
 
2694
            raise errors.NoSuchRevision(self, list(missing)[0])
 
2695
        return [revs[revid] for revid in revision_ids]
1687
2696
 
1688
2697
    def supports_rich_root(self):
1689
2698
        return self._format.rich_root_data
1690
2699
 
1691
 
    def iter_reverse_revision_history(self, revision_id):
1692
 
        self._ensure_real()
1693
 
        return self._real_repository.iter_reverse_revision_history(revision_id)
1694
 
 
1695
2700
    @property
1696
2701
    def _serializer(self):
1697
2702
        return self._format._serializer
1698
2703
 
 
2704
    @needs_write_lock
1699
2705
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1700
 
        self._ensure_real()
1701
 
        return self._real_repository.store_revision_signature(
1702
 
            gpg_strategy, plaintext, revision_id)
 
2706
        signature = gpg_strategy.sign(plaintext)
 
2707
        self.add_signature_text(revision_id, signature)
1703
2708
 
1704
2709
    def add_signature_text(self, revision_id, signature):
1705
 
        self._ensure_real()
1706
 
        return self._real_repository.add_signature_text(revision_id, signature)
 
2710
        if self._real_repository:
 
2711
            # If there is a real repository the write group will
 
2712
            # be in the real repository as well, so use that:
 
2713
            self._ensure_real()
 
2714
            return self._real_repository.add_signature_text(
 
2715
                revision_id, signature)
 
2716
        path = self.controldir._path_for_remote_call(self._client)
 
2717
        response, handler = self._call_with_body_bytes_expecting_body(
 
2718
            'Repository.add_signature_text', (path, self._lock_token,
 
2719
                revision_id) + tuple(self._write_group_tokens), signature)
 
2720
        handler.cancel_read_body()
 
2721
        self.refresh_data()
 
2722
        if response[0] != 'ok':
 
2723
            raise errors.UnexpectedSmartServerResponse(response)
 
2724
        self._write_group_tokens = response[1:]
1707
2725
 
1708
2726
    def has_signature_for_revision_id(self, revision_id):
1709
 
        self._ensure_real()
1710
 
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
2727
        path = self.controldir._path_for_remote_call(self._client)
 
2728
        try:
 
2729
            response = self._call('Repository.has_signature_for_revision_id',
 
2730
                path, revision_id)
 
2731
        except errors.UnknownSmartMethod:
 
2732
            self._ensure_real()
 
2733
            return self._real_repository.has_signature_for_revision_id(
 
2734
                revision_id)
 
2735
        if response[0] not in ('yes', 'no'):
 
2736
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2737
        if response[0] == 'yes':
 
2738
            return True
 
2739
        for fallback in self._fallback_repositories:
 
2740
            if fallback.has_signature_for_revision_id(revision_id):
 
2741
                return True
 
2742
        return False
 
2743
 
 
2744
    @needs_read_lock
 
2745
    def verify_revision_signature(self, revision_id, gpg_strategy):
 
2746
        if not self.has_signature_for_revision_id(revision_id):
 
2747
            return gpg.SIGNATURE_NOT_SIGNED, None
 
2748
        signature = self.get_signature_text(revision_id)
 
2749
 
 
2750
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2751
        plaintext = testament.as_short_text()
 
2752
 
 
2753
        return gpg_strategy.verify(signature, plaintext)
1711
2754
 
1712
2755
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1713
2756
        self._ensure_real()
1714
2757
        return self._real_repository.item_keys_introduced_by(revision_ids,
1715
2758
            _files_pb=_files_pb)
1716
2759
 
1717
 
    def revision_graph_can_have_wrong_parents(self):
1718
 
        # The answer depends on the remote repo format.
1719
 
        self._ensure_real()
1720
 
        return self._real_repository.revision_graph_can_have_wrong_parents()
1721
 
 
1722
2760
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1723
2761
        self._ensure_real()
1724
2762
        return self._real_repository._find_inconsistent_revision_parents(
1732
2770
        providers = [self._unstacked_provider]
1733
2771
        if other is not None:
1734
2772
            providers.insert(0, other)
1735
 
        providers.extend(r._make_parents_provider() for r in
1736
 
                         self._fallback_repositories)
1737
 
        return graph.StackedParentsProvider(providers)
 
2773
        return graph.StackedParentsProvider(_LazyListJoin(
 
2774
            providers, self._fallback_repositories))
1738
2775
 
1739
2776
    def _serialise_search_recipe(self, recipe):
1740
2777
        """Serialise a graph search recipe.
1748
2785
        return '\n'.join((start_keys, stop_keys, count))
1749
2786
 
1750
2787
    def _serialise_search_result(self, search_result):
1751
 
        if isinstance(search_result, graph.PendingAncestryResult):
1752
 
            parts = ['ancestry-of']
1753
 
            parts.extend(search_result.heads)
1754
 
        else:
1755
 
            recipe = search_result.get_recipe()
1756
 
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
2788
        parts = search_result.get_network_struct()
1757
2789
        return '\n'.join(parts)
1758
2790
 
1759
2791
    def autopack(self):
1760
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2792
        path = self.controldir._path_for_remote_call(self._client)
1761
2793
        try:
1762
2794
            response = self._call('PackRepository.autopack', path)
1763
2795
        except errors.UnknownSmartMethod:
1769
2801
            raise errors.UnexpectedSmartServerResponse(response)
1770
2802
 
1771
2803
 
1772
 
class RemoteStreamSink(repository.StreamSink):
 
2804
class RemoteStreamSink(vf_repository.StreamSink):
1773
2805
 
1774
2806
    def _insert_real(self, stream, src_format, resume_tokens):
1775
2807
        self.target_repo._ensure_real()
1791
2823
            lock_args = ()
1792
2824
        client = target._client
1793
2825
        medium = client._medium
1794
 
        path = target.bzrdir._path_for_remote_call(client)
 
2826
        path = target.controldir._path_for_remote_call(client)
1795
2827
        # Probe for the verb to use with an empty stream before sending the
1796
2828
        # real stream to it.  We do this both to avoid the risk of sending a
1797
2829
        # large request that is then rejected, and because we don't want to
1876
2908
        self._last_substream and self._last_stream so that the stream can be
1877
2909
        resumed by _resume_stream_with_vfs.
1878
2910
        """
1879
 
                    
 
2911
 
1880
2912
        stream_iter = iter(stream)
1881
2913
        for substream_kind, substream in stream_iter:
1882
2914
            if substream_kind == 'inventory-deltas':
1885
2917
                return
1886
2918
            else:
1887
2919
                yield substream_kind, substream
1888
 
            
1889
 
 
1890
 
class RemoteStreamSource(repository.StreamSource):
 
2920
 
 
2921
 
 
2922
class RemoteStreamSource(vf_repository.StreamSource):
1891
2923
    """Stream data from a remote server."""
1892
2924
 
1893
2925
    def get_stream(self, search):
1914
2946
 
1915
2947
    def _real_stream(self, repo, search):
1916
2948
        """Get a stream for search from repo.
1917
 
        
1918
 
        This never called RemoteStreamSource.get_stream, and is a heler
1919
 
        for RemoteStreamSource._get_stream to allow getting a stream 
 
2949
 
 
2950
        This never called RemoteStreamSource.get_stream, and is a helper
 
2951
        for RemoteStreamSource._get_stream to allow getting a stream
1920
2952
        reliably whether fallback back because of old servers or trying
1921
2953
        to stream from a non-RemoteRepository (which the stacked support
1922
2954
        code will do).
1947
2979
            return self._real_stream(repo, search)
1948
2980
        client = repo._client
1949
2981
        medium = client._medium
1950
 
        path = repo.bzrdir._path_for_remote_call(client)
 
2982
        path = repo.controldir._path_for_remote_call(client)
1951
2983
        search_bytes = repo._serialise_search_result(search)
1952
2984
        args = (path, self.to_format.network_name())
1953
2985
        candidate_verbs = [
1954
2986
            ('Repository.get_stream_1.19', (1, 19)),
1955
2987
            ('Repository.get_stream', (1, 13))]
 
2988
 
1956
2989
        found_verb = False
1957
2990
        for verb, version in candidate_verbs:
1958
2991
            if medium._is_remote_before(version):
1962
2995
                    verb, args, search_bytes)
1963
2996
            except errors.UnknownSmartMethod:
1964
2997
                medium._remember_remote_is_before(version)
 
2998
            except errors.UnknownErrorFromSmartServer as e:
 
2999
                if isinstance(search, vf_search.EverythingResult):
 
3000
                    error_verb = e.error_from_smart_server.error_verb
 
3001
                    if error_verb == 'BadSearch':
 
3002
                        # Pre-2.4 servers don't support this sort of search.
 
3003
                        # XXX: perhaps falling back to VFS on BadSearch is a
 
3004
                        # good idea in general?  It might provide a little bit
 
3005
                        # of protection against client-side bugs.
 
3006
                        medium._remember_remote_is_before((2, 4))
 
3007
                        break
 
3008
                raise
1965
3009
            else:
1966
3010
                response_tuple, response_handler = response
1967
3011
                found_verb = True
1971
3015
        if response_tuple[0] != 'ok':
1972
3016
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
3017
        byte_stream = response_handler.read_streamed_body()
1974
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
3018
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
3019
            self._record_counter)
1975
3020
        if src_format.network_name() != repo._format.network_name():
1976
3021
            raise AssertionError(
1977
3022
                "Mismatched RemoteRepository and stream src %r, %r" % (
2021
3066
    """
2022
3067
 
2023
3068
    def __init__(self, bzrdir, _client):
2024
 
        self.bzrdir = bzrdir
 
3069
        self.controldir = bzrdir
2025
3070
        self._client = _client
2026
3071
        self._need_find_modes = True
2027
3072
        LockableFiles.__init__(
2049
3094
 
2050
3095
    def _ensure_real(self):
2051
3096
        if self._custom_format is None:
2052
 
            self._custom_format = branch.network_format_registry.get(
2053
 
                self._network_name)
 
3097
            try:
 
3098
                self._custom_format = branch.network_format_registry.get(
 
3099
                    self._network_name)
 
3100
            except KeyError:
 
3101
                raise errors.UnknownFormatError(kind='branch',
 
3102
                    format=self._network_name)
2054
3103
 
2055
3104
    def get_format_description(self):
2056
3105
        self._ensure_real()
2059
3108
    def network_name(self):
2060
3109
        return self._network_name
2061
3110
 
2062
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2063
 
        return a_bzrdir.open_branch(name=name, 
 
3111
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
 
3112
        return a_controldir.open_branch(name=name, 
2064
3113
            ignore_fallbacks=ignore_fallbacks)
2065
3114
 
2066
 
    def _vfs_initialize(self, a_bzrdir, name):
 
3115
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
 
3116
                        repository=None):
2067
3117
        # Initialisation when using a local bzrdir object, or a non-vfs init
2068
3118
        # method is not available on the server.
2069
3119
        # self._custom_format is always set - the start of initialize ensures
2070
3120
        # that.
2071
 
        if isinstance(a_bzrdir, RemoteBzrDir):
2072
 
            a_bzrdir._ensure_real()
2073
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2074
 
                name)
 
3121
        if isinstance(a_controldir, RemoteBzrDir):
 
3122
            a_controldir._ensure_real()
 
3123
            result = self._custom_format.initialize(a_controldir._real_bzrdir,
 
3124
                name=name, append_revisions_only=append_revisions_only,
 
3125
                repository=repository)
2075
3126
        else:
2076
3127
            # We assume the bzrdir is parameterised; it may not be.
2077
 
            result = self._custom_format.initialize(a_bzrdir, name)
2078
 
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
3128
            result = self._custom_format.initialize(a_controldir, name=name,
 
3129
                append_revisions_only=append_revisions_only,
 
3130
                repository=repository)
 
3131
        if (isinstance(a_controldir, RemoteBzrDir) and
2079
3132
            not isinstance(result, RemoteBranch)):
2080
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
3133
            result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
2081
3134
                                  name=name)
2082
3135
        return result
2083
3136
 
2084
 
    def initialize(self, a_bzrdir, name=None):
 
3137
    def initialize(self, a_controldir, name=None, repository=None,
 
3138
                   append_revisions_only=None):
 
3139
        if name is None:
 
3140
            name = a_controldir._get_selected_branch()
2085
3141
        # 1) get the network name to use.
2086
3142
        if self._custom_format:
2087
3143
            network_name = self._custom_format.network_name()
2088
3144
        else:
2089
 
            # Select the current bzrlib default and ask for that.
2090
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
3145
            # Select the current breezy default and ask for that.
 
3146
            reference_bzrdir_format = controldir.format_registry.get('default')()
2091
3147
            reference_format = reference_bzrdir_format.get_branch_format()
2092
3148
            self._custom_format = reference_format
2093
3149
            network_name = reference_format.network_name()
2094
3150
        # Being asked to create on a non RemoteBzrDir:
2095
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
2096
 
            return self._vfs_initialize(a_bzrdir, name=name)
2097
 
        medium = a_bzrdir._client._medium
 
3151
        if not isinstance(a_controldir, RemoteBzrDir):
 
3152
            return self._vfs_initialize(a_controldir, name=name,
 
3153
                append_revisions_only=append_revisions_only,
 
3154
                repository=repository)
 
3155
        medium = a_controldir._client._medium
2098
3156
        if medium._is_remote_before((1, 13)):
2099
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3157
            return self._vfs_initialize(a_controldir, name=name,
 
3158
                append_revisions_only=append_revisions_only,
 
3159
                repository=repository)
2100
3160
        # Creating on a remote bzr dir.
2101
3161
        # 2) try direct creation via RPC
2102
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2103
 
        if name is not None:
 
3162
        path = a_controldir._path_for_remote_call(a_controldir._client)
 
3163
        if name != "":
2104
3164
            # XXX JRV20100304: Support creating colocated branches
2105
3165
            raise errors.NoColocatedBranchSupport(self)
2106
3166
        verb = 'BzrDir.create_branch'
2107
3167
        try:
2108
 
            response = a_bzrdir._call(verb, path, network_name)
 
3168
            response = a_controldir._call(verb, path, network_name)
2109
3169
        except errors.UnknownSmartMethod:
2110
3170
            # Fallback - use vfs methods
2111
3171
            medium._remember_remote_is_before((1, 13))
2112
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3172
            return self._vfs_initialize(a_controldir, name=name,
 
3173
                    append_revisions_only=append_revisions_only,
 
3174
                    repository=repository)
2113
3175
        if response[0] != 'ok':
2114
3176
            raise errors.UnexpectedSmartServerResponse(response)
2115
3177
        # Turn the response into a RemoteRepository object.
2116
3178
        format = RemoteBranchFormat(network_name=response[1])
2117
3179
        repo_format = response_tuple_to_repo_format(response[3:])
2118
 
        if response[2] == '':
2119
 
            repo_bzrdir = a_bzrdir
 
3180
        repo_path = response[2]
 
3181
        if repository is not None:
 
3182
            remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
 
3183
            url_diff = urlutils.relative_url(repository.user_url,
 
3184
                    remote_repo_url)
 
3185
            if url_diff != '.':
 
3186
                raise AssertionError(
 
3187
                    'repository.user_url %r does not match URL from server '
 
3188
                    'response (%r + %r)'
 
3189
                    % (repository.user_url, a_controldir.user_url, repo_path))
 
3190
            remote_repo = repository
2120
3191
        else:
2121
 
            repo_bzrdir = RemoteBzrDir(
2122
 
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2123
 
                a_bzrdir._client)
2124
 
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2125
 
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
3192
            if repo_path == '':
 
3193
                repo_bzrdir = a_controldir
 
3194
            else:
 
3195
                repo_bzrdir = RemoteBzrDir(
 
3196
                    a_controldir.root_transport.clone(repo_path), a_controldir._format,
 
3197
                    a_controldir._client)
 
3198
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
3199
        remote_branch = RemoteBranch(a_controldir, remote_repo,
2126
3200
            format=format, setup_stacking=False, name=name)
 
3201
        if append_revisions_only:
 
3202
            remote_branch.set_append_revisions_only(append_revisions_only)
2127
3203
        # XXX: We know this is a new branch, so it must have revno 0, revid
2128
3204
        # NULL_REVISION. Creating the branch locked would make this be unable
2129
3205
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2148
3224
        self._ensure_real()
2149
3225
        return self._custom_format.supports_set_append_revisions_only()
2150
3226
 
 
3227
    def _use_default_local_heads_to_fetch(self):
 
3228
        # If the branch format is a metadir format *and* its heads_to_fetch
 
3229
        # implementation is not overridden vs the base class, we can use the
 
3230
        # base class logic rather than use the heads_to_fetch RPC.  This is
 
3231
        # usually cheaper in terms of net round trips, as the last-revision and
 
3232
        # tags info fetched is cached and would be fetched anyway.
 
3233
        self._ensure_real()
 
3234
        if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
 
3235
            branch_class = self._custom_format._branch_class()
 
3236
            heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
 
3237
            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
 
3238
                return True
 
3239
        return False
 
3240
 
 
3241
 
 
3242
class RemoteBranchStore(_mod_config.IniFileStore):
 
3243
    """Branch store which attempts to use HPSS calls to retrieve branch store.
 
3244
 
 
3245
    Note that this is specific to bzr-based formats.
 
3246
    """
 
3247
 
 
3248
    def __init__(self, branch):
 
3249
        super(RemoteBranchStore, self).__init__()
 
3250
        self.branch = branch
 
3251
        self.id = "branch"
 
3252
        self._real_store = None
 
3253
 
 
3254
    def external_url(self):
 
3255
        return urlutils.join(self.branch.user_url, 'branch.conf')
 
3256
 
 
3257
    def _load_content(self):
 
3258
        path = self.branch._remote_path()
 
3259
        try:
 
3260
            response, handler = self.branch._call_expecting_body(
 
3261
                'Branch.get_config_file', path)
 
3262
        except errors.UnknownSmartMethod:
 
3263
            self._ensure_real()
 
3264
            return self._real_store._load_content()
 
3265
        if len(response) and response[0] != 'ok':
 
3266
            raise errors.UnexpectedSmartServerResponse(response)
 
3267
        return handler.read_body_bytes()
 
3268
 
 
3269
    def _save_content(self, content):
 
3270
        path = self.branch._remote_path()
 
3271
        try:
 
3272
            response, handler = self.branch._call_with_body_bytes_expecting_body(
 
3273
                'Branch.put_config_file', (path,
 
3274
                    self.branch._lock_token, self.branch._repo_lock_token),
 
3275
                content)
 
3276
        except errors.UnknownSmartMethod:
 
3277
            self._ensure_real()
 
3278
            return self._real_store._save_content(content)
 
3279
        handler.cancel_read_body()
 
3280
        if response != ('ok', ):
 
3281
            raise errors.UnexpectedSmartServerResponse(response)
 
3282
 
 
3283
    def _ensure_real(self):
 
3284
        self.branch._ensure_real()
 
3285
        if self._real_store is None:
 
3286
            self._real_store = _mod_config.BranchStore(self.branch)
 
3287
 
2151
3288
 
2152
3289
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2153
3290
    """Branch stored on a server accessed by HPSS RPC.
2156
3293
    """
2157
3294
 
2158
3295
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
 
        _client=None, format=None, setup_stacking=True, name=None):
 
3296
        _client=None, format=None, setup_stacking=True, name=None,
 
3297
        possible_transports=None):
2160
3298
        """Create a RemoteBranch instance.
2161
3299
 
2162
3300
        :param real_branch: An optional local implementation of the branch
2173
3311
        # We intentionally don't call the parent class's __init__, because it
2174
3312
        # will try to assign to self.tags, which is a property in this subclass.
2175
3313
        # And the parent's __init__ doesn't do much anyway.
2176
 
        self.bzrdir = remote_bzrdir
 
3314
        self.controldir = remote_bzrdir
 
3315
        self.name = name
2177
3316
        if _client is not None:
2178
3317
            self._client = _client
2179
3318
        else:
2191
3330
            self._real_branch.repository = self.repository
2192
3331
        else:
2193
3332
            self._real_branch = None
2194
 
        # Fill out expected attributes of branch for bzrlib API users.
 
3333
        # Fill out expected attributes of branch for breezy API users.
2195
3334
        self._clear_cached_state()
2196
3335
        # TODO: deprecate self.base in favor of user_url
2197
 
        self.base = self.bzrdir.user_url
 
3336
        self.base = self.controldir.user_url
2198
3337
        self._name = name
2199
3338
        self._control_files = None
2200
3339
        self._lock_mode = None
2202
3341
        self._repo_lock_token = None
2203
3342
        self._lock_count = 0
2204
3343
        self._leave_lock = False
 
3344
        self.conf_store = None
2205
3345
        # Setup a format: note that we cannot call _ensure_real until all the
2206
3346
        # attributes above are set: This code cannot be moved higher up in this
2207
3347
        # function.
2227
3367
            hook(self)
2228
3368
        self._is_stacked = False
2229
3369
        if setup_stacking:
2230
 
            self._setup_stacking()
 
3370
            self._setup_stacking(possible_transports)
2231
3371
 
2232
 
    def _setup_stacking(self):
 
3372
    def _setup_stacking(self, possible_transports):
2233
3373
        # configure stacking into the remote repository, by reading it from
2234
3374
        # the vfs branch.
2235
3375
        try:
2236
3376
            fallback_url = self.get_stacked_on_url()
2237
3377
        except (errors.NotStacked, errors.UnstackableBranchFormat,
2238
 
            errors.UnstackableRepositoryFormat), e:
 
3378
            errors.UnstackableRepositoryFormat) as e:
2239
3379
            return
2240
3380
        self._is_stacked = True
2241
 
        self._activate_fallback_location(fallback_url)
 
3381
        if possible_transports is None:
 
3382
            possible_transports = []
 
3383
        else:
 
3384
            possible_transports = list(possible_transports)
 
3385
        possible_transports.append(self.controldir.root_transport)
 
3386
        self._activate_fallback_location(fallback_url,
 
3387
            possible_transports=possible_transports)
2242
3388
 
2243
3389
    def _get_config(self):
2244
3390
        return RemoteBranchConfig(self)
2245
3391
 
 
3392
    def _get_config_store(self):
 
3393
        if self.conf_store is None:
 
3394
            self.conf_store =  RemoteBranchStore(self)
 
3395
        return self.conf_store
 
3396
 
 
3397
    def store_uncommitted(self, creator):
 
3398
        self._ensure_real()
 
3399
        return self._real_branch.store_uncommitted(creator)
 
3400
 
 
3401
    def get_unshelver(self, tree):
 
3402
        self._ensure_real()
 
3403
        return self._real_branch.get_unshelver(tree)
 
3404
 
2246
3405
    def _get_real_transport(self):
2247
3406
        # if we try vfs access, return the real branch's vfs transport
2248
3407
        self._ensure_real()
2264
3423
            if not vfs.vfs_enabled():
2265
3424
                raise AssertionError('smart server vfs must be enabled '
2266
3425
                    'to use vfs implementation')
2267
 
            self.bzrdir._ensure_real()
2268
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
3426
            self.controldir._ensure_real()
 
3427
            self._real_branch = self.controldir._real_bzrdir.open_branch(
2269
3428
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
3429
            # The remote branch and the real branch shares the same store. If
 
3430
            # we don't, there will always be cases where one of the stores
 
3431
            # doesn't see an update made on the other.
 
3432
            self._real_branch.conf_store = self.conf_store
2270
3433
            if self.repository._real_repository is None:
2271
3434
                # Give the remote repository the matching real repo.
2272
3435
                real_repo = self._real_branch.repository
2308
3471
        # because it triggers an _ensure_real that we otherwise might not need.
2309
3472
        if self._control_files is None:
2310
3473
            self._control_files = RemoteBranchLockableFiles(
2311
 
                self.bzrdir, self._client)
 
3474
                self.controldir, self._client)
2312
3475
        return self._control_files
2313
3476
 
2314
 
    def _get_checkout_format(self):
2315
 
        self._ensure_real()
2316
 
        return self._real_branch._get_checkout_format()
2317
 
 
2318
3477
    def get_physical_lock_status(self):
2319
3478
        """See Branch.get_physical_lock_status()."""
2320
 
        # should be an API call to the server, as branches must be lockable.
2321
 
        self._ensure_real()
2322
 
        return self._real_branch.get_physical_lock_status()
 
3479
        try:
 
3480
            response = self._client.call('Branch.get_physical_lock_status',
 
3481
                self._remote_path())
 
3482
        except errors.UnknownSmartMethod:
 
3483
            self._ensure_real()
 
3484
            return self._real_branch.get_physical_lock_status()
 
3485
        if response[0] not in ('yes', 'no'):
 
3486
            raise errors.UnexpectedSmartServerResponse(response)
 
3487
        return (response[0] == 'yes')
2323
3488
 
2324
3489
    def get_stacked_on_url(self):
2325
3490
        """Get the URL this branch is stacked against.
2335
3500
            # self._translate_error, so we can't use self._call either.
2336
3501
            response = self._client.call('Branch.get_stacked_on_url',
2337
3502
                self._remote_path())
2338
 
        except errors.ErrorFromSmartServer, err:
 
3503
        except errors.ErrorFromSmartServer as err:
2339
3504
            # there may not be a repository yet, so we can't call through
2340
3505
            # its _translate_error
2341
3506
            _translate_error(err, branch=self)
2342
 
        except errors.UnknownSmartMethod, err:
 
3507
        except errors.UnknownSmartMethod as err:
2343
3508
            self._ensure_real()
2344
3509
            return self._real_branch.get_stacked_on_url()
2345
3510
        if response[0] != 'ok':
2348
3513
 
2349
3514
    def set_stacked_on_url(self, url):
2350
3515
        branch.Branch.set_stacked_on_url(self, url)
 
3516
        # We need the stacked_on_url to be visible both locally (to not query
 
3517
        # it repeatedly) and remotely (so smart verbs can get it server side)
 
3518
        # Without the following line,
 
3519
        # breezy.tests.per_branch.test_create_clone.TestCreateClone
 
3520
        # .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
 
3521
        # fails for remote branches -- vila 2012-01-04
 
3522
        self.conf_store.save_changes()
2351
3523
        if not url:
2352
3524
            self._is_stacked = False
2353
3525
        else:
2354
3526
            self._is_stacked = True
2355
 
        
 
3527
 
2356
3528
    def _vfs_get_tags_bytes(self):
2357
3529
        self._ensure_real()
2358
3530
        return self._real_branch._get_tags_bytes()
2359
3531
 
 
3532
    @needs_read_lock
2360
3533
    def _get_tags_bytes(self):
 
3534
        if self._tags_bytes is None:
 
3535
            self._tags_bytes = self._get_tags_bytes_via_hpss()
 
3536
        return self._tags_bytes
 
3537
 
 
3538
    def _get_tags_bytes_via_hpss(self):
2361
3539
        medium = self._client._medium
2362
3540
        if medium._is_remote_before((1, 13)):
2363
3541
            return self._vfs_get_tags_bytes()
2373
3551
        return self._real_branch._set_tags_bytes(bytes)
2374
3552
 
2375
3553
    def _set_tags_bytes(self, bytes):
 
3554
        if self.is_locked():
 
3555
            self._tags_bytes = bytes
2376
3556
        medium = self._client._medium
2377
3557
        if medium._is_remote_before((1, 18)):
2378
3558
            self._vfs_set_tags_bytes(bytes)
2387
3567
            self._vfs_set_tags_bytes(bytes)
2388
3568
 
2389
3569
    def lock_read(self):
 
3570
        """Lock the branch for read operations.
 
3571
 
 
3572
        :return: A breezy.lock.LogicalLockResult.
 
3573
        """
2390
3574
        self.repository.lock_read()
2391
3575
        if not self._lock_mode:
2392
3576
            self._note_lock('r')
2396
3580
                self._real_branch.lock_read()
2397
3581
        else:
2398
3582
            self._lock_count += 1
 
3583
        return lock.LogicalLockResult(self.unlock)
2399
3584
 
2400
3585
    def _remote_lock_write(self, token):
2401
3586
        if token is None:
2402
3587
            branch_token = repo_token = ''
2403
3588
        else:
2404
3589
            branch_token = token
2405
 
            repo_token = self.repository.lock_write()
 
3590
            repo_token = self.repository.lock_write().repository_token
2406
3591
            self.repository.unlock()
2407
3592
        err_context = {'token': token}
2408
 
        response = self._call(
2409
 
            'Branch.lock_write', self._remote_path(), branch_token,
2410
 
            repo_token or '', **err_context)
 
3593
        try:
 
3594
            response = self._call(
 
3595
                'Branch.lock_write', self._remote_path(), branch_token,
 
3596
                repo_token or '', **err_context)
 
3597
        except errors.LockContention as e:
 
3598
            # The LockContention from the server doesn't have any
 
3599
            # information about the lock_url. We re-raise LockContention
 
3600
            # with valid lock_url.
 
3601
            raise errors.LockContention('(remote lock)',
 
3602
                self.repository.base.split('.bzr/')[0])
2411
3603
        if response[0] != 'ok':
2412
3604
            raise errors.UnexpectedSmartServerResponse(response)
2413
3605
        ok, branch_token, repo_token = response
2434
3626
            self._lock_mode = 'w'
2435
3627
            self._lock_count = 1
2436
3628
        elif self._lock_mode == 'r':
2437
 
            raise errors.ReadOnlyTransaction
 
3629
            raise errors.ReadOnlyError(self)
2438
3630
        else:
2439
3631
            if token is not None:
2440
3632
                # A token was given to lock_write, and we're relocking, so
2445
3637
            self._lock_count += 1
2446
3638
            # Re-lock the repository too.
2447
3639
            self.repository.lock_write(self._repo_lock_token)
2448
 
        return self._lock_token or None
 
3640
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2449
3641
 
2450
3642
    def _unlock(self, branch_token, repo_token):
2451
3643
        err_context = {'token': str((branch_token, repo_token))}
2461
3653
        try:
2462
3654
            self._lock_count -= 1
2463
3655
            if not self._lock_count:
 
3656
                if self.conf_store is not None:
 
3657
                    self.conf_store.save_changes()
2464
3658
                self._clear_cached_state()
2465
3659
                mode = self._lock_mode
2466
3660
                self._lock_mode = None
2489
3683
            self.repository.unlock()
2490
3684
 
2491
3685
    def break_lock(self):
2492
 
        self._ensure_real()
2493
 
        return self._real_branch.break_lock()
 
3686
        try:
 
3687
            response = self._call(
 
3688
                'Branch.break_lock', self._remote_path())
 
3689
        except errors.UnknownSmartMethod:
 
3690
            self._ensure_real()
 
3691
            return self._real_branch.break_lock()
 
3692
        if response != ('ok',):
 
3693
            raise errors.UnexpectedSmartServerResponse(response)
2494
3694
 
2495
3695
    def leave_lock_in_place(self):
2496
3696
        if not self._lock_token:
2520
3720
            missing_parent = parent_map[missing_parent]
2521
3721
        raise errors.RevisionNotPresent(missing_parent, self.repository)
2522
3722
 
2523
 
    def _last_revision_info(self):
 
3723
    def _read_last_revision_info(self):
2524
3724
        response = self._call('Branch.last_revision_info', self._remote_path())
2525
3725
        if response[0] != 'ok':
2526
3726
            raise SmartProtocolError('unexpected response code %s' % (response,))
2543
3743
        return result
2544
3744
 
2545
3745
    def _remote_path(self):
2546
 
        return self.bzrdir._path_for_remote_call(self._client)
 
3746
        return self.controldir._path_for_remote_call(self._client)
2547
3747
 
2548
3748
    def _set_last_revision_descendant(self, revision_id, other_branch,
2549
3749
            allow_diverged=False, allow_overwrite_descendant=False):
2589
3789
            raise errors.UnexpectedSmartServerResponse(response)
2590
3790
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2591
3791
 
2592
 
    @needs_write_lock
2593
 
    def set_revision_history(self, rev_history):
2594
 
        # Send just the tip revision of the history; the server will generate
2595
 
        # the full history from that.  If the revision doesn't exist in this
2596
 
        # branch, NoSuchRevision will be raised.
2597
 
        if rev_history == []:
2598
 
            rev_id = 'null:'
2599
 
        else:
2600
 
            rev_id = rev_history[-1]
2601
 
        self._set_last_revision(rev_id)
2602
 
        for hook in branch.Branch.hooks['set_rh']:
2603
 
            hook(self, rev_history)
2604
 
        self._cache_revision_history(rev_history)
2605
 
 
2606
3792
    def _get_parent_location(self):
2607
3793
        medium = self._client._medium
2608
3794
        if medium._is_remote_before((1, 13)):
2629
3815
            return self._vfs_set_parent_location(url)
2630
3816
        try:
2631
3817
            call_url = url or ''
2632
 
            if type(call_url) is not str:
 
3818
            if not isinstance(call_url, str):
2633
3819
                raise AssertionError('url must be a str or None (%s)' % url)
2634
3820
            response = self._call('Branch.set_parent_location',
2635
3821
                self._remote_path(), self._lock_token, self._repo_lock_token,
2654
3840
            _override_hook_target=self, **kwargs)
2655
3841
 
2656
3842
    @needs_read_lock
2657
 
    def push(self, target, overwrite=False, stop_revision=None):
 
3843
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
2658
3844
        self._ensure_real()
2659
3845
        return self._real_branch.push(
2660
 
            target, overwrite=overwrite, stop_revision=stop_revision,
 
3846
            target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
2661
3847
            _override_hook_source_branch=self)
2662
3848
 
 
3849
    def peek_lock_mode(self):
 
3850
        return self._lock_mode
 
3851
 
2663
3852
    def is_locked(self):
2664
3853
        return self._lock_count >= 1
2665
3854
 
2666
3855
    @needs_read_lock
 
3856
    def revision_id_to_dotted_revno(self, revision_id):
 
3857
        """Given a revision id, return its dotted revno.
 
3858
 
 
3859
        :return: a tuple like (1,) or (400,1,3).
 
3860
        """
 
3861
        try:
 
3862
            response = self._call('Branch.revision_id_to_revno',
 
3863
                self._remote_path(), revision_id)
 
3864
        except errors.UnknownSmartMethod:
 
3865
            self._ensure_real()
 
3866
            return self._real_branch.revision_id_to_dotted_revno(revision_id)
 
3867
        if response[0] == 'ok':
 
3868
            return tuple([int(x) for x in response[1:]])
 
3869
        else:
 
3870
            raise errors.UnexpectedSmartServerResponse(response)
 
3871
 
 
3872
    @needs_read_lock
2667
3873
    def revision_id_to_revno(self, revision_id):
2668
 
        self._ensure_real()
2669
 
        return self._real_branch.revision_id_to_revno(revision_id)
 
3874
        """Given a revision id on the branch mainline, return its revno.
 
3875
 
 
3876
        :return: an integer
 
3877
        """
 
3878
        try:
 
3879
            response = self._call('Branch.revision_id_to_revno',
 
3880
                self._remote_path(), revision_id)
 
3881
        except errors.UnknownSmartMethod:
 
3882
            self._ensure_real()
 
3883
            return self._real_branch.revision_id_to_revno(revision_id)
 
3884
        if response[0] == 'ok':
 
3885
            if len(response) == 2:
 
3886
                return int(response[1])
 
3887
            raise NoSuchRevision(self, revision_id)
 
3888
        else:
 
3889
            raise errors.UnexpectedSmartServerResponse(response)
2670
3890
 
2671
3891
    @needs_write_lock
2672
3892
    def set_last_revision_info(self, revno, revision_id):
2673
3893
        # XXX: These should be returned by the set_last_revision_info verb
2674
3894
        old_revno, old_revid = self.last_revision_info()
2675
3895
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
 
        revision_id = ensure_null(revision_id)
 
3896
        if not revision_id or not isinstance(revision_id, basestring):
 
3897
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
2677
3898
        try:
2678
3899
            response = self._call('Branch.set_last_revision_info',
2679
3900
                self._remote_path(), self._lock_token, self._repo_lock_token,
2708
3929
            except errors.UnknownSmartMethod:
2709
3930
                medium._remember_remote_is_before((1, 6))
2710
3931
        self._clear_cached_state_of_remote_branch_only()
2711
 
        self.set_revision_history(self._lefthand_history(revision_id,
2712
 
            last_rev=last_rev,other_branch=other_branch))
 
3932
        graph = self.repository.get_graph()
 
3933
        (last_revno, last_revid) = self.last_revision_info()
 
3934
        known_revision_ids = [
 
3935
            (last_revid, last_revno),
 
3936
            (_mod_revision.NULL_REVISION, 0),
 
3937
            ]
 
3938
        if last_rev is not None:
 
3939
            if not graph.is_ancestor(last_rev, revision_id):
 
3940
                # our previous tip is not merged into stop_revision
 
3941
                raise errors.DivergedBranches(self, other_branch)
 
3942
        revno = graph.find_distance_to_null(revision_id, known_revision_ids)
 
3943
        self.set_last_revision_info(revno, revision_id)
2713
3944
 
2714
3945
    def set_push_location(self, location):
 
3946
        self._set_config_location('push_location', location)
 
3947
 
 
3948
    def heads_to_fetch(self):
 
3949
        if self._format._use_default_local_heads_to_fetch():
 
3950
            # We recognise this format, and its heads-to-fetch implementation
 
3951
            # is the default one (tip + tags).  In this case it's cheaper to
 
3952
            # just use the default implementation rather than a special RPC as
 
3953
            # the tip and tags data is cached.
 
3954
            return branch.Branch.heads_to_fetch(self)
 
3955
        medium = self._client._medium
 
3956
        if medium._is_remote_before((2, 4)):
 
3957
            return self._vfs_heads_to_fetch()
 
3958
        try:
 
3959
            return self._rpc_heads_to_fetch()
 
3960
        except errors.UnknownSmartMethod:
 
3961
            medium._remember_remote_is_before((2, 4))
 
3962
            return self._vfs_heads_to_fetch()
 
3963
 
 
3964
    def _rpc_heads_to_fetch(self):
 
3965
        response = self._call('Branch.heads_to_fetch', self._remote_path())
 
3966
        if len(response) != 2:
 
3967
            raise errors.UnexpectedSmartServerResponse(response)
 
3968
        must_fetch, if_present_fetch = response
 
3969
        return set(must_fetch), set(if_present_fetch)
 
3970
 
 
3971
    def _vfs_heads_to_fetch(self):
2715
3972
        self._ensure_real()
2716
 
        return self._real_branch.set_push_location(location)
 
3973
        return self._real_branch.heads_to_fetch()
2717
3974
 
2718
3975
 
2719
3976
class RemoteConfig(object):
2721
3978
 
2722
3979
    It is a low-level object that considers config data to be name/value pairs
2723
3980
    that may be associated with a section. Assigning meaning to the these
2724
 
    values is done at higher levels like bzrlib.config.TreeConfig.
 
3981
    values is done at higher levels like breezy.config.TreeConfig.
2725
3982
    """
2726
3983
 
2727
3984
    def get_option(self, name, section=None, default=None):
2734
3991
        """
2735
3992
        try:
2736
3993
            configobj = self._get_configobj()
 
3994
            section_obj = None
2737
3995
            if section is None:
2738
3996
                section_obj = configobj
2739
3997
            else:
2740
3998
                try:
2741
3999
                    section_obj = configobj[section]
2742
4000
                except KeyError:
2743
 
                    return default
2744
 
            return section_obj.get(name, default)
 
4001
                    pass
 
4002
            if section_obj is None:
 
4003
                value = default
 
4004
            else:
 
4005
                value = section_obj.get(name, default)
2745
4006
        except errors.UnknownSmartMethod:
2746
 
            return self._vfs_get_option(name, section, default)
 
4007
            value = self._vfs_get_option(name, section, default)
 
4008
        for hook in _mod_config.OldConfigHooks['get']:
 
4009
            hook(self, name, value)
 
4010
        return value
2747
4011
 
2748
4012
    def _response_to_configobj(self, response):
2749
4013
        if len(response[0]) and response[0][0] != 'ok':
2750
4014
            raise errors.UnexpectedSmartServerResponse(response)
2751
4015
        lines = response[1].read_body_bytes().splitlines()
2752
 
        return config.ConfigObj(lines, encoding='utf-8')
 
4016
        conf = _mod_config.ConfigObj(lines, encoding='utf-8')
 
4017
        for hook in _mod_config.OldConfigHooks['load']:
 
4018
            hook(self)
 
4019
        return conf
2753
4020
 
2754
4021
 
2755
4022
class RemoteBranchConfig(RemoteConfig):
2774
4041
        medium = self._branch._client._medium
2775
4042
        if medium._is_remote_before((1, 14)):
2776
4043
            return self._vfs_set_option(value, name, section)
 
4044
        if isinstance(value, dict):
 
4045
            if medium._is_remote_before((2, 2)):
 
4046
                return self._vfs_set_option(value, name, section)
 
4047
            return self._set_config_option_dict(value, name, section)
 
4048
        else:
 
4049
            return self._set_config_option(value, name, section)
 
4050
 
 
4051
    def _set_config_option(self, value, name, section):
2777
4052
        try:
2778
4053
            path = self._branch._remote_path()
2779
4054
            response = self._branch._client.call('Branch.set_config_option',
2780
4055
                path, self._branch._lock_token, self._branch._repo_lock_token,
2781
4056
                value.encode('utf8'), name, section or '')
2782
4057
        except errors.UnknownSmartMethod:
 
4058
            medium = self._branch._client._medium
2783
4059
            medium._remember_remote_is_before((1, 14))
2784
4060
            return self._vfs_set_option(value, name, section)
2785
4061
        if response != ():
2786
4062
            raise errors.UnexpectedSmartServerResponse(response)
2787
4063
 
 
4064
    def _serialize_option_dict(self, option_dict):
 
4065
        utf8_dict = {}
 
4066
        for key, value in option_dict.items():
 
4067
            if isinstance(key, unicode):
 
4068
                key = key.encode('utf8')
 
4069
            if isinstance(value, unicode):
 
4070
                value = value.encode('utf8')
 
4071
            utf8_dict[key] = value
 
4072
        return bencode.bencode(utf8_dict)
 
4073
 
 
4074
    def _set_config_option_dict(self, value, name, section):
 
4075
        try:
 
4076
            path = self._branch._remote_path()
 
4077
            serialised_dict = self._serialize_option_dict(value)
 
4078
            response = self._branch._client.call(
 
4079
                'Branch.set_config_option_dict',
 
4080
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
4081
                serialised_dict, name, section or '')
 
4082
        except errors.UnknownSmartMethod:
 
4083
            medium = self._branch._client._medium
 
4084
            medium._remember_remote_is_before((2, 2))
 
4085
            return self._vfs_set_option(value, name, section)
 
4086
        if response != ():
 
4087
            raise errors.UnexpectedSmartServerResponse(response)
 
4088
 
2788
4089
    def _real_object(self):
2789
4090
        self._branch._ensure_real()
2790
4091
        return self._branch._real_branch
2829
4130
        return self._bzrdir._real_bzrdir
2830
4131
 
2831
4132
 
2832
 
 
2833
4133
def _extract_tar(tar, to_dir):
2834
4134
    """Extract all the contents of a tarfile object.
2835
4135
 
2839
4139
        tar.extract(tarinfo, to_dir)
2840
4140
 
2841
4141
 
 
4142
error_translators = registry.Registry()
 
4143
no_context_error_translators = registry.Registry()
 
4144
 
 
4145
 
2842
4146
def _translate_error(err, **context):
2843
4147
    """Translate an ErrorFromSmartServer into a more useful error.
2844
4148
 
2856
4160
    def find(name):
2857
4161
        try:
2858
4162
            return context[name]
2859
 
        except KeyError, key_err:
 
4163
        except KeyError as key_err:
2860
4164
            mutter('Missing key %r in context %r', key_err.args[0], context)
2861
4165
            raise err
2862
4166
    def get_path():
2865
4169
        """
2866
4170
        try:
2867
4171
            return context['path']
2868
 
        except KeyError, key_err:
 
4172
        except KeyError as key_err:
2869
4173
            try:
2870
4174
                return err.error_args[0]
2871
 
            except IndexError, idx_err:
 
4175
            except IndexError as idx_err:
2872
4176
                mutter(
2873
4177
                    'Missing key %r in context %r', key_err.args[0], context)
2874
4178
                raise err
2875
4179
 
2876
 
    if err.error_verb == 'IncompatibleRepositories':
2877
 
        raise errors.IncompatibleRepositories(err.error_args[0],
2878
 
            err.error_args[1], err.error_args[2])
2879
 
    elif err.error_verb == 'NoSuchRevision':
2880
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
2881
 
    elif err.error_verb == 'nosuchrevision':
2882
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
2883
 
    elif err.error_verb == 'nobranch':
2884
 
        if len(err.error_args) >= 1:
2885
 
            extra = err.error_args[0]
2886
 
        else:
2887
 
            extra = None
2888
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2889
 
            detail=extra)
2890
 
    elif err.error_verb == 'norepository':
2891
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
2892
 
    elif err.error_verb == 'LockContention':
2893
 
        raise errors.LockContention('(remote lock)')
2894
 
    elif err.error_verb == 'UnlockableTransport':
2895
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
2896
 
    elif err.error_verb == 'LockFailed':
2897
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
2898
 
    elif err.error_verb == 'TokenMismatch':
2899
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
2900
 
    elif err.error_verb == 'Diverged':
2901
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
 
    elif err.error_verb == 'TipChangeRejected':
2903
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2904
 
    elif err.error_verb == 'UnstackableBranchFormat':
2905
 
        raise errors.UnstackableBranchFormat(*err.error_args)
2906
 
    elif err.error_verb == 'UnstackableRepositoryFormat':
2907
 
        raise errors.UnstackableRepositoryFormat(*err.error_args)
2908
 
    elif err.error_verb == 'NotStacked':
2909
 
        raise errors.NotStacked(branch=find('branch'))
2910
 
    elif err.error_verb == 'PermissionDenied':
2911
 
        path = get_path()
2912
 
        if len(err.error_args) >= 2:
2913
 
            extra = err.error_args[1]
2914
 
        else:
2915
 
            extra = None
2916
 
        raise errors.PermissionDenied(path, extra=extra)
2917
 
    elif err.error_verb == 'ReadError':
2918
 
        path = get_path()
2919
 
        raise errors.ReadError(path)
2920
 
    elif err.error_verb == 'NoSuchFile':
2921
 
        path = get_path()
2922
 
        raise errors.NoSuchFile(path)
2923
 
    elif err.error_verb == 'FileExists':
2924
 
        raise errors.FileExists(err.error_args[0])
2925
 
    elif err.error_verb == 'DirectoryNotEmpty':
2926
 
        raise errors.DirectoryNotEmpty(err.error_args[0])
2927
 
    elif err.error_verb == 'ShortReadvError':
2928
 
        args = err.error_args
2929
 
        raise errors.ShortReadvError(
2930
 
            args[0], int(args[1]), int(args[2]), int(args[3]))
2931
 
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
4180
    try:
 
4181
        translator = error_translators.get(err.error_verb)
 
4182
    except KeyError:
 
4183
        pass
 
4184
    else:
 
4185
        raise translator(err, find, get_path)
 
4186
    try:
 
4187
        translator = no_context_error_translators.get(err.error_verb)
 
4188
    except KeyError:
 
4189
        raise errors.UnknownErrorFromSmartServer(err)
 
4190
    else:
 
4191
        raise translator(err)
 
4192
 
 
4193
 
 
4194
error_translators.register('NoSuchRevision',
 
4195
    lambda err, find, get_path: NoSuchRevision(
 
4196
        find('branch'), err.error_args[0]))
 
4197
error_translators.register('nosuchrevision',
 
4198
    lambda err, find, get_path: NoSuchRevision(
 
4199
        find('repository'), err.error_args[0]))
 
4200
 
 
4201
def _translate_nobranch_error(err, find, get_path):
 
4202
    if len(err.error_args) >= 1:
 
4203
        extra = err.error_args[0]
 
4204
    else:
 
4205
        extra = None
 
4206
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
4207
        detail=extra)
 
4208
 
 
4209
error_translators.register('nobranch', _translate_nobranch_error)
 
4210
error_translators.register('norepository',
 
4211
    lambda err, find, get_path: errors.NoRepositoryPresent(
 
4212
        find('bzrdir')))
 
4213
error_translators.register('UnlockableTransport',
 
4214
    lambda err, find, get_path: errors.UnlockableTransport(
 
4215
        find('bzrdir').root_transport))
 
4216
error_translators.register('TokenMismatch',
 
4217
    lambda err, find, get_path: errors.TokenMismatch(
 
4218
        find('token'), '(remote token)'))
 
4219
error_translators.register('Diverged',
 
4220
    lambda err, find, get_path: errors.DivergedBranches(
 
4221
        find('branch'), find('other_branch')))
 
4222
error_translators.register('NotStacked',
 
4223
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
 
4224
 
 
4225
def _translate_PermissionDenied(err, find, get_path):
 
4226
    path = get_path()
 
4227
    if len(err.error_args) >= 2:
 
4228
        extra = err.error_args[1]
 
4229
    else:
 
4230
        extra = None
 
4231
    return errors.PermissionDenied(path, extra=extra)
 
4232
 
 
4233
error_translators.register('PermissionDenied', _translate_PermissionDenied)
 
4234
error_translators.register('ReadError',
 
4235
    lambda err, find, get_path: errors.ReadError(get_path()))
 
4236
error_translators.register('NoSuchFile',
 
4237
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
 
4238
error_translators.register('TokenLockingNotSupported',
 
4239
    lambda err, find, get_path: errors.TokenLockingNotSupported(
 
4240
        find('repository')))
 
4241
error_translators.register('UnsuspendableWriteGroup',
 
4242
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
 
4243
        repository=find('repository')))
 
4244
error_translators.register('UnresumableWriteGroup',
 
4245
    lambda err, find, get_path: errors.UnresumableWriteGroup(
 
4246
        repository=find('repository'), write_groups=err.error_args[0],
 
4247
        reason=err.error_args[1]))
 
4248
no_context_error_translators.register('IncompatibleRepositories',
 
4249
    lambda err: errors.IncompatibleRepositories(
 
4250
        err.error_args[0], err.error_args[1], err.error_args[2]))
 
4251
no_context_error_translators.register('LockContention',
 
4252
    lambda err: errors.LockContention('(remote lock)'))
 
4253
no_context_error_translators.register('LockFailed',
 
4254
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
 
4255
no_context_error_translators.register('TipChangeRejected',
 
4256
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
 
4257
no_context_error_translators.register('UnstackableBranchFormat',
 
4258
    lambda err: errors.UnstackableBranchFormat(*err.error_args))
 
4259
no_context_error_translators.register('UnstackableRepositoryFormat',
 
4260
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
 
4261
no_context_error_translators.register('FileExists',
 
4262
    lambda err: errors.FileExists(err.error_args[0]))
 
4263
no_context_error_translators.register('DirectoryNotEmpty',
 
4264
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
 
4265
 
 
4266
def _translate_short_readv_error(err):
 
4267
    args = err.error_args
 
4268
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
 
4269
        int(args[3]))
 
4270
 
 
4271
no_context_error_translators.register('ShortReadvError',
 
4272
    _translate_short_readv_error)
 
4273
 
 
4274
def _translate_unicode_error(err):
2932
4275
        encoding = str(err.error_args[0]) # encoding must always be a string
2933
4276
        val = err.error_args[1]
2934
4277
        start = int(err.error_args[2])
2942
4285
            raise UnicodeDecodeError(encoding, val, start, end, reason)
2943
4286
        elif err.error_verb == 'UnicodeEncodeError':
2944
4287
            raise UnicodeEncodeError(encoding, val, start, end, reason)
2945
 
    elif err.error_verb == 'ReadOnlyError':
2946
 
        raise errors.TransportNotPossible('readonly transport')
2947
 
    raise errors.UnknownErrorFromSmartServer(err)
 
4288
 
 
4289
no_context_error_translators.register('UnicodeEncodeError',
 
4290
    _translate_unicode_error)
 
4291
no_context_error_translators.register('UnicodeDecodeError',
 
4292
    _translate_unicode_error)
 
4293
no_context_error_translators.register('ReadOnlyError',
 
4294
    lambda err: errors.TransportNotPossible('readonly transport'))
 
4295
no_context_error_translators.register('MemoryError',
 
4296
    lambda err: errors.BzrError("remote server out of memory\n"
 
4297
        "Retry non-remotely, or contact the server admin for details."))
 
4298
no_context_error_translators.register('RevisionNotPresent',
 
4299
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
 
4300
 
 
4301
no_context_error_translators.register('BzrCheckError',
 
4302
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
 
4303