/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: Martin
  • Date: 2017-06-05 20:48:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6658.
  • Revision ID: gzlist@googlemail.com-20170605204831-20accykspjcrx0a8
Apply 2to3 dict fixer and clean up resulting mess using view helpers

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