/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: Jelmer Vernooij
  • Date: 2017-12-21 16:44:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6842.
  • Revision ID: jelmer@jelmer.uk-20171221164419-wn90kwu2uismpznf
Revert custom gmtime implementation without tests.

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 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,
 
312
                    final_stack, 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
    def save(self):
 
382
        with self.lock_write():
 
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
478
939
        to obtain data like the network name.
479
940
    """
480
941
 
481
 
    _matchingbzrdir = RemoteBzrDirFormat()
 
942
    _matchingcontroldir = RemoteBzrDirFormat()
 
943
    supports_full_versioned_files = True
 
944
    supports_leaving_lock = True
 
945
    supports_overriding_transport = False
482
946
 
483
947
    def __init__(self):
484
 
        repository.RepositoryFormat.__init__(self)
 
948
        _mod_repository.RepositoryFormat.__init__(self)
485
949
        self._custom_format = None
486
950
        self._network_name = None
487
951
        self._creating_bzrdir = None
 
952
        self._revision_graph_can_have_wrong_parents = None
488
953
        self._supports_chks = None
489
954
        self._supports_external_lookups = None
490
955
        self._supports_tree_reference = None
 
956
        self._supports_funky_characters = None
 
957
        self._supports_nesting_repositories = None
491
958
        self._rich_root_data = None
492
959
 
493
960
    def __repr__(self):
522
989
        return self._supports_external_lookups
523
990
 
524
991
    @property
 
992
    def supports_funky_characters(self):
 
993
        if self._supports_funky_characters is None:
 
994
            self._ensure_real()
 
995
            self._supports_funky_characters = \
 
996
                self._custom_format.supports_funky_characters
 
997
        return self._supports_funky_characters
 
998
 
 
999
    @property
 
1000
    def supports_nesting_repositories(self):
 
1001
        if self._supports_nesting_repositories is None:
 
1002
            self._ensure_real()
 
1003
            self._supports_nesting_repositories = \
 
1004
                self._custom_format.supports_nesting_repositories
 
1005
        return self._supports_nesting_repositories
 
1006
 
 
1007
    @property
525
1008
    def supports_tree_reference(self):
526
1009
        if self._supports_tree_reference is None:
527
1010
            self._ensure_real()
529
1012
                self._custom_format.supports_tree_reference
530
1013
        return self._supports_tree_reference
531
1014
 
532
 
    def _vfs_initialize(self, a_bzrdir, shared):
 
1015
    @property
 
1016
    def revision_graph_can_have_wrong_parents(self):
 
1017
        if self._revision_graph_can_have_wrong_parents is None:
 
1018
            self._ensure_real()
 
1019
            self._revision_graph_can_have_wrong_parents = \
 
1020
                self._custom_format.revision_graph_can_have_wrong_parents
 
1021
        return self._revision_graph_can_have_wrong_parents
 
1022
 
 
1023
    def _vfs_initialize(self, a_controldir, shared):
533
1024
        """Helper for common code in initialize."""
534
1025
        if self._custom_format:
535
1026
            # Custom format requested
536
 
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
1027
            result = self._custom_format.initialize(a_controldir, shared=shared)
537
1028
        elif self._creating_bzrdir is not None:
538
1029
            # Use the format that the repository we were created to back
539
1030
            # has.
540
1031
            prior_repo = self._creating_bzrdir.open_repository()
541
1032
            prior_repo._ensure_real()
542
1033
            result = prior_repo._real_repository._format.initialize(
543
 
                a_bzrdir, shared=shared)
 
1034
                a_controldir, shared=shared)
544
1035
        else:
545
1036
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
1037
            # support remote initialization.
547
1038
            # We delegate to a real object at this point (as RemoteBzrDir
548
1039
            # 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)
 
1040
            # recursion if we just called a_controldir.create_repository.
 
1041
            a_controldir._ensure_real()
 
1042
            result = a_controldir._real_bzrdir.create_repository(shared=shared)
552
1043
        if not isinstance(result, RemoteRepository):
553
 
            return self.open(a_bzrdir)
 
1044
            return self.open(a_controldir)
554
1045
        else:
555
1046
            return result
556
1047
 
557
 
    def initialize(self, a_bzrdir, shared=False):
 
1048
    def initialize(self, a_controldir, shared=False):
558
1049
        # 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
 
1050
        if not isinstance(a_controldir, RemoteBzrDir):
 
1051
            return self._vfs_initialize(a_controldir, shared)
 
1052
        medium = a_controldir._client._medium
562
1053
        if medium._is_remote_before((1, 13)):
563
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1054
            return self._vfs_initialize(a_controldir, shared)
564
1055
        # Creating on a remote bzr dir.
565
1056
        # 1) get the network name to use.
566
1057
        if self._custom_format:
568
1059
        elif self._network_name:
569
1060
            network_name = self._network_name
570
1061
        else:
571
 
            # Select the current bzrlib default and ask for that.
572
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
1062
            # Select the current breezy default and ask for that.
 
1063
            reference_bzrdir_format = controldir.format_registry.get('default')()
573
1064
            reference_format = reference_bzrdir_format.repository_format
574
1065
            network_name = reference_format.network_name()
575
1066
        # 2) try direct creation via RPC
576
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
1067
        path = a_controldir._path_for_remote_call(a_controldir._client)
577
1068
        verb = 'BzrDir.create_repository'
578
1069
        if shared:
579
1070
            shared_str = 'True'
580
1071
        else:
581
1072
            shared_str = 'False'
582
1073
        try:
583
 
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
1074
            response = a_controldir._call(verb, path, network_name, shared_str)
584
1075
        except errors.UnknownSmartMethod:
585
1076
            # Fallback - use vfs methods
586
1077
            medium._remember_remote_is_before((1, 13))
587
 
            return self._vfs_initialize(a_bzrdir, shared)
 
1078
            return self._vfs_initialize(a_controldir, shared)
588
1079
        else:
589
1080
            # Turn the response into a RemoteRepository object.
590
1081
            format = response_tuple_to_repo_format(response[1:])
591
1082
            # Used to support creating a real format instance when needed.
592
 
            format._creating_bzrdir = a_bzrdir
593
 
            remote_repo = RemoteRepository(a_bzrdir, format)
 
1083
            format._creating_bzrdir = a_controldir
 
1084
            remote_repo = RemoteRepository(a_controldir, format)
594
1085
            format._creating_repo = remote_repo
595
1086
            return remote_repo
596
1087
 
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()
 
1088
    def open(self, a_controldir):
 
1089
        if not isinstance(a_controldir, RemoteBzrDir):
 
1090
            raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
 
1091
        return a_controldir.open_repository()
601
1092
 
602
1093
    def _ensure_real(self):
603
1094
        if self._custom_format is None:
604
 
            self._custom_format = repository.network_format_registry.get(
605
 
                self._network_name)
 
1095
            try:
 
1096
                self._custom_format = _mod_repository.network_format_registry.get(
 
1097
                    self._network_name)
 
1098
            except KeyError:
 
1099
                raise errors.UnknownFormatError(kind='repository',
 
1100
                    format=self._network_name)
606
1101
 
607
1102
    @property
608
1103
    def _fetch_order(self):
643
1138
        return self._custom_format._serializer
644
1139
 
645
1140
 
646
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
647
 
    bzrdir.ControlComponent):
 
1141
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
 
1142
        lock._RelockDebugMixin):
648
1143
    """Repository accessed over rpc.
649
1144
 
650
1145
    For the moment most operations are performed using local transport-backed
666
1161
            self._real_repository = real_repository
667
1162
        else:
668
1163
            self._real_repository = None
669
 
        self.bzrdir = remote_bzrdir
 
1164
        self.controldir = remote_bzrdir
670
1165
        if _client is None:
671
1166
            self._client = remote_bzrdir._client
672
1167
        else:
674
1169
        self._format = format
675
1170
        self._lock_mode = None
676
1171
        self._lock_token = None
 
1172
        self._write_group_tokens = None
677
1173
        self._lock_count = 0
678
1174
        self._leave_lock = False
679
1175
        # Cache of revision parents; misses are cached during read locks, and
689
1185
        self._reconcile_does_inventory_gc = False
690
1186
        self._reconcile_fixes_text_parents = False
691
1187
        self._reconcile_backsup_inventory = False
692
 
        self.base = self.bzrdir.transport.base
 
1188
        self.base = self.controldir.transport.base
693
1189
        # Additional places to query for data.
694
1190
        self._fallback_repositories = []
695
1191
 
696
1192
    @property
697
1193
    def user_transport(self):
698
 
        return self.bzrdir.user_transport
 
1194
        return self.controldir.user_transport
699
1195
 
700
1196
    @property
701
1197
    def control_transport(self):
702
1198
        # XXX: Normally you shouldn't directly get at the remote repository
703
1199
        # transport, but I'm not sure it's worth making this method
704
1200
        # optional -- mbp 2010-04-21
705
 
        return self.bzrdir.get_repository_transport(None)
706
 
        
 
1201
        return self.controldir.get_repository_transport(None)
 
1202
 
707
1203
    def __str__(self):
708
1204
        return "%s(%s)" % (self.__class__.__name__, self.base)
709
1205
 
719
1215
 
720
1216
        :param suppress_errors: see Repository.abort_write_group.
721
1217
        """
722
 
        self._ensure_real()
723
 
        return self._real_repository.abort_write_group(
724
 
            suppress_errors=suppress_errors)
 
1218
        if self._real_repository:
 
1219
            self._ensure_real()
 
1220
            return self._real_repository.abort_write_group(
 
1221
                suppress_errors=suppress_errors)
 
1222
        if not self.is_in_write_group():
 
1223
            if suppress_errors:
 
1224
                mutter('(suppressed) not in write group')
 
1225
                return
 
1226
            raise errors.BzrError("not in write group")
 
1227
        path = self.controldir._path_for_remote_call(self._client)
 
1228
        try:
 
1229
            response = self._call('Repository.abort_write_group', path,
 
1230
                self._lock_token, self._write_group_tokens)
 
1231
        except Exception as exc:
 
1232
            self._write_group = None
 
1233
            if not suppress_errors:
 
1234
                raise
 
1235
            mutter('abort_write_group failed')
 
1236
            log_exception_quietly()
 
1237
            note(gettext('bzr: ERROR (ignored): %s'), exc)
 
1238
        else:
 
1239
            if response != ('ok', ):
 
1240
                raise errors.UnexpectedSmartServerResponse(response)
 
1241
            self._write_group_tokens = None
725
1242
 
726
1243
    @property
727
1244
    def chk_bytes(self):
741
1258
        for older plugins that don't use e.g. the CommitBuilder
742
1259
        facility.
743
1260
        """
744
 
        self._ensure_real()
745
 
        return self._real_repository.commit_write_group()
 
1261
        if self._real_repository:
 
1262
            self._ensure_real()
 
1263
            return self._real_repository.commit_write_group()
 
1264
        if not self.is_in_write_group():
 
1265
            raise errors.BzrError("not in write group")
 
1266
        path = self.controldir._path_for_remote_call(self._client)
 
1267
        response = self._call('Repository.commit_write_group', path,
 
1268
            self._lock_token, self._write_group_tokens)
 
1269
        if response != ('ok', ):
 
1270
            raise errors.UnexpectedSmartServerResponse(response)
 
1271
        self._write_group_tokens = None
 
1272
        # Refresh data after writing to the repository.
 
1273
        self.refresh_data()
746
1274
 
747
1275
    def resume_write_group(self, tokens):
748
 
        self._ensure_real()
749
 
        return self._real_repository.resume_write_group(tokens)
 
1276
        if self._real_repository:
 
1277
            return self._real_repository.resume_write_group(tokens)
 
1278
        path = self.controldir._path_for_remote_call(self._client)
 
1279
        try:
 
1280
            response = self._call('Repository.check_write_group', path,
 
1281
               self._lock_token, tokens)
 
1282
        except errors.UnknownSmartMethod:
 
1283
            self._ensure_real()
 
1284
            return self._real_repository.resume_write_group(tokens)
 
1285
        if response != ('ok', ):
 
1286
            raise errors.UnexpectedSmartServerResponse(response)
 
1287
        self._write_group_tokens = tokens
750
1288
 
751
1289
    def suspend_write_group(self):
752
 
        self._ensure_real()
753
 
        return self._real_repository.suspend_write_group()
 
1290
        if self._real_repository:
 
1291
            return self._real_repository.suspend_write_group()
 
1292
        ret = self._write_group_tokens or []
 
1293
        self._write_group_tokens = None
 
1294
        return ret
754
1295
 
755
1296
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
756
1297
        self._ensure_real()
764
1305
 
765
1306
    def get_rev_id_for_revno(self, revno, known_pair):
766
1307
        """See Repository.get_rev_id_for_revno."""
767
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1308
        path = self.controldir._path_for_remote_call(self._client)
768
1309
        try:
769
1310
            if self._client._medium._is_remote_before((1, 17)):
770
1311
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
807
1348
                warning('VFS Repository access triggered\n%s',
808
1349
                    ''.join(traceback.format_stack()))
809
1350
            self._unstacked_provider.missing_keys.clear()
810
 
            self.bzrdir._ensure_real()
 
1351
            self.controldir._ensure_real()
811
1352
            self._set_real_repository(
812
 
                self.bzrdir._real_bzrdir.open_repository())
 
1353
                self.controldir._real_bzrdir.open_repository())
813
1354
 
814
1355
    def _translate_error(self, err, **context):
815
 
        self.bzrdir._translate_error(err, repository=self, **context)
 
1356
        self.controldir._translate_error(err, repository=self, **context)
816
1357
 
817
1358
    def find_text_key_references(self):
