/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/remote.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-10 00:21:41 UTC
  • mto: This revision was merged to the branch mainline in revision 6675.
  • Revision ID: jelmer@jelmer.uk-20170610002141-m1z5k7fs8laesa65
Fix import.

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