/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/bzr/remote.py

  • Committer: Martin
  • Date: 2017-06-27 00:57:14 UTC
  • mto: This revision was merged to the branch mainline in revision 6722.
  • Revision ID: gzlist@googlemail.com-20170627005714-64ns3u0x5g866cdp
Adapt StaticTuple tests to change in comparison semantics on Python 3

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