818
1359
        """Find the text key references within the repository.
819
1360
 
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
1361
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
1362
            to whether they were referred to by the inventory of the
825
1363
            revision_id that they contain. The inventory texts from all present
843
1381
        """Private method for using with old (< 1.2) servers to fallback."""
844
1382
        if revision_id is None:
845
1383
            revision_id = ''
846
 
        elif revision.is_null(revision_id):
 
1384
        elif _mod_revision.is_null(revision_id):
847
1385
            return {}
848
1386
 
849
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1387
        path = self.controldir._path_for_remote_call(self._client)
850
1388
        response = self._call_expecting_body(
851
1389
            'Repository.get_revision_graph', path, revision_id)
852
1390
        response_tuple, response_handler = response
872
1410
        """Return a source for streaming from this repository."""
873
1411
        return RemoteStreamSource(self, to_format)
874
1412
 
875
 
    @needs_read_lock
 
1413
    def get_file_graph(self):
 
1414
        with self.lock_read():
 
1415
            return graph.Graph(self.texts)
 
1416
 
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
879
 
        return revision_id in self.has_revisions((revision_id,))
 
1419
        # Copy of breezy.repository.Repository.has_revision
 
1420
        with self.lock_read():
 
1421
            return revision_id in self.has_revisions((revision_id,))
880
1422
 
881
 
    @needs_read_lock
882
1423
    def has_revisions(self, revision_ids):
883
1424
        """Probe to find out the presence of multiple revisions.
884
1425
 
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
889
 
        parent_map = self.get_parent_map(revision_ids)
890
 
        result = set(parent_map)
891
 
        if _mod_revision.NULL_REVISION in revision_ids:
892
 
            result.add(_mod_revision.NULL_REVISION)
893
 
        return result
 
1429
        with self.lock_read():
 
1430
            # Copy of breezy.repository.Repository.has_revisions
 
1431
            parent_map = self.get_parent_map(revision_ids)
 
1432
            result = set(parent_map)
 
1433
            if _mod_revision.NULL_REVISION in revision_ids:
 
1434
                result.add(_mod_revision.NULL_REVISION)
 
1435
            return result
894
1436
 
895
1437
    def _has_same_fallbacks(self, other_repo):
896
1438
        """Returns true if the repositories have the same fallbacks."""
897
1439
        # XXX: copied from Repository; it should be unified into a base class
898
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
1440
        # <https://bugs.launchpad.net/bzr/+bug/401622>
899
1441
        my_fb = self._fallback_repositories
900
1442
        other_fb = other_repo._fallback_repositories
901
1443
        if len(my_fb) != len(other_fb):
910
1452
        # one; unfortunately the tests rely on slightly different behaviour at
911
1453
        # present -- mbp 20090710
912
1454
        return (self.__class__ is other.__class__ and
913
 
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
1455
                self.controldir.transport.base == other.controldir.transport.base)
914
1456
 
915
1457
    def get_graph(self, other_repository=None):
916
1458
        """Return the graph for this repository format"""
917
1459
        parents_provider = self._make_parents_provider(other_repository)
918
1460
        return graph.Graph(parents_provider)
919
1461
 
920
 
    @needs_read_lock
921
1462
    def get_known_graph_ancestry(self, revision_ids):
922
1463
        """Return the known graph for a set of revision ids and their ancestors.
923
1464
        """
924
 
        st = static_tuple.StaticTuple
925
 
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
926
 
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
927
 
        return graph.GraphThunkIdsToKeys(known_graph)
 
1465
        with self.lock_read():
 
1466
            st = static_tuple.StaticTuple
 
1467
            revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
1468
            known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
1469
            return graph.GraphThunkIdsToKeys(known_graph)
928
1470
 
929
1471
    def gather_stats(self, revid=None, committers=None):
930
1472
        """See Repository.gather_stats()."""
931
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1473
        path = self.controldir._path_for_remote_call(self._client)
932
1474
        # revid can be None to indicate no revisions, not just NULL_REVISION
933
 
        if revid is None or revision.is_null(revid):
 
1475
        if revid is None or _mod_revision.is_null(revid):
934
1476
            fmt_revid = ''
935
1477
        else:
936
1478
            fmt_revid = revid
953
1495
                result[key] = int(val_text)
954
1496
            elif key in ('firstrev', 'latestrev'):
955
1497
                values = val_text.split(' ')[1:]
956
 
                result[key] = (float(values[0]), long(values[1]))
 
1498
                result[key] = (float(values[0]), int(values[1]))
957
1499
 
958
1500
        return result
959
1501
 
965
1507
 
966
1508
    def get_physical_lock_status(self):
967
1509
        """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()
 
1510
        path = self.controldir._path_for_remote_call(self._client)
 
1511
        try:
 
1512
            response = self._call('Repository.get_physical_lock_status', path)
 
1513
        except errors.UnknownSmartMethod:
 
1514
            self._ensure_real()
 
1515
            return self._real_repository.get_physical_lock_status()
 
1516
        if response[0] not in ('yes', 'no'):
 
1517
            raise errors.UnexpectedSmartServerResponse(response)
 
1518
        return (response[0] == 'yes')
971
1519
 
972
1520
    def is_in_write_group(self):
973
1521
        """Return True if there is an open write group.
974
1522
 
975
1523
        write groups are only applicable locally for the smart server..
976
1524
        """
 
1525
        if self._write_group_tokens is not None:
 
1526
            return True
977
1527
        if self._real_repository:
978
1528
            return self._real_repository.is_in_write_group()
979
1529
 
982
1532
 
983
1533
    def is_shared(self):
984
1534
        """See Repository.is_shared()."""
985
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1535
        path = self.controldir._path_for_remote_call(self._client)
986
1536
        response = self._call('Repository.is_shared', path)
987
1537
        if response[0] not in ('yes', 'no'):
988
1538
            raise SmartProtocolError('unexpected response code %s' % (response,))
997
1547
        pass
998
1548
 
999
1549
    def lock_read(self):
 
1550
        """Lock the repository for read operations.
 
1551
 
 
1552
        :return: A breezy.lock.LogicalLockResult.
 
1553
        """
1000
1554
        # wrong eventually - want a local lock cache context
1001
1555
        if not self._lock_mode:
1002
1556
            self._note_lock('r')
1009
1563
                repo.lock_read()
1010
1564
        else:
1011
1565
            self._lock_count += 1
 
1566
        return lock.LogicalLockResult(self.unlock)
1012
1567
 
1013
1568
    def _remote_lock_write(self, token):
1014
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1569
        path = self.controldir._path_for_remote_call(self._client)
1015
1570
        if token is None:
1016
1571
            token = ''
1017
1572
        err_context = {'token': token}
1054
1609
            raise errors.ReadOnlyError(self)
1055
1610
        else:
1056
1611
            self._lock_count += 1
1057
 
        return self._lock_token or None
 
1612
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1058
1613
 
1059
1614
    def leave_lock_in_place(self):
1060
1615
        if not self._lock_token:
1109
1664
            self._real_repository.lock_write(self._lock_token)
1110
1665
        elif self._lock_mode == 'r':
1111
1666
            self._real_repository.lock_read()
 
1667
        if self._write_group_tokens is not None:
 
1668
            # if we are already in a write group, resume it
 
1669
            self._real_repository.resume_write_group(self._write_group_tokens)
 
1670
            self._write_group_tokens = None
1112
1671
 
1113
1672
    def start_write_group(self):
1114
1673
        """Start a write group on the decorated repository.
1118
1677
        for older plugins that don't use e.g. the CommitBuilder
1119
1678
        facility.
1120
1679
        """
1121
 
        self._ensure_real()
1122
 
        return self._real_repository.start_write_group()
 
1680
        if self._real_repository:
 
1681
            self._ensure_real()
 
1682
            return self._real_repository.start_write_group()
 
1683
        if not self.is_write_locked():
 
1684
            raise errors.NotWriteLocked(self)
 
1685
        if self._write_group_tokens is not None:
 
1686
            raise errors.BzrError('already in a write group')
 
1687
        path = self.controldir._path_for_remote_call(self._client)
 
1688
        try:
 
1689
            response = self._call('Repository.start_write_group', path,
 
1690
                self._lock_token)
 
1691
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
 
1692
            self._ensure_real()
 
1693
            return self._real_repository.start_write_group()
 
1694
        if response[0] != 'ok':
 
1695
            raise errors.UnexpectedSmartServerResponse(response)
 
1696
        self._write_group_tokens = response[1]
1123
1697
 
1124
1698
    def _unlock(self, token):
1125
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1699
        path = self.controldir._path_for_remote_call(self._client)
1126
1700
        if not token:
1127
1701
            # with no token the remote repository is not persistently locked.
1128
1702
            return
1152
1726
            # This is just to let the _real_repository stay up to date.
1153
1727
            if self._real_repository is not None:
1154
1728
                self._real_repository.unlock()
 
1729
            elif self._write_group_tokens is not None:
 
1730
                self.abort_write_group()
1155
1731
        finally:
1156
1732
            # The rpc-level lock should be released even if there was a
1157
1733
            # problem releasing the vfs-based lock.
1169
1745
 
1170
1746
    def break_lock(self):
1171
1747
        # should hand off to the network
1172
 
        self._ensure_real()
1173
 
        return self._real_repository.break_lock()
 
1748
        path = self.controldir._path_for_remote_call(self._client)
 
1749
        try:
 
1750
            response = self._call("Repository.break_lock", path)
 
1751
        except errors.UnknownSmartMethod:
 
1752
            self._ensure_real()
 
1753
            return self._real_repository.break_lock()
 
1754
        if response != ('ok',):
 
1755
            raise errors.UnexpectedSmartServerResponse(response)
1174
1756
 
1175
1757
    def _get_tarball(self, compression):
1176
1758
        """Return a TemporaryFile containing a repository tarball.
1178
1760
        Returns None if the server does not support sending tarballs.
1179
1761
        """
1180
1762
        import tempfile
1181
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
1763
        path = self.controldir._path_for_remote_call(self._client)
1182
1764
        try:
1183
1765
            response, protocol = self._call_expecting_body(
1184
1766
                'Repository.tarball', path, compression)
1195
1777
        raise errors.UnexpectedSmartServerResponse(response)
1196
1778
 
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)
1202
 
        dest_repo.fetch(self, revision_id=revision_id)
 
1780
        """Create a descendent repository for new development.
 
1781
 
 
1782
        Unlike clone, this does not copy the settings of the repository.
 
1783
        """
 
1784
        with self.lock_read():
 
1785
            dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
1786
            dest_repo.fetch(self, revision_id=revision_id)
 
1787
            return dest_repo
 
1788
 
 
1789
    def _create_sprouting_repo(self, a_controldir, shared):
 
1790
        if not isinstance(a_controldir._format, self.controldir._format.__class__):
 
1791
            # use target default format.
 
1792
            dest_repo = a_controldir.create_repository()
 
1793
        else:
 
1794
            # Most control formats need the repository to be specifically
 
1795
            # created, but on some old all-in-one formats it's not needed
 
1796
            try:
 
1797
                dest_repo = self._format.initialize(a_controldir, shared=shared)
 
1798
            except errors.UninitializableFormat:
 
1799
                dest_repo = a_controldir.open_repository()
1203
1800
        return dest_repo
1204
1801
 
1205
1802
    ### These methods are just thin shims to the VFS object for now.
1206
1803
 
1207
1804
    def revision_tree(self, revision_id):
1208
 
        self._ensure_real()
1209
 
        return self._real_repository.revision_tree(revision_id)
 
1805
        with self.lock_read():
 
1806
            revision_id = _mod_revision.ensure_null(revision_id)
 
1807
            if revision_id == _mod_revision.NULL_REVISION:
 
1808
                return InventoryRevisionTree(self,
 
1809
                    Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
1810
            else:
 
1811
                return list(self.revision_trees([revision_id]))[0]
1210
1812
 
1211
1813
    def get_serializer_format(self):
1212
 
        self._ensure_real()
1213
 
        return self._real_repository.get_serializer_format()
 
1814
        path = self.controldir._path_for_remote_call(self._client)
 
1815
        try:
 
1816
            response = self._call('VersionedFileRepository.get_serializer_format',
 
1817
                path)
 
1818
        except errors.UnknownSmartMethod:
 
1819
            self._ensure_real()
 
1820
            return self._real_repository.get_serializer_format()
 
1821
        if response[0] != 'ok':
 
1822
            raise errors.UnexpectedSmartServerResponse(response)
 
1823
        return response[1]
1214
1824
 
1215
1825
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
1826
                           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
 
1827
                           revision_id=None, lossy=False):
 
1828
        """Obtain a CommitBuilder for this repository.
 
1829
 
 
1830
        :param branch: Branch to commit to.
 
1831
        :param parents: Revision ids of the parents of the new revision.
 
1832
        :param config: Configuration to use.
 
1833
        :param timestamp: Optional timestamp recorded for commit.
 
1834
        :param timezone: Optional timezone for timestamp.
 
1835
        :param committer: Optional committer to set for commit.
 
1836
        :param revprops: Optional dictionary of revision properties.
 
1837
        :param revision_id: Optional revision id.
 
1838
        :param lossy: Whether to discard data that can not be natively
 
1839
            represented, when pushing to a foreign VCS
 
1840
        """
 
1841
        if self._fallback_repositories and not self._format.supports_chks:
 
1842
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1843
                " in pre-2a formats. See "
 
1844
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
1845
        if self._format.rich_root_data:
 
1846
            commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
 
1847
        else:
 
1848
            commit_builder_kls = vf_repository.VersionedFileCommitBuilder
 
1849
        result = commit_builder_kls(self, parents, config,
 
1850
            timestamp, timezone, committer, revprops, revision_id,
 
1851
            lossy)
 
1852
        self.start_write_group()
 
1853
        return result
1226
1854
 
1227
1855
    def add_fallback_repository(self, repository):
1228
1856
        """Add a repository to use for looking up data not held locally.
1235
1863
        # We need to accumulate additional repositories here, to pass them in
1236
1864
        # on various RPC's.
1237
1865
        #
 
1866
        # Make the check before we lock: this raises an exception.
 
1867
        self._check_fallback_repository(repository)
1238
1868
        if self.is_locked():
1239
1869
            # We will call fallback.unlock() when we transition to the unlocked
1240
1870
            # state, so always add a lock here. If a caller passes us a locked
1241
1871
            # repository, they are responsible for unlocking it later.
1242
1872
            repository.lock_read()
1243
 
        self._check_fallback_repository(repository)
1244
1873
        self._fallback_repositories.append(repository)
1245
1874
        # If self._real_repository was parameterised already (e.g. because a
1246
1875
        # _real_branch had its get_stacked_on_url method called), then the
1272
1901
            delta, new_revision_id, parents, basis_inv=basis_inv,
1273
1902
            propagate_caches=propagate_caches)
1274
1903
 
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)
1279
 
 
1280
 
    @needs_read_lock
 
1904
    def add_revision(self, revision_id, rev, inv=None):
 
1905
        _mod_revision.check_not_reserved_id(revision_id)
 
1906
        key = (revision_id,)
 
1907
        # check inventory present
 
1908
        if not self.inventories.get_parent_map([key]):
 
1909
            if inv is None:
 
1910
                raise errors.WeaveRevisionNotPresent(revision_id,
 
1911
                                                     self.inventories)
 
1912
            else:
 
1913
                # yes, this is not suitable for adding with ghosts.
 
1914
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
 
1915
                                                        rev.parent_ids)
 
1916
        else:
 
1917
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
 
1918
        self._add_revision(rev)
 
1919
 
 
1920
    def _add_revision(self, rev):
 
1921
        if self._real_repository is not None:
 
1922
            return self._real_repository._add_revision(rev)
 
1923
        text = self._serializer.write_revision_to_string(rev)
 
1924
        key = (rev.revision_id,)
 
1925
        parents = tuple((parent,) for parent in rev.parent_ids)
 
1926
        self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
 
1927
            [('revisions', [FulltextContentFactory(key, parents, None, text)])],
 
1928
            self._format, self._write_group_tokens)
 
1929
 
1281
1930
    def get_inventory(self, revision_id):
 
1931
        with self.lock_read():
 
1932
            return list(self.iter_inventories([revision_id]))[0]
 
1933
 
 
1934
    def _iter_inventories_rpc(self, revision_ids, ordering):
 
1935
        if ordering is None:
 
1936
            ordering = 'unordered'
 
1937
        path = self.controldir._path_for_remote_call(self._client)
 
1938
        body = "\n".join(revision_ids)
 
1939
        response_tuple, response_handler = (
 
1940
            self._call_with_body_bytes_expecting_body(
 
1941
                "VersionedFileRepository.get_inventories",
 
1942
                (path, ordering), body))
 
1943
        if response_tuple[0] != "ok":
 
1944
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1945
        deserializer = inventory_delta.InventoryDeltaDeserializer()
 
1946
        byte_stream = response_handler.read_streamed_body()
 
1947
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
 
1948
        if decoded is None:
 
1949
            # no results whatsoever
 
1950
            return
 
1951
        src_format, stream = decoded
 
1952
        if src_format.network_name() != self._format.network_name():
 
1953
            raise AssertionError(
 
1954
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
1955
                src_format.network_name(), self._format.network_name()))
 
1956
        # ignore the src format, it's not really relevant
 
1957
        prev_inv = Inventory(root_id=None,
 
1958
            revision_id=_mod_revision.NULL_REVISION)
 
1959
        # there should be just one substream, with inventory deltas
 
1960
        substream_kind, substream = next(stream)
 
1961
        if substream_kind != "inventory-deltas":
 
1962
            raise AssertionError(
 
1963
                 "Unexpected stream %r received" % substream_kind)
 
1964
        for record in substream:
 
1965
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
 
1966
                deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
 
1967
            if parent_id != prev_inv.revision_id:
 
1968
                raise AssertionError("invalid base %r != %r" % (parent_id,
 
1969
                    prev_inv.revision_id))
 
1970
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
 
1971
            yield inv, inv.revision_id
 
1972
            prev_inv = inv
 
1973
 
 
1974
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
1282
1975
        self._ensure_real()
1283
 
        return self._real_repository.get_inventory(revision_id)
 
1976
        return self._real_repository._iter_inventories(revision_ids, ordering)
1284
1977
 
1285
1978
    def iter_inventories(self, revision_ids, ordering=None):
1286
 
        self._ensure_real()
1287
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
1288
 
 
1289
 
    @needs_read_lock
 
1979
        """Get many inventories by revision_ids.
 
1980
 
 
1981
        This will buffer some or all of the texts used in constructing the
 
1982
        inventories in memory, but will only parse a single inventory at a
 
1983
        time.
 
1984
 
 
1985
        :param revision_ids: The expected revision ids of the inventories.
 
1986
        :param ordering: optional ordering, e.g. 'topological'.  If not
 
1987
            specified, the order of revision_ids will be preserved (by
 
1988
            buffering if necessary).
 
1989
        :return: An iterator of inventories.
 
1990
        """
 
1991
        if ((None in revision_ids)
 
1992
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
1993
            raise ValueError('cannot get null revision inventory')
 
1994
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
1995
            if inv is None:
 
1996
                raise errors.NoSuchRevision(self, revid)
 
1997
            yield inv
 
1998
 
 
1999
    def _iter_inventories(self, revision_ids, ordering=None):
 
2000
        if len(revision_ids) == 0:
 
2001
            return
 
2002
        missing = set(revision_ids)
 
2003
        if ordering is None:
 
2004
            order_as_requested = True
 
2005
            invs = {}
 
2006
            order = list(revision_ids)
 
2007
            order.reverse()
 
2008
            next_revid = order.pop()
 
2009
        else:
 
2010
            order_as_requested = False
 
2011
            if ordering != 'unordered' and self._fallback_repositories:
 
2012
                raise ValueError('unsupported ordering %r' % ordering)
 
2013
        iter_inv_fns = [self._iter_inventories_rpc] + [
 
2014
            fallback._iter_inventories for fallback in
 
2015
            self._fallback_repositories]
 
2016
        try:
 
2017
            for iter_inv in iter_inv_fns:
 
2018
                request = [revid for revid in revision_ids if revid in missing]
 
2019
                for inv, revid in iter_inv(request, ordering):
 
2020
                    if inv is None:
 
2021
                        continue
 
2022
                    missing.remove(inv.revision_id)
 
2023
                    if ordering != 'unordered':
 
2024
                        invs[revid] = inv
 
2025
                    else:
 
2026
                        yield inv, revid
 
2027
                if order_as_requested:
 
2028
                    # Yield as many results as we can while preserving order.
 
2029
                    while next_revid in invs:
 
2030
                        inv = invs.pop(next_revid)
 
2031
                        yield inv, inv.revision_id
 
2032
                        try:
 
2033
                            next_revid = order.pop()
 
2034
                        except IndexError:
 
2035
                            # We still want to fully consume the stream, just
 
2036
                            # in case it is not actually finished at this point
 
2037
                            next_revid = None
 
2038
                            break
 
2039
        except errors.UnknownSmartMethod:
 
2040
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
 
2041
                yield inv, revid
 
2042
            return
 
2043
        # Report missing
 
2044
        if order_as_requested:
 
2045
            if next_revid is not None:
 
2046
                yield None, next_revid
 
2047
            while order:
 
2048
                revid = order.pop()
 
2049
                yield invs.get(revid), revid
 
2050
        else:
 
2051
            while missing:
 
2052
                yield None, missing.pop()
 
2053
 
1290
2054
    def get_revision(self, revision_id):
1291
 
        self._ensure_real()
1292
 
        return self._real_repository.get_revision(revision_id)
 
2055
        with self.lock_read():
 
2056
            return self.get_revisions([revision_id])[0]
1293
2057
 
1294
2058
    def get_transaction(self):
1295
2059
        self._ensure_real()
1296
2060
        return self._real_repository.get_transaction()
1297
2061
 
1298
 
    @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
        with self.lock_read():
 
2064
            dest_repo = self._create_sprouting_repo(
 
2065
                a_controldir, shared=self.is_shared())
 
2066
            self.copy_content_into(dest_repo, revision_id)
 
2067
            return dest_repo
1302
2068
 
1303
2069
    def make_working_trees(self):
1304
2070
        """See Repository.make_working_trees"""
1305
 
        self._ensure_real()
1306
 
        return self._real_repository.make_working_trees()
 
2071
        path = self.controldir._path_for_remote_call(self._client)
 
2072
        try:
 
2073
            response = self._call('Repository.make_working_trees', path)
 
2074
        except errors.UnknownSmartMethod:
 
2075
            self._ensure_real()
 
2076
            return self._real_repository.make_working_trees()
 
2077
        if response[0] not in ('yes', 'no'):
 
2078
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2079
        return response[0] == 'yes'
1307
2080
 
1308
2081
    def refresh_data(self):
1309
 
        """Re-read any data needed to to synchronise with disk.
 
2082
        """Re-read any data needed to synchronise with disk.
1310
2083
 
1311
2084
        This method is intended to be called after another repository instance
1312
2085
        (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.
 
2086
        repository. On all repositories this will work outside of write groups.
 
2087
        Some repository formats (pack and newer for breezy native formats)
 
2088
        support refresh_data inside write groups. If called inside a write
 
2089
        group on a repository that does not support refreshing in a write group
 
2090
        IsInWriteGroupError will be raised.
1315
2091
        """
1316
 
        if self.is_in_write_group():
1317
 
            raise errors.InternalBzrError(
1318
 
                "May not refresh_data while in a write group.")
1319
2092
        if self._real_repository is not None:
1320
2093
            self._real_repository.refresh_data()
 
2094
        # Refresh the parents cache for this object
 
2095
        self._unstacked_provider.disable_cache()
 
2096
        self._unstacked_provider.enable_cache()
1321
2097
 
1322
2098
    def revision_ids_to_search_result(self, result_set):
1323
2099
        """Convert a set of revision ids to a graph SearchResult."""
1324
2100
        result_parents = set()
1325
 
        for parents in self.get_graph().get_parent_map(
1326
 
            result_set).itervalues():
 
2101
        for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
1327
2102
            result_parents.update(parents)
1328
2103
        included_keys = result_set.intersection(result_parents)
1329
2104
        start_keys = result_set.difference(included_keys)
1330
2105
        exclude_keys = result_parents.difference(result_set)
1331
 
        result = graph.SearchResult(start_keys, exclude_keys,
 
2106
        result = vf_search.SearchResult(start_keys, exclude_keys,
1332
2107
            len(result_set), result_set)
1333
2108
        return result
1334
2109
 
1335
 
    @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
        with self.lock_read():
 
2120
            inter_repo = _mod_repository.InterRepository.get(other, self)
 
2121
            return inter_repo.search_missing_revision_ids(
 
2122
                find_ghosts=find_ghosts, revision_ids=revision_ids,
 
2123
                if_present_ids=if_present_ids, limit=limit)
1345
2124
 
1346
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
2125
    def fetch(self, source, revision_id=None, find_ghosts=False,
1347
2126
            fetch_spec=None):
1348
2127
        # No base implementation to use as RemoteRepository is not a subclass
1349
2128
        # of Repository; so this is a copy of Repository.fetch().
1360
2139
            # check that last_revision is in 'from' and then return a
1361
2140
            # no-operation.
1362
2141
            if (revision_id is not None and
1363
 
                not revision.is_null(revision_id)):
 
2142
                not _mod_revision.is_null(revision_id)):
1364
2143
                self.get_revision(revision_id)
1365
2144
            return 0, []
1366
2145
        # if there is no specific appropriate InterRepository, this will get
1367
2146
        # the InterRepository base class, which raises an
1368
2147
        # IncompatibleRepositories when asked to fetch.
1369
 
        inter = repository.InterRepository.get(source, self)
1370
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
2148
        inter = _mod_repository.InterRepository.get(source, self)
 
2149
        if (fetch_spec is not None and
 
2150
            not getattr(inter, "supports_fetch_spec", False)):
 
2151
            raise errors.UnsupportedOperation(
 
2152
                "fetch_spec not supported for %r" % inter)
 
2153
        return inter.fetch(revision_id=revision_id,
1371
2154
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1372
2155
 
1373
2156
    def create_bundle(self, target, base, fileobj, format=None):
1374
2157
        self._ensure_real()
1375
2158
        self._real_repository.create_bundle(target, base, fileobj, format)
1376
2159
 
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
2160
    def fileids_altered_by_revision_ids(self, revision_ids):
1383
2161
        self._ensure_real()
1384
2162
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1388
2166
        return self._real_repository._get_versioned_file_checker(
1389
2167
            revisions, revision_versions_cache)
1390
2168
 
 
2169
    def _iter_files_bytes_rpc(self, desired_files, absent):
 
2170
        path = self.controldir._path_for_remote_call(self._client)
 
2171
        lines = []
 
2172
        identifiers = []
 
2173
        for (file_id, revid, identifier) in desired_files:
 
2174
            lines.append("%s\0%s" % (
 
2175
                osutils.safe_file_id(file_id),
 
2176
                osutils.safe_revision_id(revid)))
 
2177
            identifiers.append(identifier)
 
2178
        (response_tuple, response_handler) = (
 
2179
            self._call_with_body_bytes_expecting_body(
 
2180
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
 
2181
        if response_tuple != ('ok', ):
 
2182
            response_handler.cancel_read_body()
 
2183
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2184
        byte_stream = response_handler.read_streamed_body()
 
2185
        def decompress_stream(start, byte_stream, unused):
 
2186
            decompressor = zlib.decompressobj()
 
2187
            yield decompressor.decompress(start)
 
2188
            while decompressor.unused_data == "":
 
2189
                try:
 
2190
                    data = next(byte_stream)
 
2191
                except StopIteration:
 
2192
                    break
 
2193
                yield decompressor.decompress(data)
 
2194
            yield decompressor.flush()
 
2195
            unused.append(decompressor.unused_data)
 
2196
        unused = ""
 
2197
        while True:
 
2198
            while not "\n" in unused:
 
2199
                unused += next(byte_stream)
 
2200
            header, rest = unused.split("\n", 1)
 
2201
            args = header.split("\0")
 
2202
            if args[0] == "absent":
 
2203
                absent[identifiers[int(args[3])]] = (args[1], args[2])
 
2204
                unused = rest
 
2205
                continue
 
2206
            elif args[0] == "ok":
 
2207
                idx = int(args[1])
 
2208
            else:
 
2209
                raise errors.UnexpectedSmartServerResponse(args)
 
2210
            unused_chunks = []
 
2211
            yield (identifiers[idx],
 
2212
                decompress_stream(rest, byte_stream, unused_chunks))
 
2213
            unused = "".join(unused_chunks)
 
2214
 
1391
2215
    def iter_files_bytes(self, desired_files):
1392
2216
        """See Repository.iter_file_bytes.
1393
2217
        """
1394
 
        self._ensure_real()
1395
 
        return self._real_repository.iter_files_bytes(desired_files)
 
2218
        try:
 
2219
            absent = {}
 
2220
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
 
2221
                    desired_files, absent):
 
2222
                yield identifier, bytes_iterator
 
2223
            for fallback in self._fallback_repositories:
 
2224
                if not absent:
 
2225
                    break
 
2226
                desired_files = [(key[0], key[1], identifier)
 
2227
                    for identifier, key in viewitems(absent)]
 
2228
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
 
2229
                    del absent[identifier]
 
2230
                    yield identifier, bytes_iterator
 
2231
            if absent:
 
2232
                # There may be more missing items, but raise an exception
 
2233
                # for just one.
 
2234
                missing_identifier = next(iter(absent))
 
2235
                missing_key = absent[missing_identifier]
 
2236
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
 
2237
                    file_id=missing_key[0])
 
2238
        except errors.UnknownSmartMethod:
 
2239
            self._ensure_real()
 
2240
            for (identifier, bytes_iterator) in (
 
2241
                self._real_repository.iter_files_bytes(desired_files)):
 
2242
                yield identifier, bytes_iterator
 
2243
 
 
2244
    def get_cached_parent_map(self, revision_ids):
 
2245
        """See breezy.CachingParentsProvider.get_cached_parent_map"""
 
2246
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
1396
2247
 
1397
2248
    def get_parent_map(self, revision_ids):
1398
 
        """See bzrlib.Graph.get_parent_map()."""
 
2249
        """See breezy.Graph.get_parent_map()."""
1399
2250
        return self._make_parents_provider().get_parent_map(revision_ids)
1400
2251
 
1401
2252
    def _get_parent_map_rpc(self, keys):
1420
2271
            # There is one other "bug" which is that ghosts in
1421
2272
            # get_revision_graph() are not returned at all. But we won't worry
1422
2273
            # about that for now.
1423
 
            for node_id, parent_ids in rg.iteritems():
 
2274
            for node_id, parent_ids in viewitems(rg):
1424
2275
                if parent_ids == ():
1425
2276
                    rg[node_id] = (NULL_REVISION,)
1426
2277
            rg[NULL_REVISION] = ()
1457
2308
        if parents_map is None:
1458
2309
            # Repository is not locked, so there's no cache.
1459
2310
            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)
 
2311
        if _DEFAULT_SEARCH_DEPTH <= 0:
 
2312
            (start_set, stop_keys,
 
2313
             key_count) = vf_search.search_result_from_parent_map(
 
2314
                parents_map, self._unstacked_provider.missing_keys)
 
2315
        else:
 
2316
            (start_set, stop_keys,
 
2317
             key_count) = vf_search.limited_search_result_from_parent_map(
 
2318
                parents_map, self._unstacked_provider.missing_keys,
 
2319
                keys, depth=_DEFAULT_SEARCH_DEPTH)
1480
2320
        recipe = ('manual', start_set, stop_keys, key_count)
1481
2321
        body = self._serialise_search_recipe(recipe)
1482
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2322
        path = self.controldir._path_for_remote_call(self._client)
1483
2323
        for key in keys:
1484
 
            if type(key) is not str:
 
2324
            if not isinstance(key, str):
1485
2325
                raise ValueError(
1486
2326
                    "key %r not a plain string" % (key,))
1487
2327
        verb = 'Repository.get_parent_map'
1529
2369
                        revision_graph[d[0]] = (NULL_REVISION,)
1530
2370
            return revision_graph
1531
2371
 
1532
 
    @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
        with self.lock_read():
 
2374
            path = self.controldir._path_for_remote_call(self._client)
 
2375
            try:
 
2376
                response_tuple, response_handler = self._call_expecting_body(
 
2377
                    'Repository.get_revision_signature_text', path, revision_id)
 
2378
            except errors.UnknownSmartMethod:
 
2379
                self._ensure_real()
 
2380
                return self._real_repository.get_signature_text(revision_id)
 
2381
            except errors.NoSuchRevision as err:
 
2382
                for fallback in self._fallback_repositories:
 
2383
                    try:
 
2384
                        return fallback.get_signature_text(revision_id)
 
2385
                    except errors.NoSuchRevision:
 
2386
                        pass
 
2387
                raise err
 
2388
            else:
 
2389
                if response_tuple[0] != 'ok':
 
2390
                    raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2391
                return response_handler.read_body_bytes()
1536
2392
 
1537
 
    @needs_read_lock
1538
2393
    def _get_inventory_xml(self, revision_id):
1539
 
        self._ensure_real()
1540
 
        return self._real_repository._get_inventory_xml(revision_id)
 
2394
        with self.lock_read():
 
2395
            # This call is used by older working tree formats,
 
2396
            # which stored a serialized basis inventory.
 
2397
            self._ensure_real()
 
2398
            return self._real_repository._get_inventory_xml(revision_id)
1541
2399
 
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
        with self.lock_write():
 
2403
            path = self.controldir._path_for_remote_call(self._client)
 
2404
            try:
 
2405
                response, handler = self._call_expecting_body(
 
2406
                    'Repository.reconcile', path, self._lock_token)
 
2407
            except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
 
2408
                self._ensure_real()
 
2409
                return self._real_repository.reconcile(other=other, thorough=thorough)
 
2410
            if response != ('ok', ):
 
2411
                raise errors.UnexpectedSmartServerResponse(response)
 
2412
            body = handler.read_body_bytes()
 
2413
            result = RepoReconciler(self)
 
2414
            for line in body.split('\n'):
 
2415
                if not line:
 
2416
                    continue
 
2417
                key, val_text = line.split(':')
 
2418
                if key == "garbage_inventories":
 
2419
                    result.garbage_inventories = int(val_text)
 
2420
                elif key == "inconsistent_parents":
 
2421
                    result.inconsistent_parents = int(val_text)
 
2422
                else:
 
2423
                    mutter("unknown reconcile key %r" % key)
 
2424
            return result
1545
2425
 
1546
2426
    def all_revision_ids(self):
1547
 
        self._ensure_real()
1548
 
        return self._real_repository.all_revision_ids()
1549
 
 
1550
 
    @needs_read_lock
 
2427
        path = self.controldir._path_for_remote_call(self._client)
 
2428
        try:
 
2429
            response_tuple, response_handler = self._call_expecting_body(
 
2430
                "Repository.all_revision_ids", path)
 
2431
        except errors.UnknownSmartMethod:
 
2432
            self._ensure_real()
 
2433
            return self._real_repository.all_revision_ids()
 
2434
        if response_tuple != ("ok", ):
 
2435
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2436
        revids = set(response_handler.read_body_bytes().splitlines())
 
2437
        for fallback in self._fallback_repositories:
 
2438
            revids.update(set(fallback.all_revision_ids()))
 
2439
        return list(revids)
 
2440
 
 
2441
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2442
        """Return Tree for a revision on this branch with only some files.
 
2443
 
 
2444
        :param revision_ids: a sequence of revision-ids;
 
2445
          a revision-id may not be None or 'null:'
 
2446
        :param file_ids: if not None, the result is filtered
 
2447
          so that only those file-ids, their parents and their
 
2448
          children are included.
 
2449
        """
 
2450
        inventories = self.iter_inventories(revision_ids)
 
2451
        for inv in inventories:
 
2452
            # Should we introduce a FilteredRevisionTree class rather
 
2453
            # than pre-filter the inventory here?
 
2454
            filtered_inv = inv.filter(file_ids)
 
2455
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2456
 
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)
1555
 
 
1556
 
    @needs_read_lock
 
2458
        with self.lock_read():
 
2459
            medium = self._client._medium
 
2460
            if medium._is_remote_before((1, 2)):
 
2461
                self._ensure_real()
 
2462
                for delta in self._real_repository.get_deltas_for_revisions(
 
2463
                        revisions, specific_fileids):
 
2464
                    yield delta
 
2465
                return
 
2466
            # Get the revision-ids of interest
 
2467
            required_trees = set()
 
2468
            for revision in revisions:
 
2469
                required_trees.add(revision.revision_id)
 
2470
                required_trees.update(revision.parent_ids[:1])
 
2471
 
 
2472
            # Get the matching filtered trees. Note that it's more
 
2473
            # efficient to pass filtered trees to changes_from() rather
 
2474
            # than doing the filtering afterwards. changes_from() could
 
2475
            # arguably do the filtering itself but it's path-based, not
 
2476
            # file-id based, so filtering before or afterwards is
 
2477
            # currently easier.
 
2478
            if specific_fileids is None:
 
2479
                trees = dict((t.get_revision_id(), t) for
 
2480
                    t in self.revision_trees(required_trees))
 
2481
            else:
 
2482
                trees = dict((t.get_revision_id(), t) for
 
2483
                    t in self._filtered_revision_trees(required_trees,
 
2484
                    specific_fileids))
 
2485
 
 
2486
            # Calculate the deltas
 
2487
            for revision in revisions:
 
2488
                if not revision.parent_ids:
 
2489
                    old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
2490
                else:
 
2491
                    old_tree = trees[revision.parent_ids[0]]
 
2492
                yield trees[revision.revision_id].changes_from(old_tree)
 
2493
 
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
        with self.lock_read():
 
2496
            r = self.get_revision(revision_id)
 
2497
            return list(self.get_deltas_for_revisions([r],
 
2498
                specific_fileids=specific_fileids))[0]
1561
2499
 
1562
 
    @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
        with self.lock_read():
 
2502
            inventories = self.iter_inventories(revision_ids)
 
2503
            for inv in inventories:
 
2504
                yield InventoryRevisionTree(self, inv, inv.revision_id)
1566
2505
 
1567
 
    @needs_read_lock
1568
2506
    def get_revision_reconcile(self, revision_id):
1569
 
        self._ensure_real()
1570
 
        return self._real_repository.get_revision_reconcile(revision_id)
 
2507
        with self.lock_read():
 
2508
            self._ensure_real()
 
2509
            return self._real_repository.get_revision_reconcile(revision_id)
1571
2510
 
1572
 
    @needs_read_lock
1573
2511
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1574
 
        self._ensure_real()
1575
 
        return self._real_repository.check(revision_ids=revision_ids,
1576
 
            callback_refs=callback_refs, check_repo=check_repo)
 
2512
        with self.lock_read():
 
2513
            self._ensure_real()
 
2514
            return self._real_repository.check(revision_ids=revision_ids,
 
2515
                callback_refs=callback_refs, check_repo=check_repo)
1577
2516
 
1578
2517
    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)
 
2518
        """Make a complete copy of the content in self into destination.
 
2519
 
 
2520
        This is a destructive operation! Do not use it on existing
 
2521
        repositories.
 
2522
        """
 
2523
        interrepo = _mod_repository.InterRepository.get(self, destination)
 
2524
        return interrepo.copy_content(revision_id)
1582
2525
 
1583
2526
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1584
2527
        # get a tarball of the remote repository, and copy from that into the
1585
2528
        # destination
1586
 
        from bzrlib import osutils
1587
2529
        import tarfile
1588
2530
        # TODO: Maybe a progress bar while streaming the tarball?
1589
 
        note("Copying repository content as tarball...")
 
2531
        note(gettext("Copying repository content as tarball..."))
1590
2532
        tar_file = self._get_tarball('bz2')
1591
2533
        if tar_file is None:
1592
2534
            return None
1596
2538
                mode='r|bz2')
1597
2539
            tmpdir = osutils.mkdtemp()
1598
2540
            try:
1599
 
                _extract_tar(tar, tmpdir)
1600
 
                tmp_bzrdir = BzrDir.open(tmpdir)
 
2541
                tar.extractall(tmpdir)
 
2542
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1601
2543
                tmp_repo = tmp_bzrdir.open_repository()
1602
2544
                tmp_repo.copy_content_into(destination, revision_id)
1603
2545
            finally:
1618
2560
        self._ensure_real()
1619
2561
        return self._real_repository.inventories
1620
2562
 
1621
 
    @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
        with self.lock_write():
 
2571
            path = self.controldir._path_for_remote_call(self._client)
 
2572
            try:
 
2573
                response, handler = self._call_with_body_bytes_expecting_body(
 
2574
                    'Repository.pack', (path, self._lock_token,
 
2575
                        str(clean_obsolete_packs)), body)
 
2576
            except errors.UnknownSmartMethod:
 
2577
                self._ensure_real()
 
2578
                return self._real_repository.pack(hint=hint,
 
2579
                    clean_obsolete_packs=clean_obsolete_packs)
 
2580
            handler.cancel_read_body()
 
2581
            if response != ('ok', ):
 
2582
                raise errors.UnexpectedSmartServerResponse(response)
1629
2583
 
1630
2584
    @property
1631
2585
    def revisions(self):
1632
2586
        """Decorate the real repository for now.
1633
2587
 
1634
 
        In the short term this should become a real object to intercept graph
1635
 
        lookups.
1636
 
 
1637
2588
        In the long term a full blown network facility is needed.
1638
2589
        """
1639
2590
        self._ensure_real()
1644
2595
            new_value_str = "True"
1645
2596
        else:
1646
2597
            new_value_str = "False"
1647
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2598
        path = self.controldir._path_for_remote_call(self._client)
1648
2599
        try:
1649
2600
            response = self._call(
1650
2601
                'Repository.set_make_working_trees', path, new_value_str)
1665
2616
        self._ensure_real()
1666
2617
        return self._real_repository.signatures
1667
2618
 
1668
 
    @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
        with self.lock_write():
 
2621
            testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2622
            plaintext = testament.as_short_text()
 
2623
            self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1672
2624
 
1673
2625
    @property
1674
2626
    def texts(self):
1680
2632
        self._ensure_real()
1681
2633
        return self._real_repository.texts
1682
2634
 
1683
 
    @needs_read_lock
1684
 
    def get_revisions(self, revision_ids):
1685
 
        self._ensure_real()
1686
 
        return self._real_repository.get_revisions(revision_ids)
 
2635
    def _iter_revisions_rpc(self, revision_ids):
 
2636
        body = "\n".join(revision_ids)
 
2637
        path = self.controldir._path_for_remote_call(self._client)
 
2638
        response_tuple, response_handler = (
 
2639
            self._call_with_body_bytes_expecting_body(
 
2640
            "Repository.iter_revisions", (path, ), body))
 
2641
        if response_tuple[0] != "ok":
 
2642
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2643
        serializer_format = response_tuple[1]
 
2644
        serializer = serializer_format_registry.get(serializer_format)
 
2645
        byte_stream = response_handler.read_streamed_body()
 
2646
        decompressor = zlib.decompressobj()
 
2647
        chunks = []
 
2648
        for bytes in byte_stream:
 
2649
            chunks.append(decompressor.decompress(bytes))
 
2650
            if decompressor.unused_data != "":
 
2651
                chunks.append(decompressor.flush())
 
2652
                yield serializer.read_revision_from_string("".join(chunks))
 
2653
                unused = decompressor.unused_data
 
2654
                decompressor = zlib.decompressobj()
 
2655
                chunks = [decompressor.decompress(unused)]
 
2656
        chunks.append(decompressor.flush())
 
2657
        text = "".join(chunks)
 
2658
        if text != "":
 
2659
            yield serializer.read_revision_from_string("".join(chunks))
 
2660
 
 
2661
    def iter_revisions(self, revision_ids):
 
2662
        for rev_id in revision_ids:
 
2663
            if not rev_id or not isinstance(rev_id, bytes):
 
2664
                raise errors.InvalidRevisionId(
 
2665
                    revision_id=rev_id, branch=self)
 
2666
        with self.lock_read():
 
2667
            try:
 
2668
                missing = set(revision_ids)
 
2669
                for rev in self._iter_revisions_rpc(revision_ids):
 
2670
                    missing.remove(rev.revision_id)
 
2671
                    yield (rev.revision_id, rev)
 
2672
                for fallback in self._fallback_repositories:
 
2673
                    if not missing:
 
2674
                        break
 
2675
                    for (revid, rev) in fallback.iter_revisions(missing):
 
2676
                        if rev is not None:
 
2677
                            yield (revid, rev)
 
2678
                            missing.remove(revid)
 
2679
                for revid in missing:
 
2680
                    yield (revid, None)
 
2681
            except errors.UnknownSmartMethod:
 
2682
                self._ensure_real()
 
2683
                for entry in self._real_repository.iter_revisions(revision_ids):
 
2684
                    yield entry
1687
2685
 
1688
2686
    def supports_rich_root(self):
1689
2687
        return self._format.rich_root_data
1690
2688
 
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
2689
    @property
1696
2690
    def _serializer(self):
1697
2691
        return self._format._serializer
1698
2692
 
1699
2693
    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)
 
2694
        with self.lock_write():
 
2695
            signature = gpg_strategy.sign(plaintext)
 
2696
            self.add_signature_text(revision_id, signature)
1703
2697
 
1704
2698
    def add_signature_text(self, revision_id, signature):
1705
 
        self._ensure_real()
1706
 
        return self._real_repository.add_signature_text(revision_id, signature)
 
2699
        if self._real_repository:
 
2700
            # If there is a real repository the write group will
 
2701
            # be in the real repository as well, so use that:
 
2702
            self._ensure_real()
 
2703
            return self._real_repository.add_signature_text(
 
2704
                revision_id, signature)
 
2705
        path = self.controldir._path_for_remote_call(self._client)
 
2706
        response, handler = self._call_with_body_bytes_expecting_body(
 
2707
            'Repository.add_signature_text', (path, self._lock_token,
 
2708
                revision_id) + tuple(self._write_group_tokens), signature)
 
2709
        handler.cancel_read_body()
 
2710
        self.refresh_data()
 
2711
        if response[0] != 'ok':
 
2712
            raise errors.UnexpectedSmartServerResponse(response)
 
2713
        self._write_group_tokens = response[1:]
1707
2714
 
1708
2715
    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)
 
2716
        path = self.controldir._path_for_remote_call(self._client)
 
2717
        try:
 
2718
            response = self._call('Repository.has_signature_for_revision_id',
 
2719
                path, revision_id)
 
2720
        except errors.UnknownSmartMethod:
 
2721
            self._ensure_real()
 
2722
            return self._real_repository.has_signature_for_revision_id(
 
2723
                revision_id)
 
2724
        if response[0] not in ('yes', 'no'):
 
2725
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2726
        if response[0] == 'yes':
 
2727
            return True
 
2728
        for fallback in self._fallback_repositories:
 
2729
            if fallback.has_signature_for_revision_id(revision_id):
 
2730
                return True
 
2731
        return False
 
2732
 
 
2733
    def verify_revision_signature(self, revision_id, gpg_strategy):
 
2734
        with self.lock_read():
 
2735
            if not self.has_signature_for_revision_id(revision_id):
 
2736
                return gpg.SIGNATURE_NOT_SIGNED, None
 
2737
            signature = self.get_signature_text(revision_id)
 
2738
 
 
2739
            testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2740
            plaintext = testament.as_short_text()
 
2741
 
 
2742
            return gpg_strategy.verify(signature, plaintext)
1711
2743
 
1712
2744
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1713
2745
        self._ensure_real()
1714
2746
        return self._real_repository.item_keys_introduced_by(revision_ids,
1715
2747
            _files_pb=_files_pb)
1716
2748
 
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
2749
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1723
2750
        self._ensure_real()
1724
2751
        return self._real_repository._find_inconsistent_revision_parents(
1732
2759
        providers = [self._unstacked_provider]
1733
2760
        if other is not None:
1734
2761
            providers.insert(0, other)
1735
 
        providers.extend(r._make_parents_provider() for r in
1736
 
                         self._fallback_repositories)
1737
 
        return graph.StackedParentsProvider(providers)
 
2762
        return graph.StackedParentsProvider(_LazyListJoin(
 
2763
            providers, self._fallback_repositories))
1738
2764
 
1739
2765
    def _serialise_search_recipe(self, recipe):
1740
2766
        """Serialise a graph search recipe.
1748
2774
        return '\n'.join((start_keys, stop_keys, count))
1749
2775
 
1750
2776
    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)]
 
2777
        parts = search_result.get_network_struct()
1757
2778
        return '\n'.join(parts)
1758
2779
 
1759
2780
    def autopack(self):
1760
 
        path = self.bzrdir._path_for_remote_call(self._client)
 
2781
        path = self.controldir._path_for_remote_call(self._client)
1761
2782
        try:
1762
2783
            response = self._call('PackRepository.autopack', path)
1763
2784
        except errors.UnknownSmartMethod:
1769
2790
            raise errors.UnexpectedSmartServerResponse(response)
1770
2791
 
1771
2792
 
1772
 
class RemoteStreamSink(repository.StreamSink):
 
2793
class RemoteStreamSink(vf_repository.StreamSink):
1773
2794
 
1774
2795
    def _insert_real(self, stream, src_format, resume_tokens):
1775
2796
        self.target_repo._ensure_real()
1791
2812
            lock_args = ()
1792
2813
        client = target._client
1793
2814
        medium = client._medium
1794
 
        path = target.bzrdir._path_for_remote_call(client)
 
2815
        path = target.controldir._path_for_remote_call(client)
1795
2816
        # Probe for the verb to use with an empty stream before sending the
1796
2817
        # real stream to it.  We do this both to avoid the risk of sending a
1797
2818
        # large request that is then rejected, and because we don't want to
1876
2897
        self._last_substream and self._last_stream so that the stream can be
1877
2898
        resumed by _resume_stream_with_vfs.
1878
2899
        """
1879
 
                    
 
2900
 
1880
2901
        stream_iter = iter(stream)
1881
2902
        for substream_kind, substream in stream_iter:
1882
2903
            if substream_kind == 'inventory-deltas':
1885
2906
                return
1886
2907
            else:
1887
2908
                yield substream_kind, substream
1888
 
            
1889
 
 
1890
 
class RemoteStreamSource(repository.StreamSource):
 
2909
 
 
2910
 
 
2911
class RemoteStreamSource(vf_repository.StreamSource):
1891
2912
    """Stream data from a remote server."""
1892
2913
 
1893
2914
    def get_stream(self, search):
1914
2935
 
1915
2936
    def _real_stream(self, repo, search):
1916
2937
        """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 
 
2938
 
 
2939
        This never called RemoteStreamSource.get_stream, and is a helper
 
2940
        for RemoteStreamSource._get_stream to allow getting a stream
1920
2941
        reliably whether fallback back because of old servers or trying
1921
2942
        to stream from a non-RemoteRepository (which the stacked support
1922
2943
        code will do).
1947
2968
            return self._real_stream(repo, search)
1948
2969
        client = repo._client
1949
2970
        medium = client._medium
1950
 
        path = repo.bzrdir._path_for_remote_call(client)
 
2971
        path = repo.controldir._path_for_remote_call(client)
1951
2972
        search_bytes = repo._serialise_search_result(search)
1952
2973
        args = (path, self.to_format.network_name())
1953
2974
        candidate_verbs = [
1954
2975
            ('Repository.get_stream_1.19', (1, 19)),
1955
2976
            ('Repository.get_stream', (1, 13))]
 
2977
 
1956
2978
        found_verb = False
1957
2979
        for verb, version in candidate_verbs:
1958
2980
            if medium._is_remote_before(version):
1962
2984
                    verb, args, search_bytes)
1963
2985
            except errors.UnknownSmartMethod:
1964
2986
                medium._remember_remote_is_before(version)
 
2987
            except errors.UnknownErrorFromSmartServer as e:
 
2988
                if isinstance(search, vf_search.EverythingResult):
 
2989
                    error_verb = e.error_from_smart_server.error_verb
 
2990
                    if error_verb == 'BadSearch':
 
2991
                        # Pre-2.4 servers don't support this sort of search.
 
2992
                        # XXX: perhaps falling back to VFS on BadSearch is a
 
2993
                        # good idea in general?  It might provide a little bit
 
2994
                        # of protection against client-side bugs.
 
2995
                        medium._remember_remote_is_before((2, 4))
 
2996
                        break
 
2997
                raise
1965
2998
            else:
1966
2999
                response_tuple, response_handler = response
1967
3000
                found_verb = True
1971
3004
        if response_tuple[0] != 'ok':
1972
3005
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
3006
        byte_stream = response_handler.read_streamed_body()
1974
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
3007
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
 
3008
            self._record_counter)
1975
3009
        if src_format.network_name() != repo._format.network_name():
1976
3010
            raise AssertionError(
1977
3011
                "Mismatched RemoteRepository and stream src %r, %r" % (
2021
3055
    """
2022
3056
 
2023
3057
    def __init__(self, bzrdir, _client):
2024
 
        self.bzrdir = bzrdir
 
3058
        self.controldir = bzrdir
2025
3059
        self._client = _client
2026
3060
        self._need_find_modes = True
2027
3061
        LockableFiles.__init__(
2038
3072
 
2039
3073
    def __init__(self, network_name=None):
2040
3074
        super(RemoteBranchFormat, self).__init__()
2041
 
        self._matchingbzrdir = RemoteBzrDirFormat()
2042
 
        self._matchingbzrdir.set_branch_format(self)
 
3075
        self._matchingcontroldir = RemoteBzrDirFormat()
 
3076
        self._matchingcontroldir.set_branch_format(self)
2043
3077
        self._custom_format = None
2044
3078
        self._network_name = network_name
2045
3079
 
2049
3083
 
2050
3084
    def _ensure_real(self):
2051
3085
        if self._custom_format is None:
2052
 
            self._custom_format = branch.network_format_registry.get(
2053
 
                self._network_name)
 
3086
            try:
 
3087
                self._custom_format = branch.network_format_registry.get(
 
3088
                    self._network_name)
 
3089
            except KeyError:
 
3090
                raise errors.UnknownFormatError(kind='branch',
 
3091
                    format=self._network_name)
2054
3092
 
2055
3093
    def get_format_description(self):
2056
3094
        self._ensure_real()
2059
3097
    def network_name(self):
2060
3098
        return self._network_name
2061
3099
 
2062
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2063
 
        return a_bzrdir.open_branch(name=name, 
 
3100
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
 
3101
        return a_controldir.open_branch(name=name, 
2064
3102
            ignore_fallbacks=ignore_fallbacks)
2065
3103
 
2066
 
    def _vfs_initialize(self, a_bzrdir, name):
 
3104
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
 
3105
                        repository=None):
2067
3106
        # Initialisation when using a local bzrdir object, or a non-vfs init
2068
3107
        # method is not available on the server.
2069
3108
        # self._custom_format is always set - the start of initialize ensures
2070
3109
        # that.
2071
 
        if isinstance(a_bzrdir, RemoteBzrDir):
2072
 
            a_bzrdir._ensure_real()
2073
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2074
 
                name)
 
3110
        if isinstance(a_controldir, RemoteBzrDir):
 
3111
            a_controldir._ensure_real()
 
3112
            result = self._custom_format.initialize(a_controldir._real_bzrdir,
 
3113
                name=name, append_revisions_only=append_revisions_only,
 
3114
                repository=repository)
2075
3115
        else:
2076
3116
            # 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
 
3117
            result = self._custom_format.initialize(a_controldir, name=name,
 
3118
                append_revisions_only=append_revisions_only,
 
3119
                repository=repository)
 
3120
        if (isinstance(a_controldir, RemoteBzrDir) and
2079
3121
            not isinstance(result, RemoteBranch)):
2080
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
3122
            result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
2081
3123
                                  name=name)
2082
3124
        return result
2083
3125
 
2084
 
    def initialize(self, a_bzrdir, name=None):
 
3126
    def initialize(self, a_controldir, name=None, repository=None,
 
3127
                   append_revisions_only=None):
 
3128
        if name is None:
 
3129
            name = a_controldir._get_selected_branch()
2085
3130
        # 1) get the network name to use.
2086
3131
        if self._custom_format:
2087
3132
            network_name = self._custom_format.network_name()
2088
3133
        else:
2089
 
            # Select the current bzrlib default and ask for that.
2090
 
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
3134
            # Select the current breezy default and ask for that.
 
3135
            reference_bzrdir_format = controldir.format_registry.get('default')()
2091
3136
            reference_format = reference_bzrdir_format.get_branch_format()
2092
3137
            self._custom_format = reference_format
2093
3138
            network_name = reference_format.network_name()
2094
3139
        # 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
 
3140
        if not isinstance(a_controldir, RemoteBzrDir):
 
3141
            return self._vfs_initialize(a_controldir, name=name,
 
3142
                append_revisions_only=append_revisions_only,
 
3143
                repository=repository)
 
3144
        medium = a_controldir._client._medium
2098
3145
        if medium._is_remote_before((1, 13)):
2099
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3146
            return self._vfs_initialize(a_controldir, name=name,
 
3147
                append_revisions_only=append_revisions_only,
 
3148
                repository=repository)
2100
3149
        # Creating on a remote bzr dir.
2101
3150
        # 2) try direct creation via RPC
2102
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2103
 
        if name is not None:
 
3151
        path = a_controldir._path_for_remote_call(a_controldir._client)
 
3152
        if name != "":
2104
3153
            # XXX JRV20100304: Support creating colocated branches
2105
3154
            raise errors.NoColocatedBranchSupport(self)
2106
3155
        verb = 'BzrDir.create_branch'
2107
3156
        try:
2108
 
            response = a_bzrdir._call(verb, path, network_name)
 
3157
            response = a_controldir._call(verb, path, network_name)
2109
3158
        except errors.UnknownSmartMethod:
2110
3159
            # Fallback - use vfs methods
2111
3160
            medium._remember_remote_is_before((1, 13))
2112
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3161
            return self._vfs_initialize(a_controldir, name=name,
 
3162
                    append_revisions_only=append_revisions_only,
 
3163
                    repository=repository)
2113
3164
        if response[0] != 'ok':
2114
3165
            raise errors.UnexpectedSmartServerResponse(response)
2115
3166
        # Turn the response into a RemoteRepository object.
2116
3167
        format = RemoteBranchFormat(network_name=response[1])
2117
3168
        repo_format = response_tuple_to_repo_format(response[3:])
2118
 
        if response[2] == '':
2119
 
            repo_bzrdir = a_bzrdir
 
3169
        repo_path = response[2]
 
3170
        if repository is not None:
 
3171
            remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
 
3172
            url_diff = urlutils.relative_url(repository.user_url,
 
3173
                    remote_repo_url)
 
3174
            if url_diff != '.':
 
3175
                raise AssertionError(
 
3176
                    'repository.user_url %r does not match URL from server '
 
3177
                    'response (%r + %r)'
 
3178
                    % (repository.user_url, a_controldir.user_url, repo_path))
 
3179
            remote_repo = repository
2120
3180
        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,
 
3181
            if repo_path == '':
 
3182
                repo_bzrdir = a_controldir
 
3183
            else:
 
3184
                repo_bzrdir = RemoteBzrDir(
 
3185
                    a_controldir.root_transport.clone(repo_path), a_controldir._format,
 
3186
                    a_controldir._client)
 
3187
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
3188
        remote_branch = RemoteBranch(a_controldir, remote_repo,
2126
3189
            format=format, setup_stacking=False, name=name)
 
3190
        if append_revisions_only:
 
3191
            remote_branch.set_append_revisions_only(append_revisions_only)
2127
3192
        # XXX: We know this is a new branch, so it must have revno 0, revid
2128
3193
        # NULL_REVISION. Creating the branch locked would make this be unable
2129
3194
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2148
3213
        self._ensure_real()
2149
3214
        return self._custom_format.supports_set_append_revisions_only()
2150
3215
 
 
3216
    def _use_default_local_heads_to_fetch(self):
 
3217
        # If the branch format is a metadir format *and* its heads_to_fetch
 
3218
        # implementation is not overridden vs the base class, we can use the
 
3219
        # base class logic rather than use the heads_to_fetch RPC.  This is
 
3220
        # usually cheaper in terms of net round trips, as the last-revision and
 
3221
        # tags info fetched is cached and would be fetched anyway.
 
3222
        self._ensure_real()
 
3223
        if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
 
3224
            branch_class = self._custom_format._branch_class()
 
3225
            heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
 
3226
            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
 
3227
                return True
 
3228
        return False
 
3229
 
 
3230
 
 
3231
class RemoteBranchStore(_mod_config.IniFileStore):
 
3232
    """Branch store which attempts to use HPSS calls to retrieve branch store.
 
3233
 
 
3234
    Note that this is specific to bzr-based formats.
 
3235
    """
 
3236
 
 
3237
    def __init__(self, branch):
 
3238
        super(RemoteBranchStore, self).__init__()
 
3239
        self.branch = branch
 
3240
        self.id = "branch"
 
3241
        self._real_store = None
 
3242
 
 
3243
    def external_url(self):
 
3244
        return urlutils.join(self.branch.user_url, 'branch.conf')
 
3245
 
 
3246
    def _load_content(self):
 
3247
        path = self.branch._remote_path()
 
3248
        try:
 
3249
            response, handler = self.branch._call_expecting_body(
 
3250
                'Branch.get_config_file', path)
 
3251
        except errors.UnknownSmartMethod:
 
3252
            self._ensure_real()
 
3253
            return self._real_store._load_content()
 
3254
        if len(response) and response[0] != 'ok':
 
3255
            raise errors.UnexpectedSmartServerResponse(response)
 
3256
        return handler.read_body_bytes()
 
3257
 
 
3258
    def _save_content(self, content):
 
3259
        path = self.branch._remote_path()
 
3260
        try:
 
3261
            response, handler = self.branch._call_with_body_bytes_expecting_body(
 
3262
                'Branch.put_config_file', (path,
 
3263
                    self.branch._lock_token, self.branch._repo_lock_token),
 
3264
                content)
 
3265
        except errors.UnknownSmartMethod:
 
3266
            self._ensure_real()
 
3267
            return self._real_store._save_content(content)
 
3268
        handler.cancel_read_body()
 
3269
        if response != ('ok', ):
 
3270
            raise errors.UnexpectedSmartServerResponse(response)
 
3271
 
 
3272
    def _ensure_real(self):
 
3273
        self.branch._ensure_real()
 
3274
        if self._real_store is None:
 
3275
            self._real_store = _mod_config.BranchStore(self.branch)
 
3276
 
2151
3277
 
2152
3278
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2153
3279
    """Branch stored on a server accessed by HPSS RPC.
2156
3282
    """
2157
3283
 
2158
3284
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
 
        _client=None, format=None, setup_stacking=True, name=None):
 
3285
        _client=None, format=None, setup_stacking=True, name=None,
 
3286
        possible_transports=None):
2160
3287
        """Create a RemoteBranch instance.
2161
3288
 
2162
3289
        :param real_branch: An optional local implementation of the branch
2173
3300
        # We intentionally don't call the parent class's __init__, because it
2174
3301
        # will try to assign to self.tags, which is a property in this subclass.
2175
3302
        # And the parent's __init__ doesn't do much anyway.
2176
 
        self.bzrdir = remote_bzrdir
 
3303
        self.controldir = remote_bzrdir
 
3304
        self.name = name
2177
3305
        if _client is not None:
2178
3306
            self._client = _client
2179
3307
        else:
2191
3319
            self._real_branch.repository = self.repository
2192
3320
        else:
2193
3321
            self._real_branch = None
2194
 
        # Fill out expected attributes of branch for bzrlib API users.
 
3322
        # Fill out expected attributes of branch for breezy API users.
2195
3323
        self._clear_cached_state()
2196
3324
        # TODO: deprecate self.base in favor of user_url
2197
 
        self.base = self.bzrdir.user_url
 
3325
        self.base = self.controldir.user_url
2198
3326
        self._name = name
2199
3327
        self._control_files = None
2200
3328
        self._lock_mode = None
2202
3330
        self._repo_lock_token = None
2203
3331
        self._lock_count = 0
2204
3332
        self._leave_lock = False
 
3333
        self.conf_store = None
2205
3334
        # Setup a format: note that we cannot call _ensure_real until all the
2206
3335
        # attributes above are set: This code cannot be moved higher up in this
2207
3336
        # function.
2227
3356
            hook(self)
2228
3357
        self._is_stacked = False
2229
3358
        if setup_stacking:
2230
 
            self._setup_stacking()
 
3359
            self._setup_stacking(possible_transports)
2231
3360
 
2232
 
    def _setup_stacking(self):
 
3361
    def _setup_stacking(self, possible_transports):
2233
3362
        # configure stacking into the remote repository, by reading it from
2234
3363
        # the vfs branch.
2235
3364
        try:
2236
3365
            fallback_url = self.get_stacked_on_url()
2237
 
        except (errors.NotStacked, errors.UnstackableBranchFormat,
2238
 
            errors.UnstackableRepositoryFormat), e:
 
3366
        except (errors.NotStacked, branch.UnstackableBranchFormat,
 
3367
            errors.UnstackableRepositoryFormat) as e:
2239
3368
            return
2240
3369
        self._is_stacked = True
2241
 
        self._activate_fallback_location(fallback_url)
 
3370
        if possible_transports is None:
 
3371
            possible_transports = []
 
3372
        else:
 
3373
            possible_transports = list(possible_transports)
 
3374
        possible_transports.append(self.controldir.root_transport)
 
3375
        self._activate_fallback_location(fallback_url,
 
3376
            possible_transports=possible_transports)
2242
3377
 
2243
3378
    def _get_config(self):
2244
3379
        return RemoteBranchConfig(self)
2245
3380
 
 
3381
    def _get_config_store(self):
 
3382
        if self.conf_store is None:
 
3383
            self.conf_store =  RemoteBranchStore(self)
 
3384
        return self.conf_store
 
3385
 
 
3386
    def store_uncommitted(self, creator):
 
3387
        self._ensure_real()
 
3388
        return self._real_branch.store_uncommitted(creator)
 
3389
 
 
3390
    def get_unshelver(self, tree):
 
3391
        self._ensure_real()
 
3392
        return self._real_branch.get_unshelver(tree)
 
3393
 
2246
3394
    def _get_real_transport(self):
2247
3395
        # if we try vfs access, return the real branch's vfs transport
2248
3396
        self._ensure_real()
2264
3412
            if not vfs.vfs_enabled():
2265
3413
                raise AssertionError('smart server vfs must be enabled '
2266
3414
                    'to use vfs implementation')
2267
 
            self.bzrdir._ensure_real()
2268
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
3415
            self.controldir._ensure_real()
 
3416
            self._real_branch = self.controldir._real_bzrdir.open_branch(
2269
3417
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
 
3418
            # The remote branch and the real branch shares the same store. If
 
3419
            # we don't, there will always be cases where one of the stores
 
3420
            # doesn't see an update made on the other.
 
3421
            self._real_branch.conf_store = self.conf_store
2270
3422
            if self.repository._real_repository is None:
2271
3423
                # Give the remote repository the matching real repo.
2272
3424
                real_repo = self._real_branch.repository
2308
3460
        # because it triggers an _ensure_real that we otherwise might not need.
2309
3461
        if self._control_files is None:
2310
3462
            self._control_files = RemoteBranchLockableFiles(
2311
 
                self.bzrdir, self._client)
 
3463
                self.controldir, self._client)
2312
3464
        return self._control_files
2313
3465
 
2314
 
    def _get_checkout_format(self):
2315
 
        self._ensure_real()
2316
 
        return self._real_branch._get_checkout_format()
2317
 
 
2318
3466
    def get_physical_lock_status(self):
2319
3467
        """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()
 
3468
        try:
 
3469
            response = self._client.call('Branch.get_physical_lock_status',
 
3470
                self._remote_path())
 
3471
        except errors.UnknownSmartMethod:
 
3472
            self._ensure_real()
 
3473
            return self._real_branch.get_physical_lock_status()
 
3474
        if response[0] not in ('yes', 'no'):
 
3475
            raise errors.UnexpectedSmartServerResponse(response)
 
3476
        return (response[0] == 'yes')
2323
3477
 
2324
3478
    def get_stacked_on_url(self):
2325
3479
        """Get the URL this branch is stacked against.
2335
3489
            # self._translate_error, so we can't use self._call either.
2336
3490
            response = self._client.call('Branch.get_stacked_on_url',
2337
3491
                self._remote_path())
2338
 
        except errors.ErrorFromSmartServer, err:
 
3492
        except errors.ErrorFromSmartServer as err:
2339
3493
            # there may not be a repository yet, so we can't call through
2340
3494
            # its _translate_error
2341
3495
            _translate_error(err, branch=self)
2342
 
        except errors.UnknownSmartMethod, err:
 
3496
        except errors.UnknownSmartMethod as err:
2343
3497
            self._ensure_real()
2344
3498
            return self._real_branch.get_stacked_on_url()
2345
3499
        if response[0] != 'ok':
2348
3502
 
2349
3503
    def set_stacked_on_url(self, url):
2350
3504
        branch.Branch.set_stacked_on_url(self, url)
 
3505
        # We need the stacked_on_url to be visible both locally (to not query
 
3506
        # it repeatedly) and remotely (so smart verbs can get it server side)
 
3507
        # Without the following line,
 
3508
        # breezy.tests.per_branch.test_create_clone.TestCreateClone
 
3509
        # .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
 
3510
        # fails for remote branches -- vila 2012-01-04
 
3511
        self.conf_store.save_changes()
2351
3512
        if not url:
2352
3513
            self._is_stacked = False
2353
3514
        else:
2354
3515
            self._is_stacked = True
2355
 
        
 
3516
 
2356
3517
    def _vfs_get_tags_bytes(self):
2357
3518
        self._ensure_real()
2358
3519
        return self._real_branch._get_tags_bytes()
2359
3520
 
2360
3521
    def _get_tags_bytes(self):
 
3522
        with self.lock_read():
 
3523
            if self._tags_bytes is None:
 
3524
                self._tags_bytes = self._get_tags_bytes_via_hpss()
 
3525
            return self._tags_bytes
 
3526
 
 
3527
    def _get_tags_bytes_via_hpss(self):
2361
3528
        medium = self._client._medium
2362
3529
        if medium._is_remote_before((1, 13)):
2363
3530
            return self._vfs_get_tags_bytes()
2373
3540
        return self._real_branch._set_tags_bytes(bytes)
2374
3541
 
2375
3542
    def _set_tags_bytes(self, bytes):
 
3543
        if self.is_locked():
 
3544
            self._tags_bytes = bytes
2376
3545
        medium = self._client._medium
2377
3546
        if medium._is_remote_before((1, 18)):
2378
3547
            self._vfs_set_tags_bytes(bytes)
2387
3556
            self._vfs_set_tags_bytes(bytes)
2388
3557
 
2389
3558
    def lock_read(self):
 
3559
        """Lock the branch for read operations.
 
3560
 
 
3561
        :return: A breezy.lock.LogicalLockResult.
 
3562
        """
2390
3563
        self.repository.lock_read()
2391
3564
        if not self._lock_mode:
2392
3565
            self._note_lock('r')
2396
3569
                self._real_branch.lock_read()
2397
3570
        else:
2398
3571
            self._lock_count += 1
 
3572
        return lock.LogicalLockResult(self.unlock)
2399
3573
 
2400
3574
    def _remote_lock_write(self, token):
2401
3575
        if token is None:
2402
3576
            branch_token = repo_token = ''
2403
3577
        else:
2404
3578
            branch_token = token
2405
 
            repo_token = self.repository.lock_write()
 
3579
            repo_token = self.repository.lock_write().repository_token
2406
3580
            self.repository.unlock()
2407
3581
        err_context = {'token': token}
2408
 
        response = self._call(
2409
 
            'Branch.lock_write', self._remote_path(), branch_token,
2410
 
            repo_token or '', **err_context)
 
3582
        try:
 
3583
            response = self._call(
 
3584
                'Branch.lock_write', self._remote_path(), branch_token,
 
3585
                repo_token or '', **err_context)
 
3586
        except errors.LockContention as e:
 
3587
            # The LockContention from the server doesn't have any
 
3588
            # information about the lock_url. We re-raise LockContention
 
3589
            # with valid lock_url.
 
3590
            raise errors.LockContention('(remote lock)',
 
3591
                self.repository.base.split('.bzr/')[0])
2411
3592
        if response[0] != 'ok':
2412
3593
            raise errors.UnexpectedSmartServerResponse(response)
2413
3594
        ok, branch_token, repo_token = response
2434
3615
            self._lock_mode = 'w'
2435
3616
            self._lock_count = 1
2436
3617
        elif self._lock_mode == 'r':
2437
 
            raise errors.ReadOnlyTransaction
 
3618
            raise errors.ReadOnlyError(self)
2438
3619
        else:
2439
3620
            if token is not None:
2440
3621
                # A token was given to lock_write, and we're relocking, so
2445
3626
            self._lock_count += 1
2446
3627
            # Re-lock the repository too.
2447
3628
            self.repository.lock_write(self._repo_lock_token)
2448
 
        return self._lock_token or None
 
3629
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
2449
3630
 
2450
3631
    def _unlock(self, branch_token, repo_token):
2451
3632
        err_context = {'token': str((branch_token, repo_token))}
2461
3642
        try:
2462
3643
            self._lock_count -= 1
2463
3644
            if not self._lock_count:
 
3645
                if self.conf_store is not None:
 
3646
                    self.conf_store.save_changes()
2464
3647
                self._clear_cached_state()
2465
3648
                mode = self._lock_mode
2466
3649
                self._lock_mode = None
2489
3672
            self.repository.unlock()
2490
3673
 
2491
3674
    def break_lock(self):
2492
 
        self._ensure_real()
2493
 
        return self._real_branch.break_lock()
 
3675
        try:
 
3676
            response = self._call(
 
3677
                'Branch.break_lock', self._remote_path())
 
3678
        except errors.UnknownSmartMethod:
 
3679
            self._ensure_real()
 
3680
            return self._real_branch.break_lock()
 
3681
        if response != ('ok',):
 
3682
            raise errors.UnexpectedSmartServerResponse(response)
2494
3683
 
2495
3684
    def leave_lock_in_place(self):
2496
3685
        if not self._lock_token:
2502
3691
            raise NotImplementedError(self.dont_leave_lock_in_place)
2503
3692
        self._leave_lock = False
2504
3693
 
2505
 
    @needs_read_lock
2506
3694
    def get_rev_id(self, revno, history=None):
2507
3695
        if revno == 0:
2508
3696
            return _mod_revision.NULL_REVISION
2509
 
        last_revision_info = self.last_revision_info()
2510
 
        ok, result = self.repository.get_rev_id_for_revno(
2511
 
            revno, last_revision_info)
2512
 
        if ok:
2513
 
            return result
2514
 
        missing_parent = result[1]
2515
 
        # Either the revision named by the server is missing, or its parent
2516
 
        # is.  Call get_parent_map to determine which, so that we report a
2517
 
        # useful error.
2518
 
        parent_map = self.repository.get_parent_map([missing_parent])
2519
 
        if missing_parent in parent_map:
2520
 
            missing_parent = parent_map[missing_parent]
2521
 
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
3697
        with self.lock_read():
 
3698
            last_revision_info = self.last_revision_info()
 
3699
            ok, result = self.repository.get_rev_id_for_revno(
 
3700
                revno, last_revision_info)
 
3701
            if ok:
 
3702
                return result
 
3703
            missing_parent = result[1]
 
3704
            # Either the revision named by the server is missing, or its parent
 
3705
            # is.  Call get_parent_map to determine which, so that we report a
 
3706
            # useful error.
 
3707
            parent_map = self.repository.get_parent_map([missing_parent])
 
3708
            if missing_parent in parent_map:
 
3709
                missing_parent = parent_map[missing_parent]
 
3710
            raise errors.RevisionNotPresent(missing_parent, self.repository)
2522
3711
 
2523
 
    def _last_revision_info(self):
 
3712
    def _read_last_revision_info(self):
2524
3713
        response = self._call('Branch.last_revision_info', self._remote_path())
2525
3714
        if response[0] != 'ok':
2526
3715
            raise SmartProtocolError('unexpected response code %s' % (response,))
2543
3732
        return result
2544
3733
 
2545
3734
    def _remote_path(self):
2546
 
        return self.bzrdir._path_for_remote_call(self._client)
 
3735
        return self.controldir._path_for_remote_call(self._client)
2547
3736
 
2548
3737
    def _set_last_revision_descendant(self, revision_id, other_branch,
2549
3738
            allow_diverged=False, allow_overwrite_descendant=False):
2589
3778
            raise errors.UnexpectedSmartServerResponse(response)
2590
3779
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2591
3780
 
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
3781
    def _get_parent_location(self):
2607
3782
        medium = self._client._medium
2608
3783
        if medium._is_remote_before((1, 13)):
2629
3804
            return self._vfs_set_parent_location(url)
2630
3805
        try:
2631
3806
            call_url = url or ''
2632
 
            if type(call_url) is not str:
 
3807
            if not isinstance(call_url, str):
2633
3808
                raise AssertionError('url must be a str or None (%s)' % url)
2634
3809
            response = self._call('Branch.set_parent_location',
2635
3810
                self._remote_path(), self._lock_token, self._repo_lock_token,
2644
3819
        self._ensure_real()
2645
3820
        return self._real_branch._set_parent_location(url)
2646
3821
 
2647
 
    @needs_write_lock
2648
3822
    def pull(self, source, overwrite=False, stop_revision=None,
2649
3823
             **kwargs):
2650
 
        self._clear_cached_state_of_remote_branch_only()
2651
 
        self._ensure_real()
2652
 
        return self._real_branch.pull(
2653
 
            source, overwrite=overwrite, stop_revision=stop_revision,
2654
 
            _override_hook_target=self, **kwargs)
2655
 
 
2656
 
    @needs_read_lock
2657
 
    def push(self, target, overwrite=False, stop_revision=None):
2658
 
        self._ensure_real()
2659
 
        return self._real_branch.push(
2660
 
            target, overwrite=overwrite, stop_revision=stop_revision,
2661
 
            _override_hook_source_branch=self)
 
3824
        with self.lock_write():
 
3825
            self._clear_cached_state_of_remote_branch_only()
 
3826
            self._ensure_real()
 
3827
            return self._real_branch.pull(
 
3828
                source, overwrite=overwrite, stop_revision=stop_revision,
 
3829
                _override_hook_target=self, **kwargs)
 
3830
 
 
3831
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
 
3832
        with self.lock_read():
 
3833
            self._ensure_real()
 
3834
            return self._real_branch.push(
 
3835
                target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
 
3836
                _override_hook_source_branch=self)
 
3837
 
 
3838
    def peek_lock_mode(self):
 
3839
        return self._lock_mode
2662
3840
 
2663
3841
    def is_locked(self):
2664
3842
        return self._lock_count >= 1
2665
3843
 
2666
 
    @needs_read_lock
 
3844
    def revision_id_to_dotted_revno(self, revision_id):
 
3845
        """Given a revision id, return its dotted revno.
 
3846
 
 
3847
        :return: a tuple like (1,) or (400,1,3).
 
3848
        """
 
3849
        with self.lock_read():
 
3850
            try:
 
3851
                response = self._call('Branch.revision_id_to_revno',
 
3852
                    self._remote_path(), revision_id)
 
3853
            except errors.UnknownSmartMethod:
 
3854
                self._ensure_real()
 
3855
                return self._real_branch.revision_id_to_dotted_revno(revision_id)
 
3856
            if response[0] == 'ok':
 
3857
                return tuple([int(x) for x in response[1:]])
 
3858
            else:
 
3859
                raise errors.UnexpectedSmartServerResponse(response)
 
3860
 
2667
3861
    def revision_id_to_revno(self, revision_id):
2668
 
        self._ensure_real()
2669
 
        return self._real_branch.revision_id_to_revno(revision_id)
2670
 
 
2671
 
    @needs_write_lock
 
3862
        """Given a revision id on the branch mainline, return its revno.
 
3863
 
 
3864
        :return: an integer
 
3865
        """
 
3866
        with self.lock_read():
 
3867
            try:
 
3868
                response = self._call('Branch.revision_id_to_revno',
 
3869
                    self._remote_path(), revision_id)
 
3870
            except errors.UnknownSmartMethod:
 
3871
                self._ensure_real()
 
3872
                return self._real_branch.revision_id_to_revno(revision_id)
 
3873
            if response[0] == 'ok':
 
3874
                if len(response) == 2:
 
3875
                    return int(response[1])
 
3876
                raise NoSuchRevision(self, revision_id)
 
3877
            else:
 
3878
                raise errors.UnexpectedSmartServerResponse(response)
 
3879
 
2672
3880
    def set_last_revision_info(self, revno, revision_id):
2673
 
        # XXX: These should be returned by the set_last_revision_info verb
2674
 
        old_revno, old_revid = self.last_revision_info()
2675
 
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
 
        revision_id = ensure_null(revision_id)
2677
 
        try:
2678
 
            response = self._call('Branch.set_last_revision_info',
2679
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
2680
 
                str(revno), revision_id)
2681
 
        except errors.UnknownSmartMethod:
2682
 
            self._ensure_real()
2683
 
            self._clear_cached_state_of_remote_branch_only()
2684
 
            self._real_branch.set_last_revision_info(revno, revision_id)
2685
 
            self._last_revision_info_cache = revno, revision_id
2686
 
            return
2687
 
        if response == ('ok',):
2688
 
            self._clear_cached_state()
2689
 
            self._last_revision_info_cache = revno, revision_id
2690
 
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2691
 
            # Update the _real_branch's cache too.
2692
 
            if self._real_branch is not None:
2693
 
                cache = self._last_revision_info_cache
2694
 
                self._real_branch._last_revision_info_cache = cache
2695
 
        else:
2696
 
            raise errors.UnexpectedSmartServerResponse(response)
 
3881
        with self.lock_write():
 
3882
            # XXX: These should be returned by the set_last_revision_info verb
 
3883
            old_revno, old_revid = self.last_revision_info()
 
3884
            self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
3885
            if not revision_id or not isinstance(revision_id, bytes):
 
3886
                raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
 
3887
            try:
 
3888
                response = self._call('Branch.set_last_revision_info',
 
3889
                    self._remote_path(), self._lock_token, self._repo_lock_token,
 
3890
                    str(revno), revision_id)
 
3891
            except errors.UnknownSmartMethod:
 
3892
                self._ensure_real()
 
3893
                self._clear_cached_state_of_remote_branch_only()
 
3894
                self._real_branch.set_last_revision_info(revno, revision_id)
 
3895
                self._last_revision_info_cache = revno, revision_id
 
3896
                return
 
3897
            if response == ('ok',):
 
3898
                self._clear_cached_state()
 
3899
                self._last_revision_info_cache = revno, revision_id
 
3900
                self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
3901
                # Update the _real_branch's cache too.
 
3902
                if self._real_branch is not None:
 
3903
                    cache = self._last_revision_info_cache
 
3904
                    self._real_branch._last_revision_info_cache = cache
 
3905
            else:
 
3906
                raise errors.UnexpectedSmartServerResponse(response)
2697
3907
 
2698
 
    @needs_write_lock
2699
3908
    def generate_revision_history(self, revision_id, last_rev=None,
2700
3909
                                  other_branch=None):
2701
 
        medium = self._client._medium
2702
 
        if not medium._is_remote_before((1, 6)):
2703
 
            # Use a smart method for 1.6 and above servers
2704
 
            try:
2705
 
                self._set_last_revision_descendant(revision_id, other_branch,
2706
 
                    allow_diverged=True, allow_overwrite_descendant=True)
2707
 
                return
2708
 
            except errors.UnknownSmartMethod:
2709
 
                medium._remember_remote_is_before((1, 6))
2710
 
        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))
 
3910
        with self.lock_write():
 
3911
            medium = self._client._medium
 
3912
            if not medium._is_remote_before((1, 6)):
 
3913
                # Use a smart method for 1.6 and above servers
 
3914
                try:
 
3915
                    self._set_last_revision_descendant(revision_id, other_branch,
 
3916
                        allow_diverged=True, allow_overwrite_descendant=True)
 
3917
                    return
 
3918
                except errors.UnknownSmartMethod:
 
3919
                    medium._remember_remote_is_before((1, 6))
 
3920
            self._clear_cached_state_of_remote_branch_only()
 
3921
            graph = self.repository.get_graph()
 
3922
            (last_revno, last_revid) = self.last_revision_info()
 
3923
            known_revision_ids = [
 
3924
                (last_revid, last_revno),
 
3925
                (_mod_revision.NULL_REVISION, 0),
 
3926
                ]
 
3927
            if last_rev is not None:
 
3928
                if not graph.is_ancestor(last_rev, revision_id):
 
3929
                    # our previous tip is not merged into stop_revision
 
3930
                    raise errors.DivergedBranches(self, other_branch)
 
3931
            revno = graph.find_distance_to_null(revision_id, known_revision_ids)
 
3932
            self.set_last_revision_info(revno, revision_id)
2713
3933
 
2714
3934
    def set_push_location(self, location):
 
3935
        self._set_config_location('push_location', location)
 
3936
 
 
3937
    def heads_to_fetch(self):
 
3938
        if self._format._use_default_local_heads_to_fetch():
 
3939
            # We recognise this format, and its heads-to-fetch implementation
 
3940
            # is the default one (tip + tags).  In this case it's cheaper to
 
3941
            # just use the default implementation rather than a special RPC as
 
3942
            # the tip and tags data is cached.
 
3943
            return branch.Branch.heads_to_fetch(self)
 
3944
        medium = self._client._medium
 
3945
        if medium._is_remote_before((2, 4)):
 
3946
            return self._vfs_heads_to_fetch()
 
3947
        try:
 
3948
            return self._rpc_heads_to_fetch()
 
3949
        except errors.UnknownSmartMethod:
 
3950
            medium._remember_remote_is_before((2, 4))
 
3951
            return self._vfs_heads_to_fetch()
 
3952
 
 
3953
    def _rpc_heads_to_fetch(self):
 
3954
        response = self._call('Branch.heads_to_fetch', self._remote_path())
 
3955
        if len(response) != 2:
 
3956
            raise errors.UnexpectedSmartServerResponse(response)
 
3957
        must_fetch, if_present_fetch = response
 
3958
        return set(must_fetch), set(if_present_fetch)
 
3959
 
 
3960
    def _vfs_heads_to_fetch(self):
2715
3961
        self._ensure_real()
2716
 
        return self._real_branch.set_push_location(location)
 
3962
        return self._real_branch.heads_to_fetch()
2717
3963
 
2718
3964
 
2719
3965
class RemoteConfig(object):
2721
3967
 
2722
3968
    It is a low-level object that considers config data to be name/value pairs
2723
3969
    that may be associated with a section. Assigning meaning to the these
2724
 
    values is done at higher levels like bzrlib.config.TreeConfig.
 
3970
    values is done at higher levels like breezy.config.TreeConfig.
2725
3971
    """
2726
3972
 
2727
3973
    def get_option(self, name, section=None, default=None):
2734
3980
        """
2735
3981
        try:
2736
3982
            configobj = self._get_configobj()
 
3983
            section_obj = None
2737
3984
            if section is None:
2738
3985
                section_obj = configobj
2739
3986
            else:
2740
3987
                try:
2741
3988
                    section_obj = configobj[section]
2742
3989
                except KeyError:
2743
 
                    return default
2744
 
            return section_obj.get(name, default)
 
3990
                    pass
 
3991
            if section_obj is None:
 
3992
                value = default
 
3993
            else:
 
3994
                value = section_obj.get(name, default)
2745
3995
        except errors.UnknownSmartMethod:
2746
 
            return self._vfs_get_option(name, section, default)
 
3996
            value = self._vfs_get_option(name, section, default)
 
3997
        for hook in _mod_config.OldConfigHooks['get']:
 
3998
            hook(self, name, value)
 
3999
        return value
2747
4000
 
2748
4001
    def _response_to_configobj(self, response):
2749
4002
        if len(response[0]) and response[0][0] != 'ok':
2750
4003
            raise errors.UnexpectedSmartServerResponse(response)
2751
4004
        lines = response[1].read_body_bytes().splitlines()
2752
 
        return config.ConfigObj(lines, encoding='utf-8')
 
4005
        conf = _mod_config.ConfigObj(lines, encoding='utf-8')
 
4006
        for hook in _mod_config.OldConfigHooks['load']:
 
4007
            hook(self)
 
4008
        return conf
2753
4009
 
2754
4010
 
2755
4011
class RemoteBranchConfig(RemoteConfig):
2774
4030
        medium = self._branch._client._medium
2775
4031
        if medium._is_remote_before((1, 14)):
2776
4032
            return self._vfs_set_option(value, name, section)
 
4033
        if isinstance(value, dict):
 
4034
            if medium._is_remote_before((2, 2)):
 
4035
                return self._vfs_set_option(value, name, section)
 
4036
            return self._set_config_option_dict(value, name, section)
 
4037
        else:
 
4038
            return self._set_config_option(value, name, section)
 
4039
 
 
4040
    def _set_config_option(self, value, name, section):
2777
4041
        try:
2778
4042
            path = self._branch._remote_path()
2779
4043
            response = self._branch._client.call('Branch.set_config_option',
2780
4044
                path, self._branch._lock_token, self._branch._repo_lock_token,
2781
4045
                value.encode('utf8'), name, section or '')
2782
4046
        except errors.UnknownSmartMethod:
 
4047
            medium = self._branch._client._medium
2783
4048
            medium._remember_remote_is_before((1, 14))
2784
4049
            return self._vfs_set_option(value, name, section)
2785
4050
        if response != ():
2786
4051
            raise errors.UnexpectedSmartServerResponse(response)
2787
4052
 
 
4053
    def _serialize_option_dict(self, option_dict):
 
4054
        utf8_dict = {}
 
4055
        for key, value in option_dict.items():
 
4056
            if isinstance(key, unicode):
 
4057
                key = key.encode('utf8')
 
4058
            if isinstance(value, unicode):
 
4059
                value = value.encode('utf8')
 
4060
            utf8_dict[key] = value
 
4061
        return bencode.bencode(utf8_dict)
 
4062
 
 
4063
    def _set_config_option_dict(self, value, name, section):
 
4064
        try:
 
4065
            path = self._branch._remote_path()
 
4066
            serialised_dict = self._serialize_option_dict(value)
 
4067
            response = self._branch._client.call(
 
4068
                'Branch.set_config_option_dict',
 
4069
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
4070
                serialised_dict, name, section or '')
 
4071
        except errors.UnknownSmartMethod:
 
4072
            medium = self._branch._client._medium
 
4073
            medium._remember_remote_is_before((2, 2))
 
4074
            return self._vfs_set_option(value, name, section)
 
4075
        if response != ():
 
4076
            raise errors.UnexpectedSmartServerResponse(response)
 
4077
 
2788
4078
    def _real_object(self):
2789
4079
        self._branch._ensure_real()
2790
4080
        return self._branch._real_branch
2829
4119
        return self._bzrdir._real_bzrdir
2830
4120
 
2831
4121
 
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)
 
4122
error_translators = registry.Registry()
 
4123
no_context_error_translators = registry.Registry()
2840
4124
 
2841
4125
 
2842
4126
def _translate_error(err, **context):
2856
4140
    def find(name):
2857
4141
        try:
2858
4142
            return context[name]
2859
 
        except KeyError, key_err:
 
4143
        except KeyError as key_err:
2860
4144
            mutter('Missing key %r in context %r', key_err.args[0], context)
2861
4145
            raise err
2862
4146
    def get_path():
2865
4149
        """
2866
4150
        try:
2867
4151
            return context['path']
2868
 
        except KeyError, key_err:
 
4152
        except KeyError as key_err:
2869
4153
            try:
2870
4154
                return err.error_args[0]
2871
 
            except IndexError, idx_err:
 
4155
            except IndexError as idx_err:
2872
4156
                mutter(
2873
4157
                    'Missing key %r in context %r', key_err.args[0], context)
2874
4158
                raise err
2875
4159
 
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'):
 
4160
    try:
 
4161
        translator = error_translators.get(err.error_verb)
 
4162
    except KeyError:
 
4163
        pass
 
4164
    else:
 
4165
        raise translator(err, find, get_path)
 
4166
    try:
 
4167
        translator = no_context_error_translators.get(err.error_verb)
 
4168
    except KeyError:
 
4169
        raise errors.UnknownErrorFromSmartServer(err)
 
4170
    else:
 
4171
        raise translator(err)
 
4172
 
 
4173
 
 
4174
error_translators.register('NoSuchRevision',
 
4175
    lambda err, find, get_path: NoSuchRevision(
 
4176
        find('branch'), err.error_args[0]))
 
4177
error_translators.register('nosuchrevision',
 
4178
    lambda err, find, get_path: NoSuchRevision(
 
4179
        find('repository'), err.error_args[0]))
 
4180
 
 
4181
def _translate_nobranch_error(err, find, get_path):
 
4182
    if len(err.error_args) >= 1:
 
4183
        extra = err.error_args[0]
 
4184
    else:
 
4185
        extra = None
 
4186
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
4187
        detail=extra)
 
4188
 
 
4189
error_translators.register('nobranch', _translate_nobranch_error)
 
4190
error_translators.register('norepository',
 
4191
    lambda err, find, get_path: errors.NoRepositoryPresent(
 
4192
        find('bzrdir')))
 
4193
error_translators.register('UnlockableTransport',
 
4194
    lambda err, find, get_path: errors.UnlockableTransport(
 
4195
        find('bzrdir').root_transport))
 
4196
error_translators.register('TokenMismatch',
 
4197
    lambda err, find, get_path: errors.TokenMismatch(
 
4198
        find('token'), '(remote token)'))
 
4199
error_translators.register('Diverged',
 
4200
    lambda err, find, get_path: errors.DivergedBranches(
 
4201
        find('branch'), find('other_branch')))
 
4202
error_translators.register('NotStacked',
 
4203
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
 
4204
 
 
4205
def _translate_PermissionDenied(err, find, get_path):
 
4206
    path = get_path()
 
4207
    if len(err.error_args) >= 2:
 
4208
        extra = err.error_args[1]
 
4209
    else:
 
4210
        extra = None
 
4211
    return errors.PermissionDenied(path, extra=extra)
 
4212
 
 
4213
error_translators.register('PermissionDenied', _translate_PermissionDenied)
 
4214
error_translators.register('ReadError',
 
4215
    lambda err, find, get_path: errors.ReadError(get_path()))
 
4216
error_translators.register('NoSuchFile',
 
4217
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
 
4218
error_translators.register('TokenLockingNotSupported',
 
4219
    lambda err, find, get_path: errors.TokenLockingNotSupported(
 
4220
        find('repository')))
 
4221
error_translators.register('UnsuspendableWriteGroup',
 
4222
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
 
4223
        repository=find('repository')))
 
4224
error_translators.register('UnresumableWriteGroup',
 
4225
    lambda err, find, get_path: errors.UnresumableWriteGroup(
 
4226
        repository=find('repository'), write_groups=err.error_args[0],
 
4227
        reason=err.error_args[1]))
 
4228
no_context_error_translators.register('IncompatibleRepositories',
 
4229
    lambda err: errors.IncompatibleRepositories(
 
4230
        err.error_args[0], err.error_args[1], err.error_args[2]))
 
4231
no_context_error_translators.register('LockContention',
 
4232
    lambda err: errors.LockContention('(remote lock)'))
 
4233
no_context_error_translators.register('LockFailed',
 
4234
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
 
4235
no_context_error_translators.register('TipChangeRejected',
 
4236
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
 
4237
no_context_error_translators.register('UnstackableBranchFormat',
 
4238
    lambda err: branch.UnstackableBranchFormat(*err.error_args))
 
4239
no_context_error_translators.register('UnstackableRepositoryFormat',
 
4240
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
 
4241
no_context_error_translators.register('FileExists',
 
4242
    lambda err: errors.FileExists(err.error_args[0]))
 
4243
no_context_error_translators.register('DirectoryNotEmpty',
 
4244
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
 
4245
 
 
4246
def _translate_short_readv_error(err):
 
4247
    args = err.error_args
 
4248
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
 
4249
        int(args[3]))
 
4250
 
 
4251
no_context_error_translators.register('ShortReadvError',
 
4252
    _translate_short_readv_error)
 
4253
 
 
4254
def _translate_unicode_error(err):
2932
4255
        encoding = str(err.error_args[0]) # encoding must always be a string
2933
4256
        val = err.error_args[1]
2934
4257
        start = int(err.error_args[2])
2942
4265
            raise UnicodeDecodeError(encoding, val, start, end, reason)
2943
4266
        elif err.error_verb == 'UnicodeEncodeError':
2944
4267
            raise UnicodeEncodeError(encoding, val, start, end, reason)
2945
 
    elif err.error_verb == 'ReadOnlyError':
2946
 
        raise errors.TransportNotPossible('readonly transport')
2947
 
    raise errors.UnknownErrorFromSmartServer(err)
 
4268
 
 
4269
no_context_error_translators.register('UnicodeEncodeError',
 
4270
    _translate_unicode_error)
 
4271
no_context_error_translators.register('UnicodeDecodeError',
 
4272
    _translate_unicode_error)
 
4273
no_context_error_translators.register('ReadOnlyError',
 
4274
    lambda err: errors.TransportNotPossible('readonly transport'))
 
4275
no_context_error_translators.register('MemoryError',
 
4276
    lambda err: errors.BzrError("remote server out of memory\n"
 
4277
        "Retry non-remotely, or contact the server admin for details."))
 
4278
no_context_error_translators.register('RevisionNotPresent',
 
4279
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
 
4280
 
 
4281
no_context_error_translators.register('BzrCheckError',
 
4282
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
 
4283