~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-07-19 16:09:34 UTC
  • mfrom: (2520.4.135 bzr.mpbundle)
  • Revision ID: pqm@pqm.ubuntu.com-20070719160934-d51fyijw69oto88p
Add new bundle and merge-directive formats

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from __future__ import absolute_import
18
 
 
19
 
import bz2
20
 
import zlib
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
# TODO: At some point, handle upgrades by just passing the whole request
 
18
# across to run on the server.
 
19
 
 
20
from cStringIO import StringIO
21
21
 
22
22
from bzrlib import (
23
 
    bencode,
24
23
    branch,
25
 
    bzrdir as _mod_bzrdir,
26
 
    config,
27
 
    controldir,
28
 
    debug,
29
24
    errors,
30
 
    gpg,
31
 
    graph,
32
 
    inventory_delta,
33
 
    lock,
34
25
    lockdir,
35
 
    osutils,
36
 
    registry,
37
 
    repository as _mod_repository,
38
 
    revision as _mod_revision,
39
 
    static_tuple,
40
 
    symbol_versioning,
41
 
    testament as _mod_testament,
42
 
    urlutils,
43
 
    vf_repository,
44
 
    vf_search,
45
 
    )
46
 
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
47
 
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
 
from bzrlib.errors import (
49
 
    NoSuchRevision,
50
 
    SmartProtocolError,
51
 
    )
52
 
from bzrlib.i18n import gettext
53
 
from bzrlib.inventory import Inventory
 
26
    repository,
 
27
)
 
28
from bzrlib.branch import Branch, BranchReferenceFormat
 
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
30
from bzrlib.config import BranchConfig, TreeConfig
 
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
32
from bzrlib.errors import NoSuchRevision
54
33
from bzrlib.lockable_files import LockableFiles
55
 
from bzrlib.smart import client, vfs, repository as smart_repo
56
 
from bzrlib.smart.client import _SmartClient
57
34
from bzrlib.revision import NULL_REVISION
58
 
from bzrlib.revisiontree import InventoryRevisionTree
59
 
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
60
 
from bzrlib.serializer import format_registry as serializer_format_registry
61
 
from bzrlib.trace import mutter, note, warning, log_exception_quietly
62
 
 
63
 
 
64
 
_DEFAULT_SEARCH_DEPTH = 100
65
 
 
66
 
 
67
 
class _RpcHelper(object):
68
 
    """Mixin class that helps with issuing RPCs."""
69
 
 
70
 
    def _call(self, method, *args, **err_context):
71
 
        try:
72
 
            return self._client.call(method, *args)
73
 
        except errors.ErrorFromSmartServer, err:
74
 
            self._translate_error(err, **err_context)
75
 
 
76
 
    def _call_expecting_body(self, method, *args, **err_context):
77
 
        try:
78
 
            return self._client.call_expecting_body(method, *args)
79
 
        except errors.ErrorFromSmartServer, err:
80
 
            self._translate_error(err, **err_context)
81
 
 
82
 
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
83
 
        try:
84
 
            return self._client.call_with_body_bytes(method, args, body_bytes)
85
 
        except errors.ErrorFromSmartServer, err:
86
 
            self._translate_error(err, **err_context)
87
 
 
88
 
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
89
 
                                             **err_context):
90
 
        try:
91
 
            return self._client.call_with_body_bytes_expecting_body(
92
 
                method, args, body_bytes)
93
 
        except errors.ErrorFromSmartServer, err:
94
 
            self._translate_error(err, **err_context)
95
 
 
96
 
 
97
 
def response_tuple_to_repo_format(response):
98
 
    """Convert a response tuple describing a repository format to a format."""
99
 
    format = RemoteRepositoryFormat()
100
 
    format._rich_root_data = (response[0] == 'yes')
101
 
    format._supports_tree_reference = (response[1] == 'yes')
102
 
    format._supports_external_lookups = (response[2] == 'yes')
103
 
    format._network_name = response[3]
104
 
    return format
105
 
 
106
 
 
107
 
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
108
 
# does not have to be imported unless a remote format is involved.
109
 
 
110
 
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
111
 
    """Format representing bzrdirs accessed via a smart server"""
112
 
 
113
 
    supports_workingtrees = False
114
 
 
115
 
    def __init__(self):
116
 
        _mod_bzrdir.BzrDirMetaFormat1.__init__(self)
117
 
        # XXX: It's a bit ugly that the network name is here, because we'd
118
 
        # like to believe that format objects are stateless or at least
119
 
        # immutable,  However, we do at least avoid mutating the name after
120
 
        # it's returned.  See <https://bugs.launchpad.net/bzr/+bug/504102>
121
 
        self._network_name = None
122
 
 
123
 
    def __repr__(self):
124
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
125
 
            self._network_name)
126
 
 
127
 
    def get_format_description(self):
128
 
        if self._network_name:
129
 
            try:
130
 
                real_format = controldir.network_format_registry.get(
131
 
                        self._network_name)
132
 
            except KeyError:
133
 
                pass
134
 
            else:
135
 
                return 'Remote: ' + real_format.get_format_description()
136
 
        return 'bzr remote bzrdir'
137
 
 
138
 
    def get_format_string(self):
139
 
        raise NotImplementedError(self.get_format_string)
140
 
 
141
 
    def network_name(self):
142
 
        if self._network_name:
143
 
            return self._network_name
144
 
        else:
145
 
            raise AssertionError("No network name set.")
146
 
 
147
 
    def initialize_on_transport(self, transport):
148
 
        try:
149
 
            # hand off the request to the smart server
150
 
            client_medium = transport.get_smart_medium()
151
 
        except errors.NoSmartMedium:
152
 
            # TODO: lookup the local format from a server hint.
153
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
154
 
            return local_dir_format.initialize_on_transport(transport)
155
 
        client = _SmartClient(client_medium)
156
 
        path = client.remote_path_from_transport(transport)
157
 
        try:
158
 
            response = client.call('BzrDirFormat.initialize', path)
159
 
        except errors.ErrorFromSmartServer, err:
160
 
            _translate_error(err, path=path)
161
 
        if response[0] != 'ok':
162
 
            raise errors.SmartProtocolError('unexpected response code %s' % (response,))
163
 
        format = RemoteBzrDirFormat()
164
 
        self._supply_sub_formats_to(format)
165
 
        return RemoteBzrDir(transport, format)
166
 
 
167
 
    def parse_NoneTrueFalse(self, arg):
168
 
        if not arg:
169
 
            return None
170
 
        if arg == 'False':
171
 
            return False
172
 
        if arg == 'True':
173
 
            return True
174
 
        raise AssertionError("invalid arg %r" % arg)
175
 
 
176
 
    def _serialize_NoneTrueFalse(self, arg):
177
 
        if arg is False:
178
 
            return 'False'
179
 
        if arg:
180
 
            return 'True'
181
 
        return ''
182
 
 
183
 
    def _serialize_NoneString(self, arg):
184
 
        return arg or ''
185
 
 
186
 
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
187
 
        create_prefix=False, force_new_repo=False, stacked_on=None,
188
 
        stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
189
 
        shared_repo=False):
190
 
        try:
191
 
            # hand off the request to the smart server
192
 
            client_medium = transport.get_smart_medium()
193
 
        except errors.NoSmartMedium:
194
 
            do_vfs = True
195
 
        else:
196
 
            # Decline to open it if the server doesn't support our required
197
 
            # version (3) so that the VFS-based transport will do it.
198
 
            if client_medium.should_probe():
199
 
                try:
200
 
                    server_version = client_medium.protocol_version()
201
 
                    if server_version != '2':
202
 
                        do_vfs = True
203
 
                    else:
204
 
                        do_vfs = False
205
 
                except errors.SmartProtocolError:
206
 
                    # Apparently there's no usable smart server there, even though
207
 
                    # the medium supports the smart protocol.
208
 
                    do_vfs = True
209
 
            else:
210
 
                do_vfs = False
211
 
        if not do_vfs:
212
 
            client = _SmartClient(client_medium)
213
 
            path = client.remote_path_from_transport(transport)
214
 
            if client_medium._is_remote_before((1, 16)):
215
 
                do_vfs = True
216
 
        if do_vfs:
217
 
            # TODO: lookup the local format from a server hint.
218
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
219
 
            self._supply_sub_formats_to(local_dir_format)
220
 
            return local_dir_format.initialize_on_transport_ex(transport,
221
 
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
222
 
                force_new_repo=force_new_repo, stacked_on=stacked_on,
223
 
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
224
 
                make_working_trees=make_working_trees, shared_repo=shared_repo,
225
 
                vfs_only=True)
226
 
        return self._initialize_on_transport_ex_rpc(client, path, transport,
227
 
            use_existing_dir, create_prefix, force_new_repo, stacked_on,
228
 
            stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
229
 
 
230
 
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
231
 
        use_existing_dir, create_prefix, force_new_repo, stacked_on,
232
 
        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
233
 
        args = []
234
 
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
235
 
        args.append(self._serialize_NoneTrueFalse(create_prefix))
236
 
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
237
 
        args.append(self._serialize_NoneString(stacked_on))
238
 
        # stack_on_pwd is often/usually our transport
239
 
        if stack_on_pwd:
240
 
            try:
241
 
                stack_on_pwd = transport.relpath(stack_on_pwd)
242
 
                if not stack_on_pwd:
243
 
                    stack_on_pwd = '.'
244
 
            except errors.PathNotChild:
245
 
                pass
246
 
        args.append(self._serialize_NoneString(stack_on_pwd))
247
 
        args.append(self._serialize_NoneString(repo_format_name))
248
 
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
249
 
        args.append(self._serialize_NoneTrueFalse(shared_repo))
250
 
        request_network_name = self._network_name or \
251
 
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
252
 
        try:
253
 
            response = client.call('BzrDirFormat.initialize_ex_1.16',
254
 
                request_network_name, path, *args)
255
 
        except errors.UnknownSmartMethod:
256
 
            client._medium._remember_remote_is_before((1,16))
257
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
258
 
            self._supply_sub_formats_to(local_dir_format)
259
 
            return local_dir_format.initialize_on_transport_ex(transport,
260
 
                use_existing_dir=use_existing_dir, create_prefix=create_prefix,
261
 
                force_new_repo=force_new_repo, stacked_on=stacked_on,
262
 
                stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
263
 
                make_working_trees=make_working_trees, shared_repo=shared_repo,
264
 
                vfs_only=True)
265
 
        except errors.ErrorFromSmartServer, err:
266
 
            _translate_error(err, path=path)
267
 
        repo_path = response[0]
268
 
        bzrdir_name = response[6]
269
 
        require_stacking = response[7]
270
 
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
271
 
        format = RemoteBzrDirFormat()
272
 
        format._network_name = bzrdir_name
273
 
        self._supply_sub_formats_to(format)
274
 
        bzrdir = RemoteBzrDir(transport, format, _client=client)
275
 
        if repo_path:
276
 
            repo_format = response_tuple_to_repo_format(response[1:])
277
 
            if repo_path == '.':
278
 
                repo_path = ''
279
 
            if repo_path:
280
 
                repo_bzrdir_format = RemoteBzrDirFormat()
281
 
                repo_bzrdir_format._network_name = response[5]
282
 
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
283
 
                    repo_bzrdir_format)
284
 
            else:
285
 
                repo_bzr = bzrdir
286
 
            final_stack = response[8] or None
287
 
            final_stack_pwd = response[9] or None
288
 
            if final_stack_pwd:
289
 
                final_stack_pwd = urlutils.join(
290
 
                    transport.base, final_stack_pwd)
291
 
            remote_repo = RemoteRepository(repo_bzr, repo_format)
292
 
            if len(response) > 10:
293
 
                # Updated server verb that locks remotely.
294
 
                repo_lock_token = response[10] or None
295
 
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
296
 
                if repo_lock_token:
297
 
                    remote_repo.dont_leave_lock_in_place()
298
 
            else:
299
 
                remote_repo.lock_write()
300
 
            policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
301
 
                final_stack_pwd, require_stacking)
302
 
            policy.acquire_repository()
303
 
        else:
304
 
            remote_repo = None
305
 
            policy = None
306
 
        bzrdir._format.set_branch_format(self.get_branch_format())
307
 
        if require_stacking:
308
 
            # The repo has already been created, but we need to make sure that
309
 
            # we'll make a stackable branch.
310
 
            bzrdir._format.require_stacking(_skip_repo=True)
311
 
        return remote_repo, bzrdir, require_stacking, policy
312
 
 
313
 
    def _open(self, transport):
314
 
        return RemoteBzrDir(transport, self)
315
 
 
316
 
    def __eq__(self, other):
317
 
        if not isinstance(other, RemoteBzrDirFormat):
318
 
            return False
319
 
        return self.get_format_description() == other.get_format_description()
320
 
 
321
 
    def __return_repository_format(self):
322
 
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
323
 
        # repository format has been asked for, tell the RemoteRepositoryFormat
324
 
        # that it should use that for init() etc.
325
 
        result = RemoteRepositoryFormat()
326
 
        custom_format = getattr(self, '_repository_format', None)
327
 
        if custom_format:
328
 
            if isinstance(custom_format, RemoteRepositoryFormat):
329
 
                return custom_format
330
 
            else:
331
 
                # We will use the custom format to create repositories over the
332
 
                # wire; expose its details like rich_root_data for code to
333
 
                # query
334
 
                result._custom_format = custom_format
335
 
        return result
336
 
 
337
 
    def get_branch_format(self):
338
 
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
339
 
        if not isinstance(result, RemoteBranchFormat):
340
 
            new_result = RemoteBranchFormat()
341
 
            new_result._custom_format = result
342
 
            # cache the result
343
 
            self.set_branch_format(new_result)
344
 
            result = new_result
345
 
        return result
346
 
 
347
 
    repository_format = property(__return_repository_format,
348
 
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
349
 
 
350
 
 
351
 
class RemoteControlStore(config.IniFileStore):
352
 
    """Control store which attempts to use HPSS calls to retrieve control store.
353
 
 
354
 
    Note that this is specific to bzr-based formats.
355
 
    """
356
 
 
357
 
    def __init__(self, bzrdir):
358
 
        super(RemoteControlStore, self).__init__()
359
 
        self.bzrdir = bzrdir
360
 
        self._real_store = None
361
 
 
362
 
    def lock_write(self, token=None):
363
 
        self._ensure_real()
364
 
        return self._real_store.lock_write(token)
365
 
 
366
 
    def unlock(self):
367
 
        self._ensure_real()
368
 
        return self._real_store.unlock()
369
 
 
370
 
    @needs_write_lock
371
 
    def save(self):
372
 
        # We need to be able to override the undecorated implementation
373
 
        self.save_without_locking()
374
 
 
375
 
    def save_without_locking(self):
376
 
        super(RemoteControlStore, self).save()
377
 
 
378
 
    def _ensure_real(self):
379
 
        self.bzrdir._ensure_real()
380
 
        if self._real_store is None:
381
 
            self._real_store = config.ControlStore(self.bzrdir)
382
 
 
383
 
    def external_url(self):
384
 
        return self.bzrdir.user_url
385
 
 
386
 
    def _load_content(self):
387
 
        medium = self.bzrdir._client._medium
388
 
        path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
389
 
        try:
390
 
            response, handler = self.bzrdir._call_expecting_body(
391
 
                'BzrDir.get_config_file', path)
392
 
        except errors.UnknownSmartMethod:
393
 
            self._ensure_real()
394
 
            return self._real_store._load_content()
395
 
        if len(response) and response[0] != 'ok':
396
 
            raise errors.UnexpectedSmartServerResponse(response)
397
 
        return handler.read_body_bytes()
398
 
 
399
 
    def _save_content(self, content):
400
 
        # FIXME JRV 2011-11-22: Ideally this should use a
401
 
        # HPSS call too, but at the moment it is not possible
402
 
        # to write lock control directories.
403
 
        self._ensure_real()
404
 
        return self._real_store._save_content(content)
405
 
 
406
 
 
407
 
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
 
35
from bzrlib.smart import client, vfs
 
36
from bzrlib.trace import note
 
37
 
 
38
# Note: RemoteBzrDirFormat is in bzrdir.py
 
39
 
 
40
class RemoteBzrDir(BzrDir):
408
41
    """Control directory on a remote server, accessed via bzr:// or similar."""
409
42
 
410
 
    def __init__(self, transport, format, _client=None, _force_probe=False):
 
43
    def __init__(self, transport, _client=None):
411
44
        """Construct a RemoteBzrDir.
412
45
 
413
46
        :param _client: Private parameter for testing. Disables probing and the
414
47
            use of a real bzrdir.
415
48
        """
416
 
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
49
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
417
50
        # this object holds a delegated bzrdir that uses file-level operations
418
51
        # to talk to the other side
419
52
        self._real_bzrdir = None
420
 
        self._has_working_tree = None
421
 
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
422
 
        # create_branch for details.
423
 
        self._next_open_branch_result = None
424
53
 
425
54
        if _client is None:
426
 
            medium = transport.get_smart_medium()
427
 
            self._client = client._SmartClient(medium)
 
55
            self._medium = transport.get_smart_client()
 
56
            self._client = client._SmartClient(self._medium)
428
57
        else:
429
58
            self._client = _client
430
 
            if not _force_probe:
431
 
                return
432
 
 
433
 
        self._probe_bzrdir()
434
 
 
435
 
    def __repr__(self):
436
 
        return '%s(%r)' % (self.__class__.__name__, self._client)
437
 
 
438
 
    def _probe_bzrdir(self):
439
 
        medium = self._client._medium
 
59
            self._medium = None
 
60
            return
 
61
 
440
62
        path = self._path_for_remote_call(self._client)
441
 
        if medium._is_remote_before((2, 1)):
442
 
            self._rpc_open(path)
443
 
            return
444
 
        try:
445
 
            self._rpc_open_2_1(path)
446
 
            return
447
 
        except errors.UnknownSmartMethod:
448
 
            medium._remember_remote_is_before((2, 1))
449
 
            self._rpc_open(path)
450
 
 
451
 
    def _rpc_open_2_1(self, path):
452
 
        response = self._call('BzrDir.open_2.1', path)
453
 
        if response == ('no',):
454
 
            raise errors.NotBranchError(path=self.root_transport.base)
455
 
        elif response[0] == 'yes':
456
 
            if response[1] == 'yes':
457
 
                self._has_working_tree = True
458
 
            elif response[1] == 'no':
459
 
                self._has_working_tree = False
460
 
            else:
461
 
                raise errors.UnexpectedSmartServerResponse(response)
462
 
        else:
463
 
            raise errors.UnexpectedSmartServerResponse(response)
464
 
 
465
 
    def _rpc_open(self, path):
466
 
        response = self._call('BzrDir.open', path)
 
63
        response = self._client.call('BzrDir.open', path)
467
64
        if response not in [('yes',), ('no',)]:
468
65
            raise errors.UnexpectedSmartServerResponse(response)
469
66
        if response == ('no',):
470
 
            raise errors.NotBranchError(path=self.root_transport.base)
 
67
            raise errors.NotBranchError(path=transport.base)
471
68
 
472
69
    def _ensure_real(self):
473
70
        """Ensure that there is a _real_bzrdir set.
475
72
        Used before calls to self._real_bzrdir.
476
73
        """
477
74
        if not self._real_bzrdir:
478
 
            if 'hpssvfs' in debug.debug_flags:
479
 
                import traceback
480
 
                warning('VFS BzrDir access triggered\n%s',
481
 
                    ''.join(traceback.format_stack()))
482
 
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
 
75
            self._real_bzrdir = BzrDir.open_from_transport(
483
76
                self.root_transport, _server_formats=False)
484
 
            self._format._network_name = \
485
 
                self._real_bzrdir._format.network_name()
486
 
 
487
 
    def _translate_error(self, err, **context):
488
 
        _translate_error(err, bzrdir=self, **context)
489
 
 
490
 
    def break_lock(self):
491
 
        # Prevent aliasing problems in the next_open_branch_result cache.
492
 
        # See create_branch for rationale.
493
 
        self._next_open_branch_result = None
494
 
        return _mod_bzrdir.BzrDir.break_lock(self)
495
 
 
496
 
    def _vfs_checkout_metadir(self):
497
 
        self._ensure_real()
498
 
        return self._real_bzrdir.checkout_metadir()
499
 
 
500
 
    def checkout_metadir(self):
501
 
        """Retrieve the controldir format to use for checkouts of this one.
502
 
        """
503
 
        medium = self._client._medium
504
 
        if medium._is_remote_before((2, 5)):
505
 
            return self._vfs_checkout_metadir()
506
 
        path = self._path_for_remote_call(self._client)
507
 
        try:
508
 
            response = self._client.call('BzrDir.checkout_metadir',
509
 
                path)
510
 
        except errors.UnknownSmartMethod:
511
 
            medium._remember_remote_is_before((2, 5))
512
 
            return self._vfs_checkout_metadir()
513
 
        if len(response) != 3:
514
 
            raise errors.UnexpectedSmartServerResponse(response)
515
 
        control_name, repo_name, branch_name = response
516
 
        try:
517
 
            format = controldir.network_format_registry.get(control_name)
518
 
        except KeyError:
519
 
            raise errors.UnknownFormatError(kind='control',
520
 
                format=control_name)
521
 
        if repo_name:
522
 
            try:
523
 
                repo_format = _mod_repository.network_format_registry.get(
524
 
                    repo_name)
525
 
            except KeyError:
526
 
                raise errors.UnknownFormatError(kind='repository',
527
 
                    format=repo_name)
528
 
            format.repository_format = repo_format
529
 
        if branch_name:
530
 
            try:
531
 
                format.set_branch_format(
532
 
                    branch.network_format_registry.get(branch_name))
533
 
            except KeyError:
534
 
                raise errors.UnknownFormatError(kind='branch',
535
 
                    format=branch_name)
536
 
        return format
537
 
 
538
 
    def _vfs_cloning_metadir(self, require_stacking=False):
539
 
        self._ensure_real()
540
 
        return self._real_bzrdir.cloning_metadir(
541
 
            require_stacking=require_stacking)
542
 
 
543
 
    def cloning_metadir(self, require_stacking=False):
544
 
        medium = self._client._medium
545
 
        if medium._is_remote_before((1, 13)):
546
 
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
547
 
        verb = 'BzrDir.cloning_metadir'
548
 
        if require_stacking:
549
 
            stacking = 'True'
550
 
        else:
551
 
            stacking = 'False'
552
 
        path = self._path_for_remote_call(self._client)
553
 
        try:
554
 
            response = self._call(verb, path, stacking)
555
 
        except errors.UnknownSmartMethod:
556
 
            medium._remember_remote_is_before((1, 13))
557
 
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
 
        except errors.UnknownErrorFromSmartServer, err:
559
 
            if err.error_tuple != ('BranchReference',):
560
 
                raise
561
 
            # We need to resolve the branch reference to determine the
562
 
            # cloning_metadir.  This causes unnecessary RPCs to open the
563
 
            # referenced branch (and bzrdir, etc) but only when the caller
564
 
            # didn't already resolve the branch reference.
565
 
            referenced_branch = self.open_branch()
566
 
            return referenced_branch.bzrdir.cloning_metadir()
567
 
        if len(response) != 3:
568
 
            raise errors.UnexpectedSmartServerResponse(response)
569
 
        control_name, repo_name, branch_info = response
570
 
        if len(branch_info) != 2:
571
 
            raise errors.UnexpectedSmartServerResponse(response)
572
 
        branch_ref, branch_name = branch_info
573
 
        try:
574
 
            format = controldir.network_format_registry.get(control_name)
575
 
        except KeyError:
576
 
            raise errors.UnknownFormatError(kind='control', format=control_name)
577
 
 
578
 
        if repo_name:
579
 
            try:
580
 
                format.repository_format = _mod_repository.network_format_registry.get(
581
 
                    repo_name)
582
 
            except KeyError:
583
 
                raise errors.UnknownFormatError(kind='repository',
584
 
                    format=repo_name)
585
 
        if branch_ref == 'ref':
586
 
            # XXX: we need possible_transports here to avoid reopening the
587
 
            # connection to the referenced location
588
 
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
589
 
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
590
 
            format.set_branch_format(branch_format)
591
 
        elif branch_ref == 'branch':
592
 
            if branch_name:
593
 
                try:
594
 
                    branch_format = branch.network_format_registry.get(
595
 
                        branch_name)
596
 
                except KeyError:
597
 
                    raise errors.UnknownFormatError(kind='branch',
598
 
                        format=branch_name)
599
 
                format.set_branch_format(branch_format)
600
 
        else:
601
 
            raise errors.UnexpectedSmartServerResponse(response)
602
 
        return format
603
77
 
604
78
    def create_repository(self, shared=False):
605
 
        # as per meta1 formats - just delegate to the format object which may
606
 
        # be parameterised.
607
 
        result = self._format.repository_format.initialize(self, shared)
608
 
        if not isinstance(result, RemoteRepository):
609
 
            return self.open_repository()
610
 
        else:
611
 
            return result
612
 
 
613
 
    def destroy_repository(self):
614
 
        """See BzrDir.destroy_repository"""
615
 
        path = self._path_for_remote_call(self._client)
616
 
        try:
617
 
            response = self._call('BzrDir.destroy_repository', path)
618
 
        except errors.UnknownSmartMethod:
619
 
            self._ensure_real()
620
 
            self._real_bzrdir.destroy_repository()
621
 
            return
622
 
        if response[0] != 'ok':
623
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
624
 
 
625
 
    def create_branch(self, name=None, repository=None,
626
 
                      append_revisions_only=None):
627
 
        # as per meta1 formats - just delegate to the format object which may
628
 
        # be parameterised.
629
 
        real_branch = self._format.get_branch_format().initialize(self,
630
 
            name=name, repository=repository,
631
 
            append_revisions_only=append_revisions_only)
632
 
        if not isinstance(real_branch, RemoteBranch):
633
 
            if not isinstance(repository, RemoteRepository):
634
 
                raise AssertionError(
635
 
                    'need a RemoteRepository to use with RemoteBranch, got %r'
636
 
                    % (repository,))
637
 
            result = RemoteBranch(self, repository, real_branch, name=name)
638
 
        else:
639
 
            result = real_branch
640
 
        # BzrDir.clone_on_transport() uses the result of create_branch but does
641
 
        # not return it to its callers; we save approximately 8% of our round
642
 
        # trips by handing the branch we created back to the first caller to
643
 
        # open_branch rather than probing anew. Long term we need a API in
644
 
        # bzrdir that doesn't discard result objects (like result_branch).
645
 
        # RBC 20090225
646
 
        self._next_open_branch_result = result
647
 
        return result
648
 
 
649
 
    def destroy_branch(self, name=None):
650
 
        """See BzrDir.destroy_branch"""
651
 
        path = self._path_for_remote_call(self._client)
652
 
        try:
653
 
            if name is not None:
654
 
                args = (name, )
655
 
            else:
656
 
                args = ()
657
 
            response = self._call('BzrDir.destroy_branch', path, *args)
658
 
        except errors.UnknownSmartMethod:
659
 
            self._ensure_real()
660
 
            self._real_bzrdir.destroy_branch(name=name)
661
 
            self._next_open_branch_result = None
662
 
            return
663
 
        self._next_open_branch_result = None
664
 
        if response[0] != 'ok':
665
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
666
 
 
667
 
    def create_workingtree(self, revision_id=None, from_branch=None,
668
 
        accelerator_tree=None, hardlink=False):
 
79
        self._ensure_real()
 
80
        self._real_bzrdir.create_repository(shared=shared)
 
81
        return self.open_repository()
 
82
 
 
83
    def create_branch(self):
 
84
        self._ensure_real()
 
85
        real_branch = self._real_bzrdir.create_branch()
 
86
        return RemoteBranch(self, self.find_repository(), real_branch)
 
87
 
 
88
    def create_workingtree(self, revision_id=None):
669
89
        raise errors.NotLocalUrl(self.transport.base)
670
90
 
671
 
    def find_branch_format(self, name=None):
 
91
    def find_branch_format(self):
672
92
        """Find the branch 'format' for this bzrdir.
673
93
 
674
94
        This might be a synthetic object for e.g. RemoteBranch and SVN.
675
95
        """
676
 
        b = self.open_branch(name=name)
 
96
        b = self.open_branch()
677
97
        return b._format
678
98
 
679
 
    def get_branch_reference(self, name=None):
 
99
    def get_branch_reference(self):
680
100
        """See BzrDir.get_branch_reference()."""
681
 
        if name is not None:
682
 
            # XXX JRV20100304: Support opening colocated branches
683
 
            raise errors.NoColocatedBranchSupport(self)
684
 
        response = self._get_branch_reference()
685
 
        if response[0] == 'ref':
686
 
            return response[1]
687
 
        else:
688
 
            return None
689
 
 
690
 
    def _get_branch_reference(self):
691
101
        path = self._path_for_remote_call(self._client)
692
 
        medium = self._client._medium
693
 
        candidate_calls = [
694
 
            ('BzrDir.open_branchV3', (2, 1)),
695
 
            ('BzrDir.open_branchV2', (1, 13)),
696
 
            ('BzrDir.open_branch', None),
697
 
            ]
698
 
        for verb, required_version in candidate_calls:
699
 
            if required_version and medium._is_remote_before(required_version):
700
 
                continue
701
 
            try:
702
 
                response = self._call(verb, path)
703
 
            except errors.UnknownSmartMethod:
704
 
                if required_version is None:
705
 
                    raise
706
 
                medium._remember_remote_is_before(required_version)
707
 
            else:
708
 
                break
709
 
        if verb == 'BzrDir.open_branch':
710
 
            if response[0] != 'ok':
711
 
                raise errors.UnexpectedSmartServerResponse(response)
712
 
            if response[1] != '':
713
 
                return ('ref', response[1])
714
 
            else:
715
 
                return ('branch', '')
716
 
        if response[0] not in ('ref', 'branch'):
 
102
        response = self._client.call('BzrDir.open_branch', path)
 
103
        if response[0] == 'ok':
 
104
            if response[1] == '':
 
105
                # branch at this location.
 
106
                return None
 
107
            else:
 
108
                # a branch reference, use the existing BranchReference logic.
 
109
                return response[1]
 
110
        elif response == ('nobranch',):
 
111
            raise errors.NotBranchError(path=self.root_transport.base)
 
112
        else:
717
113
            raise errors.UnexpectedSmartServerResponse(response)
718
 
        return response
719
 
 
720
 
    def _get_tree_branch(self, name=None):
721
 
        """See BzrDir._get_tree_branch()."""
722
 
        return None, self.open_branch(name=name)
723
 
 
724
 
    def open_branch(self, name=None, unsupported=False,
725
 
                    ignore_fallbacks=False, possible_transports=None):
726
 
        if unsupported:
727
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
728
 
        if self._next_open_branch_result is not None:
729
 
            # See create_branch for details.
730
 
            result = self._next_open_branch_result
731
 
            self._next_open_branch_result = None
732
 
            return result
733
 
        response = self._get_branch_reference()
734
 
        if response[0] == 'ref':
 
114
 
 
115
    def open_branch(self, _unsupported=False):
 
116
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
117
        reference_url = self.get_branch_reference()
 
118
        if reference_url is None:
 
119
            # branch at this location.
 
120
            return RemoteBranch(self, self.find_repository())
 
121
        else:
735
122
            # a branch reference, use the existing BranchReference logic.
736
123
            format = BranchReferenceFormat()
737
 
            return format.open(self, name=name, _found=True,
738
 
                location=response[1], ignore_fallbacks=ignore_fallbacks,
739
 
                possible_transports=possible_transports)
740
 
        branch_format_name = response[1]
741
 
        if not branch_format_name:
742
 
            branch_format_name = None
743
 
        format = RemoteBranchFormat(network_name=branch_format_name)
744
 
        return RemoteBranch(self, self.find_repository(), format=format,
745
 
            setup_stacking=not ignore_fallbacks, name=name,
746
 
            possible_transports=possible_transports)
747
 
 
748
 
    def _open_repo_v1(self, path):
749
 
        verb = 'BzrDir.find_repository'
750
 
        response = self._call(verb, path)
751
 
        if response[0] != 'ok':
752
 
            raise errors.UnexpectedSmartServerResponse(response)
753
 
        # servers that only support the v1 method don't support external
754
 
        # references either.
755
 
        self._ensure_real()
756
 
        repo = self._real_bzrdir.open_repository()
757
 
        response = response + ('no', repo._format.network_name())
758
 
        return response, repo
759
 
 
760
 
    def _open_repo_v2(self, path):
761
 
        verb = 'BzrDir.find_repositoryV2'
762
 
        response = self._call(verb, path)
763
 
        if response[0] != 'ok':
764
 
            raise errors.UnexpectedSmartServerResponse(response)
765
 
        self._ensure_real()
766
 
        repo = self._real_bzrdir.open_repository()
767
 
        response = response + (repo._format.network_name(),)
768
 
        return response, repo
769
 
 
770
 
    def _open_repo_v3(self, path):
771
 
        verb = 'BzrDir.find_repositoryV3'
772
 
        medium = self._client._medium
773
 
        if medium._is_remote_before((1, 13)):
774
 
            raise errors.UnknownSmartMethod(verb)
775
 
        try:
776
 
            response = self._call(verb, path)
777
 
        except errors.UnknownSmartMethod:
778
 
            medium._remember_remote_is_before((1, 13))
779
 
            raise
780
 
        if response[0] != 'ok':
781
 
            raise errors.UnexpectedSmartServerResponse(response)
782
 
        return response, None
783
 
 
 
124
            return format.open(self, _found=True, location=reference_url)
 
125
                
784
126
    def open_repository(self):
785
127
        path = self._path_for_remote_call(self._client)
786
 
        response = None
787
 
        for probe in [self._open_repo_v3, self._open_repo_v2,
788
 
            self._open_repo_v1]:
789
 
            try:
790
 
                response, real_repo = probe(path)
791
 
                break
792
 
            except errors.UnknownSmartMethod:
793
 
                pass
794
 
        if response is None:
795
 
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
796
 
        if response[0] != 'ok':
797
 
            raise errors.UnexpectedSmartServerResponse(response)
798
 
        if len(response) != 6:
799
 
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
128
        response = self._client.call('BzrDir.find_repository', path)
 
129
        assert response[0] in ('ok', 'norepository'), \
 
130
            'unexpected response code %s' % (response,)
 
131
        if response[0] == 'norepository':
 
132
            raise errors.NoRepositoryPresent(self)
 
133
        assert len(response) == 4, 'incorrect response length %s' % (response,)
800
134
        if response[1] == '':
801
 
            # repo is at this dir.
802
 
            format = response_tuple_to_repo_format(response[2:])
803
 
            # Used to support creating a real format instance when needed.
804
 
            format._creating_bzrdir = self
805
 
            remote_repo = RemoteRepository(self, format)
806
 
            format._creating_repo = remote_repo
807
 
            if real_repo is not None:
808
 
                remote_repo._set_real_repository(real_repo)
809
 
            return remote_repo
 
135
            format = RemoteRepositoryFormat()
 
136
            format.rich_root_data = (response[2] == 'yes')
 
137
            format.supports_tree_reference = (response[3] == 'yes')
 
138
            return RemoteRepository(self, format)
810
139
        else:
811
140
            raise errors.NoRepositoryPresent(self)
812
141
 
813
 
    def has_workingtree(self):
814
 
        if self._has_working_tree is None:
815
 
            path = self._path_for_remote_call(self._client)
816
 
            try:
817
 
                response = self._call('BzrDir.has_workingtree', path)
818
 
            except errors.UnknownSmartMethod:
819
 
                self._ensure_real()
820
 
                self._has_working_tree = self._real_bzrdir.has_workingtree()
821
 
            else:
822
 
                if response[0] not in ('yes', 'no'):
823
 
                    raise SmartProtocolError('unexpected response code %s' % (response,))
824
 
                self._has_working_tree = (response[0] == 'yes')
825
 
        return self._has_working_tree
826
 
 
827
142
    def open_workingtree(self, recommend_upgrade=True):
828
 
        if self.has_workingtree():
 
143
        self._ensure_real()
 
144
        if self._real_bzrdir.has_workingtree():
829
145
            raise errors.NotLocalUrl(self.root_transport)
830
146
        else:
831
147
            raise errors.NoWorkingTree(self.root_transport.base)
832
148
 
833
149
    def _path_for_remote_call(self, client):
834
150
        """Return the path to be used for this bzrdir in a remote call."""
835
 
        return urlutils.split_segment_parameters_raw(
836
 
            client.remote_path_from_transport(self.root_transport))[0]
 
151
        return client.remote_path_from_transport(self.root_transport)
837
152
 
838
 
    def get_branch_transport(self, branch_format, name=None):
 
153
    def get_branch_transport(self, branch_format):
839
154
        self._ensure_real()
840
 
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
 
155
        return self._real_bzrdir.get_branch_transport(branch_format)
841
156
 
842
157
    def get_repository_transport(self, repository_format):
843
158
        self._ensure_real()
851
166
        """Upgrading of remote bzrdirs is not supported yet."""
852
167
        return False
853
168
 
854
 
    def needs_format_conversion(self, format):
 
169
    def needs_format_conversion(self, format=None):
855
170
        """Upgrading of remote bzrdirs is not supported yet."""
856
171
        return False
857
172
 
858
 
    def _get_config(self):
859
 
        return RemoteBzrDirConfig(self)
860
 
 
861
 
    def _get_config_store(self):
862
 
        return RemoteControlStore(self)
863
 
 
864
 
 
865
 
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
 
173
    def clone(self, url, revision_id=None, force_new_repo=False):
 
174
        self._ensure_real()
 
175
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
176
            force_new_repo=force_new_repo)
 
177
 
 
178
 
 
179
class RemoteRepositoryFormat(repository.RepositoryFormat):
866
180
    """Format for repositories accessed over a _SmartClient.
867
181
 
868
182
    Instances of this repository are represented by RemoteRepository
869
183
    instances.
870
184
 
871
 
    The RemoteRepositoryFormat is parameterized during construction
 
185
    The RemoteRepositoryFormat is parameterised during construction
872
186
    to reflect the capabilities of the real, remote format. Specifically
873
187
    the attributes rich_root_data and supports_tree_reference are set
874
188
    on a per instance basis, and are not set (and should not be) at
875
189
    the class level.
876
 
 
877
 
    :ivar _custom_format: If set, a specific concrete repository format that
878
 
        will be used when initializing a repository with this
879
 
        RemoteRepositoryFormat.
880
 
    :ivar _creating_repo: If set, the repository object that this
881
 
        RemoteRepositoryFormat was created for: it can be called into
882
 
        to obtain data like the network name.
883
190
    """
884
191
 
885
 
    _matchingbzrdir = RemoteBzrDirFormat()
886
 
    supports_full_versioned_files = True
887
 
    supports_leaving_lock = True
888
 
 
889
 
    def __init__(self):
890
 
        _mod_repository.RepositoryFormat.__init__(self)
891
 
        self._custom_format = None
892
 
        self._network_name = None
893
 
        self._creating_bzrdir = None
894
 
        self._revision_graph_can_have_wrong_parents = None
895
 
        self._supports_chks = None
896
 
        self._supports_external_lookups = None
897
 
        self._supports_tree_reference = None
898
 
        self._supports_funky_characters = None
899
 
        self._supports_nesting_repositories = None
900
 
        self._rich_root_data = None
901
 
 
902
 
    def __repr__(self):
903
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
904
 
            self._network_name)
905
 
 
906
 
    @property
907
 
    def fast_deltas(self):
908
 
        self._ensure_real()
909
 
        return self._custom_format.fast_deltas
910
 
 
911
 
    @property
912
 
    def rich_root_data(self):
913
 
        if self._rich_root_data is None:
914
 
            self._ensure_real()
915
 
            self._rich_root_data = self._custom_format.rich_root_data
916
 
        return self._rich_root_data
917
 
 
918
 
    @property
919
 
    def supports_chks(self):
920
 
        if self._supports_chks is None:
921
 
            self._ensure_real()
922
 
            self._supports_chks = self._custom_format.supports_chks
923
 
        return self._supports_chks
924
 
 
925
 
    @property
926
 
    def supports_external_lookups(self):
927
 
        if self._supports_external_lookups is None:
928
 
            self._ensure_real()
929
 
            self._supports_external_lookups = \
930
 
                self._custom_format.supports_external_lookups
931
 
        return self._supports_external_lookups
932
 
 
933
 
    @property
934
 
    def supports_funky_characters(self):
935
 
        if self._supports_funky_characters is None:
936
 
            self._ensure_real()
937
 
            self._supports_funky_characters = \
938
 
                self._custom_format.supports_funky_characters
939
 
        return self._supports_funky_characters
940
 
 
941
 
    @property
942
 
    def supports_nesting_repositories(self):
943
 
        if self._supports_nesting_repositories is None:
944
 
            self._ensure_real()
945
 
            self._supports_nesting_repositories = \
946
 
                self._custom_format.supports_nesting_repositories
947
 
        return self._supports_nesting_repositories
948
 
 
949
 
    @property
950
 
    def supports_tree_reference(self):
951
 
        if self._supports_tree_reference is None:
952
 
            self._ensure_real()
953
 
            self._supports_tree_reference = \
954
 
                self._custom_format.supports_tree_reference
955
 
        return self._supports_tree_reference
956
 
 
957
 
    @property
958
 
    def revision_graph_can_have_wrong_parents(self):
959
 
        if self._revision_graph_can_have_wrong_parents is None:
960
 
            self._ensure_real()
961
 
            self._revision_graph_can_have_wrong_parents = \
962
 
                self._custom_format.revision_graph_can_have_wrong_parents
963
 
        return self._revision_graph_can_have_wrong_parents
964
 
 
965
 
    def _vfs_initialize(self, a_bzrdir, shared):
966
 
        """Helper for common code in initialize."""
967
 
        if self._custom_format:
968
 
            # Custom format requested
969
 
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
970
 
        elif self._creating_bzrdir is not None:
971
 
            # Use the format that the repository we were created to back
972
 
            # has.
973
 
            prior_repo = self._creating_bzrdir.open_repository()
974
 
            prior_repo._ensure_real()
975
 
            result = prior_repo._real_repository._format.initialize(
976
 
                a_bzrdir, shared=shared)
977
 
        else:
978
 
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
979
 
            # support remote initialization.
980
 
            # We delegate to a real object at this point (as RemoteBzrDir
981
 
            # delegate to the repository format which would lead to infinite
982
 
            # recursion if we just called a_bzrdir.create_repository.
983
 
            a_bzrdir._ensure_real()
984
 
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
985
 
        if not isinstance(result, RemoteRepository):
986
 
            return self.open(a_bzrdir)
987
 
        else:
988
 
            return result
 
192
    _matchingbzrdir = RemoteBzrDirFormat
989
193
 
990
194
    def initialize(self, a_bzrdir, shared=False):
991
 
        # Being asked to create on a non RemoteBzrDir:
992
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
993
 
            return self._vfs_initialize(a_bzrdir, shared)
994
 
        medium = a_bzrdir._client._medium
995
 
        if medium._is_remote_before((1, 13)):
996
 
            return self._vfs_initialize(a_bzrdir, shared)
997
 
        # Creating on a remote bzr dir.
998
 
        # 1) get the network name to use.
999
 
        if self._custom_format:
1000
 
            network_name = self._custom_format.network_name()
1001
 
        elif self._network_name:
1002
 
            network_name = self._network_name
1003
 
        else:
1004
 
            # Select the current bzrlib default and ask for that.
1005
 
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1006
 
            reference_format = reference_bzrdir_format.repository_format
1007
 
            network_name = reference_format.network_name()
1008
 
        # 2) try direct creation via RPC
1009
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1010
 
        verb = 'BzrDir.create_repository'
1011
 
        if shared:
1012
 
            shared_str = 'True'
1013
 
        else:
1014
 
            shared_str = 'False'
1015
 
        try:
1016
 
            response = a_bzrdir._call(verb, path, network_name, shared_str)
1017
 
        except errors.UnknownSmartMethod:
1018
 
            # Fallback - use vfs methods
1019
 
            medium._remember_remote_is_before((1, 13))
1020
 
            return self._vfs_initialize(a_bzrdir, shared)
1021
 
        else:
1022
 
            # Turn the response into a RemoteRepository object.
1023
 
            format = response_tuple_to_repo_format(response[1:])
1024
 
            # Used to support creating a real format instance when needed.
1025
 
            format._creating_bzrdir = a_bzrdir
1026
 
            remote_repo = RemoteRepository(a_bzrdir, format)
1027
 
            format._creating_repo = remote_repo
1028
 
            return remote_repo
1029
 
 
 
195
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
196
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
 
197
        return a_bzrdir.create_repository(shared=shared)
 
198
    
1030
199
    def open(self, a_bzrdir):
1031
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
1032
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
200
        assert isinstance(a_bzrdir, RemoteBzrDir)
1033
201
        return a_bzrdir.open_repository()
1034
202
 
1035
 
    def _ensure_real(self):
1036
 
        if self._custom_format is None:
1037
 
            try:
1038
 
                self._custom_format = _mod_repository.network_format_registry.get(
1039
 
                    self._network_name)
1040
 
            except KeyError:
1041
 
                raise errors.UnknownFormatError(kind='repository',
1042
 
                    format=self._network_name)
1043
 
 
1044
 
    @property
1045
 
    def _fetch_order(self):
1046
 
        self._ensure_real()
1047
 
        return self._custom_format._fetch_order
1048
 
 
1049
 
    @property
1050
 
    def _fetch_uses_deltas(self):
1051
 
        self._ensure_real()
1052
 
        return self._custom_format._fetch_uses_deltas
1053
 
 
1054
 
    @property
1055
 
    def _fetch_reconcile(self):
1056
 
        self._ensure_real()
1057
 
        return self._custom_format._fetch_reconcile
1058
 
 
1059
203
    def get_format_description(self):
1060
 
        self._ensure_real()
1061
 
        return 'Remote: ' + self._custom_format.get_format_description()
 
204
        return 'bzr remote repository'
1062
205
 
1063
206
    def __eq__(self, other):
1064
 
        return self.__class__ is other.__class__
1065
 
 
1066
 
    def network_name(self):
1067
 
        if self._network_name:
1068
 
            return self._network_name
1069
 
        self._creating_repo._ensure_real()
1070
 
        return self._creating_repo._real_repository._format.network_name()
1071
 
 
1072
 
    @property
1073
 
    def pack_compresses(self):
1074
 
        self._ensure_real()
1075
 
        return self._custom_format.pack_compresses
1076
 
 
1077
 
    @property
1078
 
    def _serializer(self):
1079
 
        self._ensure_real()
1080
 
        return self._custom_format._serializer
1081
 
 
1082
 
 
1083
 
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1084
 
        lock._RelockDebugMixin):
 
207
        return self.__class__ == other.__class__
 
208
 
 
209
    def check_conversion_target(self, target_format):
 
210
        if self.rich_root_data and not target_format.rich_root_data:
 
211
            raise errors.BadConversionTarget(
 
212
                'Does not support rich root data.', target_format)
 
213
        if (self.supports_tree_reference and
 
214
            not getattr(target_format, 'supports_tree_reference', False)):
 
215
            raise errors.BadConversionTarget(
 
216
                'Does not support nested trees', target_format)
 
217
 
 
218
 
 
219
class RemoteRepository(object):
1085
220
    """Repository accessed over rpc.
1086
221
 
1087
222
    For the moment most operations are performed using local transport-backed
1090
225
 
1091
226
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1092
227
        """Create a RemoteRepository instance.
1093
 
 
 
228
        
1094
229
        :param remote_bzrdir: The bzrdir hosting this repository.
1095
230
        :param format: The RemoteFormat object to use.
1096
231
        :param real_repository: If not None, a local implementation of the
1105
240
            self._real_repository = None
1106
241
        self.bzrdir = remote_bzrdir
1107
242
        if _client is None:
1108
 
            self._client = remote_bzrdir._client
 
243
            self._client = client._SmartClient(self.bzrdir._medium)
1109
244
        else:
1110
245
            self._client = _client
1111
246
        self._format = format
1112
247
        self._lock_mode = None
1113
248
        self._lock_token = None
1114
 
        self._write_group_tokens = None
1115
249
        self._lock_count = 0
1116
250
        self._leave_lock = False
1117
 
        # Cache of revision parents; misses are cached during read locks, and
1118
 
        # write locks when no _real_repository has been set.
1119
 
        self._unstacked_provider = graph.CachingParentsProvider(
1120
 
            get_parent_map=self._get_parent_map_rpc)
1121
 
        self._unstacked_provider.disable_cache()
1122
 
        # For tests:
1123
 
        # These depend on the actual remote format, so force them off for
1124
 
        # maximum compatibility. XXX: In future these should depend on the
1125
 
        # remote repository instance, but this is irrelevant until we perform
1126
 
        # reconcile via an RPC call.
1127
 
        self._reconcile_does_inventory_gc = False
1128
 
        self._reconcile_fixes_text_parents = False
1129
 
        self._reconcile_backsup_inventory = False
1130
 
        self.base = self.bzrdir.transport.base
1131
 
        # Additional places to query for data.
1132
 
        self._fallback_repositories = []
1133
 
 
1134
 
    @property
1135
 
    def user_transport(self):
1136
 
        return self.bzrdir.user_transport
1137
 
 
1138
 
    @property
1139
 
    def control_transport(self):
1140
 
        # XXX: Normally you shouldn't directly get at the remote repository
1141
 
        # transport, but I'm not sure it's worth making this method
1142
 
        # optional -- mbp 2010-04-21
1143
 
        return self.bzrdir.get_repository_transport(None)
1144
 
 
1145
 
    def __str__(self):
1146
 
        return "%s(%s)" % (self.__class__.__name__, self.base)
1147
 
 
1148
 
    __repr__ = __str__
1149
 
 
1150
 
    def abort_write_group(self, suppress_errors=False):
1151
 
        """Complete a write group on the decorated repository.
1152
 
 
1153
 
        Smart methods perform operations in a single step so this API
1154
 
        is not really applicable except as a compatibility thunk
1155
 
        for older plugins that don't use e.g. the CommitBuilder
1156
 
        facility.
1157
 
 
1158
 
        :param suppress_errors: see Repository.abort_write_group.
1159
 
        """
1160
 
        if self._real_repository:
1161
 
            self._ensure_real()
1162
 
            return self._real_repository.abort_write_group(
1163
 
                suppress_errors=suppress_errors)
1164
 
        if not self.is_in_write_group():
1165
 
            if suppress_errors:
1166
 
                mutter('(suppressed) not in write group')
1167
 
                return
1168
 
            raise errors.BzrError("not in write group")
1169
 
        path = self.bzrdir._path_for_remote_call(self._client)
1170
 
        try:
1171
 
            response = self._call('Repository.abort_write_group', path,
1172
 
                self._lock_token, self._write_group_tokens)
1173
 
        except Exception, exc:
1174
 
            self._write_group = None
1175
 
            if not suppress_errors:
1176
 
                raise
1177
 
            mutter('abort_write_group failed')
1178
 
            log_exception_quietly()
1179
 
            note(gettext('bzr: ERROR (ignored): %s'), exc)
1180
 
        else:
1181
 
            if response != ('ok', ):
1182
 
                raise errors.UnexpectedSmartServerResponse(response)
1183
 
            self._write_group_tokens = None
1184
 
 
1185
 
    @property
1186
 
    def chk_bytes(self):
1187
 
        """Decorate the real repository for now.
1188
 
 
1189
 
        In the long term a full blown network facility is needed to avoid
1190
 
        creating a real repository object locally.
1191
 
        """
1192
 
        self._ensure_real()
1193
 
        return self._real_repository.chk_bytes
1194
 
 
1195
 
    def commit_write_group(self):
1196
 
        """Complete a write group on the decorated repository.
1197
 
 
1198
 
        Smart methods perform operations in a single step so this API
1199
 
        is not really applicable except as a compatibility thunk
1200
 
        for older plugins that don't use e.g. the CommitBuilder
1201
 
        facility.
1202
 
        """
1203
 
        if self._real_repository:
1204
 
            self._ensure_real()
1205
 
            return self._real_repository.commit_write_group()
1206
 
        if not self.is_in_write_group():
1207
 
            raise errors.BzrError("not in write group")
1208
 
        path = self.bzrdir._path_for_remote_call(self._client)
1209
 
        response = self._call('Repository.commit_write_group', path,
1210
 
            self._lock_token, self._write_group_tokens)
1211
 
        if response != ('ok', ):
1212
 
            raise errors.UnexpectedSmartServerResponse(response)
1213
 
        self._write_group_tokens = None
1214
 
 
1215
 
    def resume_write_group(self, tokens):
1216
 
        if self._real_repository:
1217
 
            return self._real_repository.resume_write_group(tokens)
1218
 
        path = self.bzrdir._path_for_remote_call(self._client)
1219
 
        try:
1220
 
            response = self._call('Repository.check_write_group', path,
1221
 
               self._lock_token, tokens)
1222
 
        except errors.UnknownSmartMethod:
1223
 
            self._ensure_real()
1224
 
            return self._real_repository.resume_write_group(tokens)
1225
 
        if response != ('ok', ):
1226
 
            raise errors.UnexpectedSmartServerResponse(response)
1227
 
        self._write_group_tokens = tokens
1228
 
 
1229
 
    def suspend_write_group(self):
1230
 
        if self._real_repository:
1231
 
            return self._real_repository.suspend_write_group()
1232
 
        ret = self._write_group_tokens or []
1233
 
        self._write_group_tokens = None
1234
 
        return ret
1235
 
 
1236
 
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1237
 
        self._ensure_real()
1238
 
        return self._real_repository.get_missing_parent_inventories(
1239
 
            check_for_missing_texts=check_for_missing_texts)
1240
 
 
1241
 
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1242
 
        self._ensure_real()
1243
 
        return self._real_repository.get_rev_id_for_revno(
1244
 
            revno, known_pair)
1245
 
 
1246
 
    def get_rev_id_for_revno(self, revno, known_pair):
1247
 
        """See Repository.get_rev_id_for_revno."""
1248
 
        path = self.bzrdir._path_for_remote_call(self._client)
1249
 
        try:
1250
 
            if self._client._medium._is_remote_before((1, 17)):
1251
 
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
1252
 
            response = self._call(
1253
 
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
1254
 
        except errors.UnknownSmartMethod:
1255
 
            self._client._medium._remember_remote_is_before((1, 17))
1256
 
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
1257
 
        if response[0] == 'ok':
1258
 
            return True, response[1]
1259
 
        elif response[0] == 'history-incomplete':
1260
 
            known_pair = response[1:3]
1261
 
            for fallback in self._fallback_repositories:
1262
 
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1263
 
                if found:
1264
 
                    return True, result
1265
 
                else:
1266
 
                    known_pair = result
1267
 
            # Not found in any fallbacks
1268
 
            return False, known_pair
1269
 
        else:
1270
 
            raise errors.UnexpectedSmartServerResponse(response)
1271
251
 
1272
252
    def _ensure_real(self):
1273
253
        """Ensure that there is a _real_repository set.
1274
254
 
1275
255
        Used before calls to self._real_repository.
1276
 
 
1277
 
        Note that _ensure_real causes many roundtrips to the server which are
1278
 
        not desirable, and prevents the use of smart one-roundtrip RPC's to
1279
 
        perform complex operations (such as accessing parent data, streaming
1280
 
        revisions etc). Adding calls to _ensure_real should only be done when
1281
 
        bringing up new functionality, adding fallbacks for smart methods that
1282
 
        require a fallback path, and never to replace an existing smart method
1283
 
        invocation. If in doubt chat to the bzr network team.
1284
256
        """
1285
 
        if self._real_repository is None:
1286
 
            if 'hpssvfs' in debug.debug_flags:
1287
 
                import traceback
1288
 
                warning('VFS Repository access triggered\n%s',
1289
 
                    ''.join(traceback.format_stack()))
1290
 
            self._unstacked_provider.missing_keys.clear()
 
257
        if not self._real_repository:
1291
258
            self.bzrdir._ensure_real()
1292
 
            self._set_real_repository(
1293
 
                self.bzrdir._real_bzrdir.open_repository())
1294
 
 
1295
 
    def _translate_error(self, err, **context):
1296
 
        self.bzrdir._translate_error(err, repository=self, **context)
1297
 
 
1298
 
    def find_text_key_references(self):
1299
 
        """Find the text key references within the repository.
1300
 
 
1301
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1302
 
            to whether they were referred to by the inventory of the
1303
 
            revision_id that they contain. The inventory texts from all present
1304
 
            revision ids are assessed to generate this report.
1305
 
        """
1306
 
        self._ensure_real()
1307
 
        return self._real_repository.find_text_key_references()
1308
 
 
1309
 
    def _generate_text_key_index(self):
1310
 
        """Generate a new text key index for the repository.
1311
 
 
1312
 
        This is an expensive function that will take considerable time to run.
1313
 
 
1314
 
        :return: A dict mapping (file_id, revision_id) tuples to a list of
1315
 
            parents, also (file_id, revision_id) tuples.
1316
 
        """
1317
 
        self._ensure_real()
1318
 
        return self._real_repository._generate_text_key_index()
1319
 
 
1320
 
    def _get_revision_graph(self, revision_id):
1321
 
        """Private method for using with old (< 1.2) servers to fallback."""
 
259
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
260
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
261
 
 
262
    def get_revision_graph(self, revision_id=None):
 
263
        """See Repository.get_revision_graph()."""
1322
264
        if revision_id is None:
1323
265
            revision_id = ''
1324
 
        elif _mod_revision.is_null(revision_id):
 
266
        elif revision_id == NULL_REVISION:
1325
267
            return {}
1326
268
 
1327
269
        path = self.bzrdir._path_for_remote_call(self._client)
1328
 
        response = self._call_expecting_body(
 
270
        assert type(revision_id) is str
 
271
        response = self._client.call_expecting_body(
1329
272
            'Repository.get_revision_graph', path, revision_id)
1330
 
        response_tuple, response_handler = response
1331
 
        if response_tuple[0] != 'ok':
1332
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1333
 
        coded = response_handler.read_body_bytes()
1334
 
        if coded == '':
1335
 
            # no revisions in this repository!
1336
 
            return {}
1337
 
        lines = coded.split('\n')
1338
 
        revision_graph = {}
1339
 
        for line in lines:
1340
 
            d = tuple(line.split())
1341
 
            revision_graph[d[0]] = d[1:]
1342
 
 
1343
 
        return revision_graph
1344
 
 
1345
 
    def _get_sink(self):
1346
 
        """See Repository._get_sink()."""
1347
 
        return RemoteStreamSink(self)
1348
 
 
1349
 
    def _get_source(self, to_format):
1350
 
        """Return a source for streaming from this repository."""
1351
 
        return RemoteStreamSource(self, to_format)
1352
 
 
1353
 
    @needs_read_lock
1354
 
    def get_file_graph(self):
1355
 
        return graph.Graph(self.texts)
1356
 
 
1357
 
    @needs_read_lock
 
273
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
274
            raise errors.UnexpectedSmartServerResponse(response[0])
 
275
        if response[0][0] == 'ok':
 
276
            coded = response[1].read_body_bytes()
 
277
            if coded == '':
 
278
                # no revisions in this repository!
 
279
                return {}
 
280
            lines = coded.split('\n')
 
281
            revision_graph = {}
 
282
            for line in lines:
 
283
                d = list(line.split())
 
284
                revision_graph[d[0]] = d[1:]
 
285
                
 
286
            return revision_graph
 
287
        else:
 
288
            response_body = response[1].read_body_bytes()
 
289
            assert response_body == ''
 
290
            raise NoSuchRevision(self, revision_id)
 
291
 
1358
292
    def has_revision(self, revision_id):
1359
 
        """True if this repository has a copy of the revision."""
1360
 
        # Copy of bzrlib.repository.Repository.has_revision
1361
 
        return revision_id in self.has_revisions((revision_id,))
1362
 
 
1363
 
    @needs_read_lock
1364
 
    def has_revisions(self, revision_ids):
1365
 
        """Probe to find out the presence of multiple revisions.
1366
 
 
1367
 
        :param revision_ids: An iterable of revision_ids.
1368
 
        :return: A set of the revision_ids that were present.
1369
 
        """
1370
 
        # Copy of bzrlib.repository.Repository.has_revisions
1371
 
        parent_map = self.get_parent_map(revision_ids)
1372
 
        result = set(parent_map)
1373
 
        if _mod_revision.NULL_REVISION in revision_ids:
1374
 
            result.add(_mod_revision.NULL_REVISION)
1375
 
        return result
1376
 
 
1377
 
    def _has_same_fallbacks(self, other_repo):
1378
 
        """Returns true if the repositories have the same fallbacks."""
1379
 
        # XXX: copied from Repository; it should be unified into a base class
1380
 
        # <https://bugs.launchpad.net/bzr/+bug/401622>
1381
 
        my_fb = self._fallback_repositories
1382
 
        other_fb = other_repo._fallback_repositories
1383
 
        if len(my_fb) != len(other_fb):
1384
 
            return False
1385
 
        for f, g in zip(my_fb, other_fb):
1386
 
            if not f.has_same_location(g):
1387
 
                return False
1388
 
        return True
1389
 
 
1390
 
    def has_same_location(self, other):
1391
 
        # TODO: Move to RepositoryBase and unify with the regular Repository
1392
 
        # one; unfortunately the tests rely on slightly different behaviour at
1393
 
        # present -- mbp 20090710
1394
 
        return (self.__class__ is other.__class__ and
1395
 
                self.bzrdir.transport.base == other.bzrdir.transport.base)
 
293
        """See Repository.has_revision()."""
 
294
        if revision_id is None:
 
295
            # The null revision is always present.
 
296
            return True
 
297
        path = self.bzrdir._path_for_remote_call(self._client)
 
298
        response = self._client.call('Repository.has_revision', path, revision_id)
 
299
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
300
        return response[0] == 'yes'
1396
301
 
1397
302
    def get_graph(self, other_repository=None):
1398
303
        """Return the graph for this repository format"""
1399
 
        parents_provider = self._make_parents_provider(other_repository)
1400
 
        return graph.Graph(parents_provider)
1401
 
 
1402
 
    @needs_read_lock
1403
 
    def get_known_graph_ancestry(self, revision_ids):
1404
 
        """Return the known graph for a set of revision ids and their ancestors.
1405
 
        """
1406
 
        st = static_tuple.StaticTuple
1407
 
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
1408
 
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1409
 
        return graph.GraphThunkIdsToKeys(known_graph)
 
304
        return self._real_repository.get_graph(other_repository)
1410
305
 
1411
306
    def gather_stats(self, revid=None, committers=None):
1412
307
        """See Repository.gather_stats()."""
1413
308
        path = self.bzrdir._path_for_remote_call(self._client)
1414
 
        # revid can be None to indicate no revisions, not just NULL_REVISION
1415
 
        if revid is None or _mod_revision.is_null(revid):
 
309
        if revid in (None, NULL_REVISION):
1416
310
            fmt_revid = ''
1417
311
        else:
1418
312
            fmt_revid = revid
1420
314
            fmt_committers = 'no'
1421
315
        else:
1422
316
            fmt_committers = 'yes'
1423
 
        response_tuple, response_handler = self._call_expecting_body(
 
317
        response = self._client.call_expecting_body(
1424
318
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
1425
 
        if response_tuple[0] != 'ok':
1426
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
319
        assert response[0][0] == 'ok', \
 
320
            'unexpected response code %s' % (response[0],)
1427
321
 
1428
 
        body = response_handler.read_body_bytes()
 
322
        body = response[1].read_body_bytes()
1429
323
        result = {}
1430
324
        for line in body.split('\n'):
1431
325
            if not line:
1439
333
 
1440
334
        return result
1441
335
 
1442
 
    def find_branches(self, using=False):
1443
 
        """See Repository.find_branches()."""
1444
 
        # should be an API call to the server.
1445
 
        self._ensure_real()
1446
 
        return self._real_repository.find_branches(using=using)
1447
 
 
1448
336
    def get_physical_lock_status(self):
1449
337
        """See Repository.get_physical_lock_status()."""
1450
 
        path = self.bzrdir._path_for_remote_call(self._client)
1451
 
        try:
1452
 
            response = self._call('Repository.get_physical_lock_status', path)
1453
 
        except errors.UnknownSmartMethod:
1454
 
            self._ensure_real()
1455
 
            return self._real_repository.get_physical_lock_status()
1456
 
        if response[0] not in ('yes', 'no'):
1457
 
            raise errors.UnexpectedSmartServerResponse(response)
1458
 
        return (response[0] == 'yes')
1459
 
 
1460
 
    def is_in_write_group(self):
1461
 
        """Return True if there is an open write group.
1462
 
 
1463
 
        write groups are only applicable locally for the smart server..
1464
 
        """
1465
 
        if self._write_group_tokens is not None:
1466
 
            return True
1467
 
        if self._real_repository:
1468
 
            return self._real_repository.is_in_write_group()
1469
 
 
1470
 
    def is_locked(self):
1471
 
        return self._lock_count >= 1
 
338
        return False
1472
339
 
1473
340
    def is_shared(self):
1474
341
        """See Repository.is_shared()."""
1475
342
        path = self.bzrdir._path_for_remote_call(self._client)
1476
 
        response = self._call('Repository.is_shared', path)
1477
 
        if response[0] not in ('yes', 'no'):
1478
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
343
        response = self._client.call('Repository.is_shared', path)
 
344
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
1479
345
        return response[0] == 'yes'
1480
346
 
1481
 
    def is_write_locked(self):
1482
 
        return self._lock_mode == 'w'
1483
 
 
1484
 
    def _warn_if_deprecated(self, branch=None):
1485
 
        # If we have a real repository, the check will be done there, if we
1486
 
        # don't the check will be done remotely.
1487
 
        pass
1488
 
 
1489
347
    def lock_read(self):
1490
 
        """Lock the repository for read operations.
1491
 
 
1492
 
        :return: A bzrlib.lock.LogicalLockResult.
1493
 
        """
1494
348
        # wrong eventually - want a local lock cache context
1495
349
        if not self._lock_mode:
1496
 
            self._note_lock('r')
1497
350
            self._lock_mode = 'r'
1498
351
            self._lock_count = 1
1499
 
            self._unstacked_provider.enable_cache(cache_misses=True)
1500
352
            if self._real_repository is not None:
1501
353
                self._real_repository.lock_read()
1502
 
            for repo in self._fallback_repositories:
1503
 
                repo.lock_read()
1504
354
        else:
1505
355
            self._lock_count += 1
1506
 
        return lock.LogicalLockResult(self.unlock)
1507
356
 
1508
357
    def _remote_lock_write(self, token):
1509
358
        path = self.bzrdir._path_for_remote_call(self._client)
1510
359
        if token is None:
1511
360
            token = ''
1512
 
        err_context = {'token': token}
1513
 
        response = self._call('Repository.lock_write', path, token,
1514
 
                              **err_context)
 
361
        response = self._client.call('Repository.lock_write', path, token)
1515
362
        if response[0] == 'ok':
1516
363
            ok, token = response
1517
364
            return token
 
365
        elif response[0] == 'LockContention':
 
366
            raise errors.LockContention('(remote lock)')
 
367
        elif response[0] == 'UnlockableTransport':
 
368
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
1518
369
        else:
1519
370
            raise errors.UnexpectedSmartServerResponse(response)
1520
371
 
1521
 
    def lock_write(self, token=None, _skip_rpc=False):
 
372
    def lock_write(self, token=None):
1522
373
        if not self._lock_mode:
1523
 
            self._note_lock('w')
1524
 
            if _skip_rpc:
1525
 
                if self._lock_token is not None:
1526
 
                    if token != self._lock_token:
1527
 
                        raise errors.TokenMismatch(token, self._lock_token)
1528
 
                self._lock_token = token
1529
 
            else:
1530
 
                self._lock_token = self._remote_lock_write(token)
1531
 
            # if self._lock_token is None, then this is something like packs or
1532
 
            # svn where we don't get to lock the repo, or a weave style repository
1533
 
            # where we cannot lock it over the wire and attempts to do so will
1534
 
            # fail.
 
374
            self._lock_token = self._remote_lock_write(token)
 
375
            assert self._lock_token, 'Remote server did not return a token!'
1535
376
            if self._real_repository is not None:
1536
377
                self._real_repository.lock_write(token=self._lock_token)
1537
378
            if token is not None:
1540
381
                self._leave_lock = False
1541
382
            self._lock_mode = 'w'
1542
383
            self._lock_count = 1
1543
 
            cache_misses = self._real_repository is None
1544
 
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1545
 
            for repo in self._fallback_repositories:
1546
 
                # Writes don't affect fallback repos
1547
 
                repo.lock_read()
1548
384
        elif self._lock_mode == 'r':
1549
385
            raise errors.ReadOnlyError(self)
1550
386
        else:
1551
387
            self._lock_count += 1
1552
 
        return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
 
388
        return self._lock_token
1553
389
 
1554
390
    def leave_lock_in_place(self):
1555
 
        if not self._lock_token:
1556
 
            raise NotImplementedError(self.leave_lock_in_place)
1557
391
        self._leave_lock = True
1558
392
 
1559
393
    def dont_leave_lock_in_place(self):
1560
 
        if not self._lock_token:
1561
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
1562
394
        self._leave_lock = False
1563
395
 
1564
396
    def _set_real_repository(self, repository):
1567
399
        :param repository: The repository to fallback to for non-hpss
1568
400
            implemented operations.
1569
401
        """
1570
 
        if self._real_repository is not None:
1571
 
            # Replacing an already set real repository.
1572
 
            # We cannot do this [currently] if the repository is locked -
1573
 
            # synchronised state might be lost.
1574
 
            if self.is_locked():
1575
 
                raise AssertionError('_real_repository is already set')
1576
 
        if isinstance(repository, RemoteRepository):
1577
 
            raise AssertionError()
 
402
        assert not isinstance(repository, RemoteRepository)
1578
403
        self._real_repository = repository
1579
 
        # three code paths happen here:
1580
 
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1581
 
        # up stacking. In this case self._fallback_repositories is [], and the
1582
 
        # real repo is already setup. Preserve the real repo and
1583
 
        # RemoteRepository.add_fallback_repository will avoid adding
1584
 
        # duplicates.
1585
 
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
1586
 
        # ensure_real is triggered from a branch, the real repository to
1587
 
        # set already has a matching list with separate instances, but
1588
 
        # as they are also RemoteRepositories we don't worry about making the
1589
 
        # lists be identical.
1590
 
        # 3) new servers, RemoteRepository.ensure_real is triggered before
1591
 
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1592
 
        # and need to populate it.
1593
 
        if (self._fallback_repositories and
1594
 
            len(self._real_repository._fallback_repositories) !=
1595
 
            len(self._fallback_repositories)):
1596
 
            if len(self._real_repository._fallback_repositories):
1597
 
                raise AssertionError(
1598
 
                    "cannot cleanly remove existing _fallback_repositories")
1599
 
        for fb in self._fallback_repositories:
1600
 
            self._real_repository.add_fallback_repository(fb)
1601
404
        if self._lock_mode == 'w':
1602
405
            # if we are already locked, the real repository must be able to
1603
406
            # acquire the lock with our token.
1604
407
            self._real_repository.lock_write(self._lock_token)
1605
408
        elif self._lock_mode == 'r':
1606
409
            self._real_repository.lock_read()
1607
 
        if self._write_group_tokens is not None:
1608
 
            # if we are already in a write group, resume it
1609
 
            self._real_repository.resume_write_group(self._write_group_tokens)
1610
 
            self._write_group_tokens = None
1611
 
 
1612
 
    def start_write_group(self):
1613
 
        """Start a write group on the decorated repository.
1614
 
 
1615
 
        Smart methods perform operations in a single step so this API
1616
 
        is not really applicable except as a compatibility thunk
1617
 
        for older plugins that don't use e.g. the CommitBuilder
1618
 
        facility.
1619
 
        """
1620
 
        if self._real_repository:
1621
 
            self._ensure_real()
1622
 
            return self._real_repository.start_write_group()
1623
 
        if not self.is_write_locked():
1624
 
            raise errors.NotWriteLocked(self)
1625
 
        if self._write_group_tokens is not None:
1626
 
            raise errors.BzrError('already in a write group')
1627
 
        path = self.bzrdir._path_for_remote_call(self._client)
1628
 
        try:
1629
 
            response = self._call('Repository.start_write_group', path,
1630
 
                self._lock_token)
1631
 
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1632
 
            self._ensure_real()
1633
 
            return self._real_repository.start_write_group()
1634
 
        if response[0] != 'ok':
1635
 
            raise errors.UnexpectedSmartServerResponse(response)
1636
 
        self._write_group_tokens = response[1]
1637
410
 
1638
411
    def _unlock(self, token):
1639
412
        path = self.bzrdir._path_for_remote_call(self._client)
1640
 
        if not token:
1641
 
            # with no token the remote repository is not persistently locked.
1642
 
            return
1643
 
        err_context = {'token': token}
1644
 
        response = self._call('Repository.unlock', path, token,
1645
 
                              **err_context)
 
413
        response = self._client.call('Repository.unlock', path, token)
1646
414
        if response == ('ok',):
1647
415
            return
 
416
        elif response[0] == 'TokenMismatch':
 
417
            raise errors.TokenMismatch(token, '(remote token)')
1648
418
        else:
1649
419
            raise errors.UnexpectedSmartServerResponse(response)
1650
420
 
1651
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1652
421
    def unlock(self):
 
422
        self._lock_count -= 1
1653
423
        if not self._lock_count:
1654
 
            return lock.cant_unlock_not_held(self)
1655
 
        self._lock_count -= 1
1656
 
        if self._lock_count > 0:
1657
 
            return
1658
 
        self._unstacked_provider.disable_cache()
1659
 
        old_mode = self._lock_mode
1660
 
        self._lock_mode = None
1661
 
        try:
1662
 
            # The real repository is responsible at present for raising an
1663
 
            # exception if it's in an unfinished write group.  However, it
1664
 
            # normally will *not* actually remove the lock from disk - that's
1665
 
            # done by the server on receiving the Repository.unlock call.
1666
 
            # This is just to let the _real_repository stay up to date.
 
424
            mode = self._lock_mode
 
425
            self._lock_mode = None
1667
426
            if self._real_repository is not None:
1668
427
                self._real_repository.unlock()
1669
 
            elif self._write_group_tokens is not None:
1670
 
                self.abort_write_group()
1671
 
        finally:
1672
 
            # The rpc-level lock should be released even if there was a
1673
 
            # problem releasing the vfs-based lock.
1674
 
            if old_mode == 'w':
 
428
            if mode != 'w':
1675
429
                # Only write-locked repositories need to make a remote method
1676
 
                # call to perform the unlock.
1677
 
                old_token = self._lock_token
1678
 
                self._lock_token = None
1679
 
                if not self._leave_lock:
1680
 
                    self._unlock(old_token)
1681
 
        # Fallbacks are always 'lock_read()' so we don't pay attention to
1682
 
        # self._leave_lock
1683
 
        for repo in self._fallback_repositories:
1684
 
            repo.unlock()
 
430
                # call to perfom the unlock.
 
431
                return
 
432
            assert self._lock_token, 'Locked, but no token!'
 
433
            token = self._lock_token
 
434
            self._lock_token = None
 
435
            if not self._leave_lock:
 
436
                self._unlock(token)
1685
437
 
1686
438
    def break_lock(self):
1687
439
        # should hand off to the network
1688
 
        path = self.bzrdir._path_for_remote_call(self._client)
1689
 
        try:
1690
 
            response = self._call("Repository.break_lock", path)
1691
 
        except errors.UnknownSmartMethod:
1692
 
            self._ensure_real()
1693
 
            return self._real_repository.break_lock()
1694
 
        if response != ('ok',):
1695
 
            raise errors.UnexpectedSmartServerResponse(response)
 
440
        self._ensure_real()
 
441
        return self._real_repository.break_lock()
1696
442
 
1697
443
    def _get_tarball(self, compression):
1698
 
        """Return a TemporaryFile containing a repository tarball.
1699
 
 
1700
 
        Returns None if the server does not support sending tarballs.
1701
 
        """
 
444
        """Return a TemporaryFile containing a repository tarball"""
1702
445
        import tempfile
1703
446
        path = self.bzrdir._path_for_remote_call(self._client)
1704
 
        try:
1705
 
            response, protocol = self._call_expecting_body(
1706
 
                'Repository.tarball', path, compression)
1707
 
        except errors.UnknownSmartMethod:
1708
 
            protocol.cancel_read_body()
1709
 
            return None
 
447
        response, protocol = self._client.call_expecting_body(
 
448
            'Repository.tarball', path, compression)
 
449
        assert response[0] in ('ok', 'failure'), \
 
450
            'unexpected response code %s' % (response,)
1710
451
        if response[0] == 'ok':
1711
452
            # Extract the tarball and return it
1712
453
            t = tempfile.NamedTemporaryFile()
1714
455
            t.write(protocol.read_body_bytes())
1715
456
            t.seek(0)
1716
457
            return t
1717
 
        raise errors.UnexpectedSmartServerResponse(response)
 
458
        else:
 
459
            raise errors.SmartServerError(error_code=response)
1718
460
 
1719
 
    @needs_read_lock
1720
461
    def sprout(self, to_bzrdir, revision_id=None):
1721
 
        """Create a descendent repository for new development.
1722
 
 
1723
 
        Unlike clone, this does not copy the settings of the repository.
1724
 
        """
1725
 
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1726
 
        dest_repo.fetch(self, revision_id=revision_id)
1727
 
        return dest_repo
1728
 
 
1729
 
    def _create_sprouting_repo(self, a_bzrdir, shared):
1730
 
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1731
 
            # use target default format.
1732
 
            dest_repo = a_bzrdir.create_repository()
1733
 
        else:
1734
 
            # Most control formats need the repository to be specifically
1735
 
            # created, but on some old all-in-one formats it's not needed
1736
 
            try:
1737
 
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1738
 
            except errors.UninitializableFormat:
1739
 
                dest_repo = a_bzrdir.open_repository()
1740
 
        return dest_repo
 
462
        # TODO: Option to control what format is created?
 
463
        to_repo = to_bzrdir.create_repository()
 
464
        self._copy_repository_tarball(to_repo, revision_id)
 
465
        return to_repo
1741
466
 
1742
467
    ### These methods are just thin shims to the VFS object for now.
1743
468
 
1744
 
    @needs_read_lock
1745
469
    def revision_tree(self, revision_id):
1746
 
        revision_id = _mod_revision.ensure_null(revision_id)
1747
 
        if revision_id == _mod_revision.NULL_REVISION:
1748
 
            return InventoryRevisionTree(self,
1749
 
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
1750
 
        else:
1751
 
            return list(self.revision_trees([revision_id]))[0]
 
470
        self._ensure_real()
 
471
        return self._real_repository.revision_tree(revision_id)
1752
472
 
1753
473
    def get_serializer_format(self):
1754
 
        path = self.bzrdir._path_for_remote_call(self._client)
1755
 
        try:
1756
 
            response = self._call('VersionedFileRepository.get_serializer_format',
1757
 
                path)
1758
 
        except errors.UnknownSmartMethod:
1759
 
            self._ensure_real()
1760
 
            return self._real_repository.get_serializer_format()
1761
 
        if response[0] != 'ok':
1762
 
            raise errors.UnexpectedSmartServerResponse(response)
1763
 
        return response[1]
 
474
        self._ensure_real()
 
475
        return self._real_repository.get_serializer_format()
1764
476
 
1765
477
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1766
478
                           timezone=None, committer=None, revprops=None,
1767
 
                           revision_id=None, lossy=False):
 
479
                           revision_id=None):
1768
480
        # FIXME: It ought to be possible to call this without immediately
1769
481
        # triggering _ensure_real.  For now it's the easiest thing to do.
1770
482
        self._ensure_real()
1771
 
        real_repo = self._real_repository
1772
 
        builder = real_repo.get_commit_builder(branch, parents,
 
483
        builder = self._real_repository.get_commit_builder(branch, parents,
1773
484
                config, timestamp=timestamp, timezone=timezone,
1774
 
                committer=committer, revprops=revprops,
1775
 
                revision_id=revision_id, lossy=lossy)
 
485
                committer=committer, revprops=revprops, revision_id=revision_id)
 
486
        # Make the builder use this RemoteRepository rather than the real one.
 
487
        builder.repository = self
1776
488
        return builder
1777
489
 
1778
 
    def add_fallback_repository(self, repository):
1779
 
        """Add a repository to use for looking up data not held locally.
1780
 
 
1781
 
        :param repository: A repository.
1782
 
        """
1783
 
        if not self._format.supports_external_lookups:
1784
 
            raise errors.UnstackableRepositoryFormat(
1785
 
                self._format.network_name(), self.base)
1786
 
        # We need to accumulate additional repositories here, to pass them in
1787
 
        # on various RPC's.
1788
 
        #
1789
 
        # Make the check before we lock: this raises an exception.
1790
 
        self._check_fallback_repository(repository)
1791
 
        if self.is_locked():
1792
 
            # We will call fallback.unlock() when we transition to the unlocked
1793
 
            # state, so always add a lock here. If a caller passes us a locked
1794
 
            # repository, they are responsible for unlocking it later.
1795
 
            repository.lock_read()
1796
 
        self._fallback_repositories.append(repository)
1797
 
        # If self._real_repository was parameterised already (e.g. because a
1798
 
        # _real_branch had its get_stacked_on_url method called), then the
1799
 
        # repository to be added may already be in the _real_repositories list.
1800
 
        if self._real_repository is not None:
1801
 
            fallback_locations = [repo.user_url for repo in
1802
 
                self._real_repository._fallback_repositories]
1803
 
            if repository.user_url not in fallback_locations:
1804
 
                self._real_repository.add_fallback_repository(repository)
1805
 
 
1806
 
    def _check_fallback_repository(self, repository):
1807
 
        """Check that this repository can fallback to repository safely.
1808
 
 
1809
 
        Raise an error if not.
1810
 
 
1811
 
        :param repository: A repository to fallback to.
1812
 
        """
1813
 
        return _mod_repository.InterRepository._assert_same_model(
1814
 
            self, repository)
1815
 
 
 
490
    @needs_write_lock
1816
491
    def add_inventory(self, revid, inv, parents):
1817
492
        self._ensure_real()
1818
493
        return self._real_repository.add_inventory(revid, inv, parents)
1819
494
 
1820
 
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1821
 
            parents, basis_inv=None, propagate_caches=False):
1822
 
        self._ensure_real()
1823
 
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
1824
 
            delta, new_revision_id, parents, basis_inv=basis_inv,
1825
 
            propagate_caches=propagate_caches)
1826
 
 
 
495
    @needs_write_lock
1827
496
    def add_revision(self, rev_id, rev, inv=None, config=None):
1828
497
        self._ensure_real()
1829
498
        return self._real_repository.add_revision(
1831
500
 
1832
501
    @needs_read_lock
1833
502
    def get_inventory(self, revision_id):
1834
 
        return list(self.iter_inventories([revision_id]))[0]
1835
 
 
1836
 
    def _iter_inventories_rpc(self, revision_ids, ordering):
1837
 
        if ordering is None:
1838
 
            ordering = 'unordered'
1839
 
        path = self.bzrdir._path_for_remote_call(self._client)
1840
 
        body = "\n".join(revision_ids)
1841
 
        response_tuple, response_handler = (
1842
 
            self._call_with_body_bytes_expecting_body(
1843
 
                "VersionedFileRepository.get_inventories",
1844
 
                (path, ordering), body))
1845
 
        if response_tuple[0] != "ok":
1846
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1847
 
        deserializer = inventory_delta.InventoryDeltaDeserializer()
1848
 
        byte_stream = response_handler.read_streamed_body()
1849
 
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
1850
 
        if decoded is None:
1851
 
            # no results whatsoever
1852
 
            return
1853
 
        src_format, stream = decoded
1854
 
        if src_format.network_name() != self._format.network_name():
1855
 
            raise AssertionError(
1856
 
                "Mismatched RemoteRepository and stream src %r, %r" % (
1857
 
                src_format.network_name(), self._format.network_name()))
1858
 
        # ignore the src format, it's not really relevant
1859
 
        prev_inv = Inventory(root_id=None,
1860
 
            revision_id=_mod_revision.NULL_REVISION)
1861
 
        # there should be just one substream, with inventory deltas
1862
 
        substream_kind, substream = stream.next()
1863
 
        if substream_kind != "inventory-deltas":
1864
 
            raise AssertionError(
1865
 
                 "Unexpected stream %r received" % substream_kind)
1866
 
        for record in substream:
1867
 
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
1868
 
                deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1869
 
            if parent_id != prev_inv.revision_id:
1870
 
                raise AssertionError("invalid base %r != %r" % (parent_id,
1871
 
                    prev_inv.revision_id))
1872
 
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1873
 
            yield inv, inv.revision_id
1874
 
            prev_inv = inv
1875
 
 
1876
 
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
1877
503
        self._ensure_real()
1878
 
        return self._real_repository._iter_inventories(revision_ids, ordering)
1879
 
 
1880
 
    def iter_inventories(self, revision_ids, ordering=None):
1881
 
        """Get many inventories by revision_ids.
1882
 
 
1883
 
        This will buffer some or all of the texts used in constructing the
1884
 
        inventories in memory, but will only parse a single inventory at a
1885
 
        time.
1886
 
 
1887
 
        :param revision_ids: The expected revision ids of the inventories.
1888
 
        :param ordering: optional ordering, e.g. 'topological'.  If not
1889
 
            specified, the order of revision_ids will be preserved (by
1890
 
            buffering if necessary).
1891
 
        :return: An iterator of inventories.
1892
 
        """
1893
 
        if ((None in revision_ids)
1894
 
            or (_mod_revision.NULL_REVISION in revision_ids)):
1895
 
            raise ValueError('cannot get null revision inventory')
1896
 
        for inv, revid in self._iter_inventories(revision_ids, ordering):
1897
 
            if inv is None:
1898
 
                raise errors.NoSuchRevision(self, revid)
1899
 
            yield inv
1900
 
 
1901
 
    def _iter_inventories(self, revision_ids, ordering=None):
1902
 
        if len(revision_ids) == 0:
1903
 
            return
1904
 
        missing = set(revision_ids)
1905
 
        if ordering is None:
1906
 
            order_as_requested = True
1907
 
            invs = {}
1908
 
            order = list(revision_ids)
1909
 
            order.reverse()
1910
 
            next_revid = order.pop()
1911
 
        else:
1912
 
            order_as_requested = False
1913
 
            if ordering != 'unordered' and self._fallback_repositories:
1914
 
                raise ValueError('unsupported ordering %r' % ordering)
1915
 
        iter_inv_fns = [self._iter_inventories_rpc] + [
1916
 
            fallback._iter_inventories for fallback in
1917
 
            self._fallback_repositories]
1918
 
        try:
1919
 
            for iter_inv in iter_inv_fns:
1920
 
                request = [revid for revid in revision_ids if revid in missing]
1921
 
                for inv, revid in iter_inv(request, ordering):
1922
 
                    if inv is None:
1923
 
                        continue
1924
 
                    missing.remove(inv.revision_id)
1925
 
                    if ordering != 'unordered':
1926
 
                        invs[revid] = inv
1927
 
                    else:
1928
 
                        yield inv, revid
1929
 
                if order_as_requested:
1930
 
                    # Yield as many results as we can while preserving order.
1931
 
                    while next_revid in invs:
1932
 
                        inv = invs.pop(next_revid)
1933
 
                        yield inv, inv.revision_id
1934
 
                        try:
1935
 
                            next_revid = order.pop()
1936
 
                        except IndexError:
1937
 
                            # We still want to fully consume the stream, just
1938
 
                            # in case it is not actually finished at this point
1939
 
                            next_revid = None
1940
 
                            break
1941
 
        except errors.UnknownSmartMethod:
1942
 
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
1943
 
                yield inv, revid
1944
 
            return
1945
 
        # Report missing
1946
 
        if order_as_requested:
1947
 
            if next_revid is not None:
1948
 
                yield None, next_revid
1949
 
            while order:
1950
 
                revid = order.pop()
1951
 
                yield invs.get(revid), revid
1952
 
        else:
1953
 
            while missing:
1954
 
                yield None, missing.pop()
 
504
        return self._real_repository.get_inventory(revision_id)
1955
505
 
1956
506
    @needs_read_lock
1957
507
    def get_revision(self, revision_id):
1958
 
        return self.get_revisions([revision_id])[0]
 
508
        self._ensure_real()
 
509
        return self._real_repository.get_revision(revision_id)
 
510
 
 
511
    @property
 
512
    def weave_store(self):
 
513
        self._ensure_real()
 
514
        return self._real_repository.weave_store
1959
515
 
1960
516
    def get_transaction(self):
1961
517
        self._ensure_real()
1963
519
 
1964
520
    @needs_read_lock
1965
521
    def clone(self, a_bzrdir, revision_id=None):
1966
 
        dest_repo = self._create_sprouting_repo(
1967
 
            a_bzrdir, shared=self.is_shared())
1968
 
        self.copy_content_into(dest_repo, revision_id)
1969
 
        return dest_repo
 
522
        self._ensure_real()
 
523
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1970
524
 
1971
525
    def make_working_trees(self):
1972
 
        """See Repository.make_working_trees"""
1973
 
        path = self.bzrdir._path_for_remote_call(self._client)
1974
 
        try:
1975
 
            response = self._call('Repository.make_working_trees', path)
1976
 
        except errors.UnknownSmartMethod:
1977
 
            self._ensure_real()
1978
 
            return self._real_repository.make_working_trees()
1979
 
        if response[0] not in ('yes', 'no'):
1980
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
1981
 
        return response[0] == 'yes'
1982
 
 
1983
 
    def refresh_data(self):
1984
 
        """Re-read any data needed to synchronise with disk.
1985
 
 
1986
 
        This method is intended to be called after another repository instance
1987
 
        (such as one used by a smart server) has inserted data into the
1988
 
        repository. On all repositories this will work outside of write groups.
1989
 
        Some repository formats (pack and newer for bzrlib native formats)
1990
 
        support refresh_data inside write groups. If called inside a write
1991
 
        group on a repository that does not support refreshing in a write group
1992
 
        IsInWriteGroupError will be raised.
1993
 
        """
1994
 
        if self._real_repository is not None:
1995
 
            self._real_repository.refresh_data()
1996
 
 
1997
 
    def revision_ids_to_search_result(self, result_set):
1998
 
        """Convert a set of revision ids to a graph SearchResult."""
1999
 
        result_parents = set()
2000
 
        for parents in self.get_graph().get_parent_map(
2001
 
            result_set).itervalues():
2002
 
            result_parents.update(parents)
2003
 
        included_keys = result_set.intersection(result_parents)
2004
 
        start_keys = result_set.difference(included_keys)
2005
 
        exclude_keys = result_parents.difference(result_set)
2006
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
2007
 
            len(result_set), result_set)
2008
 
        return result
2009
 
 
2010
 
    @needs_read_lock
2011
 
    def search_missing_revision_ids(self, other,
2012
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2013
 
            find_ghosts=True, revision_ids=None, if_present_ids=None,
2014
 
            limit=None):
2015
 
        """Return the revision ids that other has that this does not.
2016
 
 
2017
 
        These are returned in topological order.
2018
 
 
2019
 
        revision_id: only return revision ids included by revision_id.
2020
 
        """
2021
 
        if symbol_versioning.deprecated_passed(revision_id):
2022
 
            symbol_versioning.warn(
2023
 
                'search_missing_revision_ids(revision_id=...) was '
2024
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
2025
 
                DeprecationWarning, stacklevel=2)
2026
 
            if revision_ids is not None:
2027
 
                raise AssertionError(
2028
 
                    'revision_ids is mutually exclusive with revision_id')
2029
 
            if revision_id is not None:
2030
 
                revision_ids = [revision_id]
2031
 
        inter_repo = _mod_repository.InterRepository.get(other, self)
2032
 
        return inter_repo.search_missing_revision_ids(
2033
 
            find_ghosts=find_ghosts, revision_ids=revision_ids,
2034
 
            if_present_ids=if_present_ids, limit=limit)
2035
 
 
2036
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
2037
 
            fetch_spec=None):
2038
 
        # No base implementation to use as RemoteRepository is not a subclass
2039
 
        # of Repository; so this is a copy of Repository.fetch().
2040
 
        if fetch_spec is not None and revision_id is not None:
2041
 
            raise AssertionError(
2042
 
                "fetch_spec and revision_id are mutually exclusive.")
2043
 
        if self.is_in_write_group():
2044
 
            raise errors.InternalBzrError(
2045
 
                "May not fetch while in a write group.")
2046
 
        # fast path same-url fetch operations
2047
 
        if (self.has_same_location(source)
2048
 
            and fetch_spec is None
2049
 
            and self._has_same_fallbacks(source)):
2050
 
            # check that last_revision is in 'from' and then return a
2051
 
            # no-operation.
2052
 
            if (revision_id is not None and
2053
 
                not _mod_revision.is_null(revision_id)):
2054
 
                self.get_revision(revision_id)
2055
 
            return 0, []
2056
 
        # if there is no specific appropriate InterRepository, this will get
2057
 
        # the InterRepository base class, which raises an
2058
 
        # IncompatibleRepositories when asked to fetch.
2059
 
        inter = _mod_repository.InterRepository.get(source, self)
2060
 
        if (fetch_spec is not None and
2061
 
            not getattr(inter, "supports_fetch_spec", False)):
2062
 
            raise errors.UnsupportedOperation(
2063
 
                "fetch_spec not supported for %r" % inter)
2064
 
        return inter.fetch(revision_id=revision_id,
2065
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
526
        """RemoteRepositories never create working trees by default."""
 
527
        return False
 
528
 
 
529
    def fetch(self, source, revision_id=None, pb=None):
 
530
        self._ensure_real()
 
531
        return self._real_repository.fetch(
 
532
            source, revision_id=revision_id, pb=pb)
2066
533
 
2067
534
    def create_bundle(self, target, base, fileobj, format=None):
2068
535
        self._ensure_real()
2069
536
        self._real_repository.create_bundle(target, base, fileobj, format)
2070
537
 
 
538
    @property
 
539
    def control_weaves(self):
 
540
        self._ensure_real()
 
541
        return self._real_repository.control_weaves
 
542
 
2071
543
    @needs_read_lock
2072
 
    @symbol_versioning.deprecated_method(
2073
 
        symbol_versioning.deprecated_in((2, 4, 0)))
2074
544
    def get_ancestry(self, revision_id, topo_sorted=True):
2075
545
        self._ensure_real()
2076
546
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
2077
547
 
 
548
    @needs_read_lock
 
549
    def get_inventory_weave(self):
 
550
        self._ensure_real()
 
551
        return self._real_repository.get_inventory_weave()
 
552
 
2078
553
    def fileids_altered_by_revision_ids(self, revision_ids):
2079
554
        self._ensure_real()
2080
555
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2081
556
 
2082
 
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2083
 
        self._ensure_real()
2084
 
        return self._real_repository._get_versioned_file_checker(
2085
 
            revisions, revision_versions_cache)
2086
 
 
2087
 
    def _iter_files_bytes_rpc(self, desired_files, absent):
2088
 
        path = self.bzrdir._path_for_remote_call(self._client)
2089
 
        lines = []
2090
 
        identifiers = []
2091
 
        for (file_id, revid, identifier) in desired_files:
2092
 
            lines.append("%s\0%s" % (
2093
 
                osutils.safe_file_id(file_id),
2094
 
                osutils.safe_revision_id(revid)))
2095
 
            identifiers.append(identifier)
2096
 
        (response_tuple, response_handler) = (
2097
 
            self._call_with_body_bytes_expecting_body(
2098
 
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2099
 
        if response_tuple != ('ok', ):
2100
 
            response_handler.cancel_read_body()
2101
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2102
 
        byte_stream = response_handler.read_streamed_body()
2103
 
        def decompress_stream(start, byte_stream, unused):
2104
 
            decompressor = zlib.decompressobj()
2105
 
            yield decompressor.decompress(start)
2106
 
            while decompressor.unused_data == "":
2107
 
                try:
2108
 
                    data = byte_stream.next()
2109
 
                except StopIteration:
2110
 
                    break
2111
 
                yield decompressor.decompress(data)
2112
 
            yield decompressor.flush()
2113
 
            unused.append(decompressor.unused_data)
2114
 
        unused = ""
2115
 
        while True:
2116
 
            while not "\n" in unused:
2117
 
                unused += byte_stream.next()
2118
 
            header, rest = unused.split("\n", 1)
2119
 
            args = header.split("\0")
2120
 
            if args[0] == "absent":
2121
 
                absent[identifiers[int(args[3])]] = (args[1], args[2])
2122
 
                unused = rest
2123
 
                continue
2124
 
            elif args[0] == "ok":
2125
 
                idx = int(args[1])
2126
 
            else:
2127
 
                raise errors.UnexpectedSmartServerResponse(args)
2128
 
            unused_chunks = []
2129
 
            yield (identifiers[idx],
2130
 
                decompress_stream(rest, byte_stream, unused_chunks))
2131
 
            unused = "".join(unused_chunks)
2132
 
 
2133
 
    def iter_files_bytes(self, desired_files):
2134
 
        """See Repository.iter_file_bytes.
2135
 
        """
2136
 
        try:
2137
 
            absent = {}
2138
 
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2139
 
                    desired_files, absent):
2140
 
                yield identifier, bytes_iterator
2141
 
            for fallback in self._fallback_repositories:
2142
 
                if not absent:
2143
 
                    break
2144
 
                desired_files = [(key[0], key[1], identifier) for
2145
 
                    (identifier, key) in absent.iteritems()]
2146
 
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2147
 
                    del absent[identifier]
2148
 
                    yield identifier, bytes_iterator
2149
 
            if absent:
2150
 
                # There may be more missing items, but raise an exception
2151
 
                # for just one.
2152
 
                missing_identifier = absent.keys()[0]
2153
 
                missing_key = absent[missing_identifier]
2154
 
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
2155
 
                    file_id=missing_key[0])
2156
 
        except errors.UnknownSmartMethod:
2157
 
            self._ensure_real()
2158
 
            for (identifier, bytes_iterator) in (
2159
 
                self._real_repository.iter_files_bytes(desired_files)):
2160
 
                yield identifier, bytes_iterator
2161
 
 
2162
 
    def get_cached_parent_map(self, revision_ids):
2163
 
        """See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2164
 
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
2165
 
 
2166
 
    def get_parent_map(self, revision_ids):
2167
 
        """See bzrlib.Graph.get_parent_map()."""
2168
 
        return self._make_parents_provider().get_parent_map(revision_ids)
2169
 
 
2170
 
    def _get_parent_map_rpc(self, keys):
2171
 
        """Helper for get_parent_map that performs the RPC."""
2172
 
        medium = self._client._medium
2173
 
        if medium._is_remote_before((1, 2)):
2174
 
            # We already found out that the server can't understand
2175
 
            # Repository.get_parent_map requests, so just fetch the whole
2176
 
            # graph.
2177
 
            #
2178
 
            # Note that this reads the whole graph, when only some keys are
2179
 
            # wanted.  On this old server there's no way (?) to get them all
2180
 
            # in one go, and the user probably will have seen a warning about
2181
 
            # the server being old anyhow.
2182
 
            rg = self._get_revision_graph(None)
2183
 
            # There is an API discrepancy between get_parent_map and
2184
 
            # get_revision_graph. Specifically, a "key:()" pair in
2185
 
            # get_revision_graph just means a node has no parents. For
2186
 
            # "get_parent_map" it means the node is a ghost. So fix up the
2187
 
            # graph to correct this.
2188
 
            #   https://bugs.launchpad.net/bzr/+bug/214894
2189
 
            # There is one other "bug" which is that ghosts in
2190
 
            # get_revision_graph() are not returned at all. But we won't worry
2191
 
            # about that for now.
2192
 
            for node_id, parent_ids in rg.iteritems():
2193
 
                if parent_ids == ():
2194
 
                    rg[node_id] = (NULL_REVISION,)
2195
 
            rg[NULL_REVISION] = ()
2196
 
            return rg
2197
 
 
2198
 
        keys = set(keys)
2199
 
        if None in keys:
2200
 
            raise ValueError('get_parent_map(None) is not valid')
2201
 
        if NULL_REVISION in keys:
2202
 
            keys.discard(NULL_REVISION)
2203
 
            found_parents = {NULL_REVISION:()}
2204
 
            if not keys:
2205
 
                return found_parents
2206
 
        else:
2207
 
            found_parents = {}
2208
 
        # TODO(Needs analysis): We could assume that the keys being requested
2209
 
        # from get_parent_map are in a breadth first search, so typically they
2210
 
        # will all be depth N from some common parent, and we don't have to
2211
 
        # have the server iterate from the root parent, but rather from the
2212
 
        # keys we're searching; and just tell the server the keyspace we
2213
 
        # already have; but this may be more traffic again.
2214
 
 
2215
 
        # Transform self._parents_map into a search request recipe.
2216
 
        # TODO: Manage this incrementally to avoid covering the same path
2217
 
        # repeatedly. (The server will have to on each request, but the less
2218
 
        # work done the better).
2219
 
        #
2220
 
        # Negative caching notes:
2221
 
        # new server sends missing when a request including the revid
2222
 
        # 'include-missing:' is present in the request.
2223
 
        # missing keys are serialised as missing:X, and we then call
2224
 
        # provider.note_missing(X) for-all X
2225
 
        parents_map = self._unstacked_provider.get_cached_map()
2226
 
        if parents_map is None:
2227
 
            # Repository is not locked, so there's no cache.
2228
 
            parents_map = {}
2229
 
        if _DEFAULT_SEARCH_DEPTH <= 0:
2230
 
            (start_set, stop_keys,
2231
 
             key_count) = vf_search.search_result_from_parent_map(
2232
 
                parents_map, self._unstacked_provider.missing_keys)
2233
 
        else:
2234
 
            (start_set, stop_keys,
2235
 
             key_count) = vf_search.limited_search_result_from_parent_map(
2236
 
                parents_map, self._unstacked_provider.missing_keys,
2237
 
                keys, depth=_DEFAULT_SEARCH_DEPTH)
2238
 
        recipe = ('manual', start_set, stop_keys, key_count)
2239
 
        body = self._serialise_search_recipe(recipe)
2240
 
        path = self.bzrdir._path_for_remote_call(self._client)
2241
 
        for key in keys:
2242
 
            if type(key) is not str:
2243
 
                raise ValueError(
2244
 
                    "key %r not a plain string" % (key,))
2245
 
        verb = 'Repository.get_parent_map'
2246
 
        args = (path, 'include-missing:') + tuple(keys)
2247
 
        try:
2248
 
            response = self._call_with_body_bytes_expecting_body(
2249
 
                verb, args, body)
2250
 
        except errors.UnknownSmartMethod:
2251
 
            # Server does not support this method, so get the whole graph.
2252
 
            # Worse, we have to force a disconnection, because the server now
2253
 
            # doesn't realise it has a body on the wire to consume, so the
2254
 
            # only way to recover is to abandon the connection.
2255
 
            warning(
2256
 
                'Server is too old for fast get_parent_map, reconnecting.  '
2257
 
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
2258
 
            medium.disconnect()
2259
 
            # To avoid having to disconnect repeatedly, we keep track of the
2260
 
            # fact the server doesn't understand remote methods added in 1.2.
2261
 
            medium._remember_remote_is_before((1, 2))
2262
 
            # Recurse just once and we should use the fallback code.
2263
 
            return self._get_parent_map_rpc(keys)
2264
 
        response_tuple, response_handler = response
2265
 
        if response_tuple[0] not in ['ok']:
2266
 
            response_handler.cancel_read_body()
2267
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2268
 
        if response_tuple[0] == 'ok':
2269
 
            coded = bz2.decompress(response_handler.read_body_bytes())
2270
 
            if coded == '':
2271
 
                # no revisions found
2272
 
                return {}
2273
 
            lines = coded.split('\n')
2274
 
            revision_graph = {}
2275
 
            for line in lines:
2276
 
                d = tuple(line.split())
2277
 
                if len(d) > 1:
2278
 
                    revision_graph[d[0]] = d[1:]
2279
 
                else:
2280
 
                    # No parents:
2281
 
                    if d[0].startswith('missing:'):
2282
 
                        revid = d[0][8:]
2283
 
                        self._unstacked_provider.note_missing_key(revid)
2284
 
                    else:
2285
 
                        # no parents - so give the Graph result
2286
 
                        # (NULL_REVISION,).
2287
 
                        revision_graph[d[0]] = (NULL_REVISION,)
2288
 
            return revision_graph
2289
 
 
2290
557
    @needs_read_lock
2291
558
    def get_signature_text(self, revision_id):
2292
 
        path = self.bzrdir._path_for_remote_call(self._client)
2293
 
        try:
2294
 
            response_tuple, response_handler = self._call_expecting_body(
2295
 
                'Repository.get_revision_signature_text', path, revision_id)
2296
 
        except errors.UnknownSmartMethod:
2297
 
            self._ensure_real()
2298
 
            return self._real_repository.get_signature_text(revision_id)
2299
 
        except errors.NoSuchRevision, err:
2300
 
            for fallback in self._fallback_repositories:
2301
 
                try:
2302
 
                    return fallback.get_signature_text(revision_id)
2303
 
                except errors.NoSuchRevision:
2304
 
                    pass
2305
 
            raise err
2306
 
        else:
2307
 
            if response_tuple[0] != 'ok':
2308
 
                raise errors.UnexpectedSmartServerResponse(response_tuple)
2309
 
            return response_handler.read_body_bytes()
2310
 
 
2311
 
    @needs_read_lock
2312
 
    def _get_inventory_xml(self, revision_id):
2313
 
        # This call is used by older working tree formats,
2314
 
        # which stored a serialized basis inventory.
2315
 
        self._ensure_real()
2316
 
        return self._real_repository._get_inventory_xml(revision_id)
2317
 
 
2318
 
    @needs_write_lock
 
559
        self._ensure_real()
 
560
        return self._real_repository.get_signature_text(revision_id)
 
561
 
 
562
    @needs_read_lock
 
563
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
564
        self._ensure_real()
 
565
        return self._real_repository.get_revision_graph_with_ghosts(
 
566
            revision_ids=revision_ids)
 
567
 
 
568
    @needs_read_lock
 
569
    def get_inventory_xml(self, revision_id):
 
570
        self._ensure_real()
 
571
        return self._real_repository.get_inventory_xml(revision_id)
 
572
 
 
573
    def deserialise_inventory(self, revision_id, xml):
 
574
        self._ensure_real()
 
575
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
576
 
2319
577
    def reconcile(self, other=None, thorough=False):
2320
 
        from bzrlib.reconcile import RepoReconciler
2321
 
        path = self.bzrdir._path_for_remote_call(self._client)
2322
 
        try:
2323
 
            response, handler = self._call_expecting_body(
2324
 
                'Repository.reconcile', path, self._lock_token)
2325
 
        except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2326
 
            self._ensure_real()
2327
 
            return self._real_repository.reconcile(other=other, thorough=thorough)
2328
 
        if response != ('ok', ):
2329
 
            raise errors.UnexpectedSmartServerResponse(response)
2330
 
        body = handler.read_body_bytes()
2331
 
        result = RepoReconciler(self)
2332
 
        for line in body.split('\n'):
2333
 
            if not line:
2334
 
                continue
2335
 
            key, val_text = line.split(':')
2336
 
            if key == "garbage_inventories":
2337
 
                result.garbage_inventories = int(val_text)
2338
 
            elif key == "inconsistent_parents":
2339
 
                result.inconsistent_parents = int(val_text)
2340
 
            else:
2341
 
                mutter("unknown reconcile key %r" % key)
2342
 
        return result
2343
 
 
 
578
        self._ensure_real()
 
579
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
580
        
2344
581
    def all_revision_ids(self):
2345
 
        path = self.bzrdir._path_for_remote_call(self._client)
2346
 
        try:
2347
 
            response_tuple, response_handler = self._call_expecting_body(
2348
 
                "Repository.all_revision_ids", path)
2349
 
        except errors.UnknownSmartMethod:
2350
 
            self._ensure_real()
2351
 
            return self._real_repository.all_revision_ids()
2352
 
        if response_tuple != ("ok", ):
2353
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2354
 
        revids = set(response_handler.read_body_bytes().splitlines())
2355
 
        for fallback in self._fallback_repositories:
2356
 
            revids.update(set(fallback.all_revision_ids()))
2357
 
        return list(revids)
2358
 
 
2359
 
    def _filtered_revision_trees(self, revision_ids, file_ids):
2360
 
        """Return Tree for a revision on this branch with only some files.
2361
 
 
2362
 
        :param revision_ids: a sequence of revision-ids;
2363
 
          a revision-id may not be None or 'null:'
2364
 
        :param file_ids: if not None, the result is filtered
2365
 
          so that only those file-ids, their parents and their
2366
 
          children are included.
2367
 
        """
2368
 
        inventories = self.iter_inventories(revision_ids)
2369
 
        for inv in inventories:
2370
 
            # Should we introduce a FilteredRevisionTree class rather
2371
 
            # than pre-filter the inventory here?
2372
 
            filtered_inv = inv.filter(file_ids)
2373
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2374
 
 
2375
 
    @needs_read_lock
2376
 
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2377
 
        medium = self._client._medium
2378
 
        if medium._is_remote_before((1, 2)):
2379
 
            self._ensure_real()
2380
 
            for delta in self._real_repository.get_deltas_for_revisions(
2381
 
                    revisions, specific_fileids):
2382
 
                yield delta
2383
 
            return
2384
 
        # Get the revision-ids of interest
2385
 
        required_trees = set()
2386
 
        for revision in revisions:
2387
 
            required_trees.add(revision.revision_id)
2388
 
            required_trees.update(revision.parent_ids[:1])
2389
 
 
2390
 
        # Get the matching filtered trees. Note that it's more
2391
 
        # efficient to pass filtered trees to changes_from() rather
2392
 
        # than doing the filtering afterwards. changes_from() could
2393
 
        # arguably do the filtering itself but it's path-based, not
2394
 
        # file-id based, so filtering before or afterwards is
2395
 
        # currently easier.
2396
 
        if specific_fileids is None:
2397
 
            trees = dict((t.get_revision_id(), t) for
2398
 
                t in self.revision_trees(required_trees))
2399
 
        else:
2400
 
            trees = dict((t.get_revision_id(), t) for
2401
 
                t in self._filtered_revision_trees(required_trees,
2402
 
                specific_fileids))
2403
 
 
2404
 
        # Calculate the deltas
2405
 
        for revision in revisions:
2406
 
            if not revision.parent_ids:
2407
 
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2408
 
            else:
2409
 
                old_tree = trees[revision.parent_ids[0]]
2410
 
            yield trees[revision.revision_id].changes_from(old_tree)
2411
 
 
2412
 
    @needs_read_lock
2413
 
    def get_revision_delta(self, revision_id, specific_fileids=None):
2414
 
        r = self.get_revision(revision_id)
2415
 
        return list(self.get_deltas_for_revisions([r],
2416
 
            specific_fileids=specific_fileids))[0]
 
582
        self._ensure_real()
 
583
        return self._real_repository.all_revision_ids()
 
584
    
 
585
    @needs_read_lock
 
586
    def get_deltas_for_revisions(self, revisions):
 
587
        self._ensure_real()
 
588
        return self._real_repository.get_deltas_for_revisions(revisions)
 
589
 
 
590
    @needs_read_lock
 
591
    def get_revision_delta(self, revision_id):
 
592
        self._ensure_real()
 
593
        return self._real_repository.get_revision_delta(revision_id)
2417
594
 
2418
595
    @needs_read_lock
2419
596
    def revision_trees(self, revision_ids):
2420
 
        inventories = self.iter_inventories(revision_ids)
2421
 
        for inv in inventories:
2422
 
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
597
        self._ensure_real()
 
598
        return self._real_repository.revision_trees(revision_ids)
2423
599
 
2424
600
    @needs_read_lock
2425
601
    def get_revision_reconcile(self, revision_id):
2427
603
        return self._real_repository.get_revision_reconcile(revision_id)
2428
604
 
2429
605
    @needs_read_lock
2430
 
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
606
    def check(self, revision_ids):
2431
607
        self._ensure_real()
2432
 
        return self._real_repository.check(revision_ids=revision_ids,
2433
 
            callback_refs=callback_refs, check_repo=check_repo)
 
608
        return self._real_repository.check(revision_ids)
2434
609
 
2435
610
    def copy_content_into(self, destination, revision_id=None):
2436
 
        """Make a complete copy of the content in self into destination.
2437
 
 
2438
 
        This is a destructive operation! Do not use it on existing
2439
 
        repositories.
2440
 
        """
2441
 
        interrepo = _mod_repository.InterRepository.get(self, destination)
2442
 
        return interrepo.copy_content(revision_id)
2443
 
 
2444
 
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
 
611
        self._ensure_real()
 
612
        return self._real_repository.copy_content_into(
 
613
            destination, revision_id=revision_id)
 
614
 
 
615
    def _copy_repository_tarball(self, destination, revision_id=None):
2445
616
        # get a tarball of the remote repository, and copy from that into the
2446
617
        # destination
 
618
        from bzrlib import osutils
2447
619
        import tarfile
 
620
        import tempfile
 
621
        from StringIO import StringIO
2448
622
        # TODO: Maybe a progress bar while streaming the tarball?
2449
 
        note(gettext("Copying repository content as tarball..."))
 
623
        note("Copying repository content as tarball...")
2450
624
        tar_file = self._get_tarball('bz2')
2451
 
        if tar_file is None:
2452
 
            return None
2453
 
        destination = to_bzrdir.create_repository()
2454
625
        try:
2455
626
            tar = tarfile.open('repository', fileobj=tar_file,
2456
627
                mode='r|bz2')
2457
 
            tmpdir = osutils.mkdtemp()
 
628
            tmpdir = tempfile.mkdtemp()
2458
629
            try:
2459
630
                _extract_tar(tar, tmpdir)
2460
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
631
                tmp_bzrdir = BzrDir.open(tmpdir)
2461
632
                tmp_repo = tmp_bzrdir.open_repository()
2462
633
                tmp_repo.copy_content_into(destination, revision_id)
2463
634
            finally:
2464
635
                osutils.rmtree(tmpdir)
2465
636
        finally:
2466
637
            tar_file.close()
2467
 
        return destination
 
638
        # TODO: if the server doesn't support this operation, maybe do it the
 
639
        # slow way using the _real_repository?
 
640
        #
2468
641
        # TODO: Suggestion from john: using external tar is much faster than
2469
642
        # python's tarfile library, but it may not work on windows.
2470
643
 
2471
 
    @property
2472
 
    def inventories(self):
2473
 
        """Decorate the real repository for now.
2474
 
 
2475
 
        In the long term a full blown network facility is needed to
2476
 
        avoid creating a real repository object locally.
2477
 
        """
2478
 
        self._ensure_real()
2479
 
        return self._real_repository.inventories
2480
 
 
2481
644
    @needs_write_lock
2482
 
    def pack(self, hint=None, clean_obsolete_packs=False):
 
645
    def pack(self):
2483
646
        """Compress the data within the repository.
2484
 
        """
2485
 
        if hint is None:
2486
 
            body = ""
2487
 
        else:
2488
 
            body = "".join([l+"\n" for l in hint])
2489
 
        path = self.bzrdir._path_for_remote_call(self._client)
2490
 
        try:
2491
 
            response, handler = self._call_with_body_bytes_expecting_body(
2492
 
                'Repository.pack', (path, self._lock_token,
2493
 
                    str(clean_obsolete_packs)), body)
2494
 
        except errors.UnknownSmartMethod:
2495
 
            self._ensure_real()
2496
 
            return self._real_repository.pack(hint=hint,
2497
 
                clean_obsolete_packs=clean_obsolete_packs)
2498
 
        handler.cancel_read_body()
2499
 
        if response != ('ok', ):
2500
 
            raise errors.UnexpectedSmartServerResponse(response)
2501
 
 
2502
 
    @property
2503
 
    def revisions(self):
2504
 
        """Decorate the real repository for now.
2505
 
 
2506
 
        In the long term a full blown network facility is needed.
 
647
 
 
648
        This is not currently implemented within the smart server.
2507
649
        """
2508
650
        self._ensure_real()
2509
 
        return self._real_repository.revisions
 
651
        return self._real_repository.pack()
2510
652
 
2511
653
    def set_make_working_trees(self, new_value):
2512
 
        if new_value:
2513
 
            new_value_str = "True"
2514
 
        else:
2515
 
            new_value_str = "False"
2516
 
        path = self.bzrdir._path_for_remote_call(self._client)
2517
 
        try:
2518
 
            response = self._call(
2519
 
                'Repository.set_make_working_trees', path, new_value_str)
2520
 
        except errors.UnknownSmartMethod:
2521
 
            self._ensure_real()
2522
 
            self._real_repository.set_make_working_trees(new_value)
2523
 
        else:
2524
 
            if response[0] != 'ok':
2525
 
                raise errors.UnexpectedSmartServerResponse(response)
2526
 
 
2527
 
    @property
2528
 
    def signatures(self):
2529
 
        """Decorate the real repository for now.
2530
 
 
2531
 
        In the long term a full blown network facility is needed to avoid
2532
 
        creating a real repository object locally.
2533
 
        """
2534
 
        self._ensure_real()
2535
 
        return self._real_repository.signatures
 
654
        raise NotImplementedError(self.set_make_working_trees)
2536
655
 
2537
656
    @needs_write_lock
2538
657
    def sign_revision(self, revision_id, gpg_strategy):
2539
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
2540
 
        plaintext = testament.as_short_text()
2541
 
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2542
 
 
2543
 
    @property
2544
 
    def texts(self):
2545
 
        """Decorate the real repository for now.
2546
 
 
2547
 
        In the long term a full blown network facility is needed to avoid
2548
 
        creating a real repository object locally.
2549
 
        """
2550
658
        self._ensure_real()
2551
 
        return self._real_repository.texts
2552
 
 
2553
 
    def _iter_revisions_rpc(self, revision_ids):
2554
 
        body = "\n".join(revision_ids)
2555
 
        path = self.bzrdir._path_for_remote_call(self._client)
2556
 
        response_tuple, response_handler = (
2557
 
            self._call_with_body_bytes_expecting_body(
2558
 
            "Repository.iter_revisions", (path, ), body))
2559
 
        if response_tuple[0] != "ok":
2560
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2561
 
        serializer_format = response_tuple[1]
2562
 
        serializer = serializer_format_registry.get(serializer_format)
2563
 
        byte_stream = response_handler.read_streamed_body()
2564
 
        decompressor = zlib.decompressobj()
2565
 
        chunks = []
2566
 
        for bytes in byte_stream:
2567
 
            chunks.append(decompressor.decompress(bytes))
2568
 
            if decompressor.unused_data != "":
2569
 
                chunks.append(decompressor.flush())
2570
 
                yield serializer.read_revision_from_string("".join(chunks))
2571
 
                unused = decompressor.unused_data
2572
 
                decompressor = zlib.decompressobj()
2573
 
                chunks = [decompressor.decompress(unused)]
2574
 
        chunks.append(decompressor.flush())
2575
 
        text = "".join(chunks)
2576
 
        if text != "":
2577
 
            yield serializer.read_revision_from_string("".join(chunks))
 
659
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2578
660
 
2579
661
    @needs_read_lock
2580
662
    def get_revisions(self, revision_ids):
2581
 
        if revision_ids is None:
2582
 
            revision_ids = self.all_revision_ids()
2583
 
        else:
2584
 
            for rev_id in revision_ids:
2585
 
                if not rev_id or not isinstance(rev_id, basestring):
2586
 
                    raise errors.InvalidRevisionId(
2587
 
                        revision_id=rev_id, branch=self)
2588
 
        try:
2589
 
            missing = set(revision_ids)
2590
 
            revs = {}
2591
 
            for rev in self._iter_revisions_rpc(revision_ids):
2592
 
                missing.remove(rev.revision_id)
2593
 
                revs[rev.revision_id] = rev
2594
 
        except errors.UnknownSmartMethod:
2595
 
            self._ensure_real()
2596
 
            return self._real_repository.get_revisions(revision_ids)
2597
 
        for fallback in self._fallback_repositories:
2598
 
            if not missing:
2599
 
                break
2600
 
            for revid in list(missing):
2601
 
                # XXX JRV 2011-11-20: It would be nice if there was a
2602
 
                # public method on Repository that could be used to query
2603
 
                # for revision objects *without* failing completely if one
2604
 
                # was missing. There is VersionedFileRepository._iter_revisions,
2605
 
                # but unfortunately that's private and not provided by
2606
 
                # all repository implementations.
2607
 
                try:
2608
 
                    revs[revid] = fallback.get_revision(revid)
2609
 
                except errors.NoSuchRevision:
2610
 
                    pass
2611
 
                else:
2612
 
                    missing.remove(revid)
2613
 
        if missing:
2614
 
            raise errors.NoSuchRevision(self, list(missing)[0])
2615
 
        return [revs[revid] for revid in revision_ids]
 
663
        self._ensure_real()
 
664
        return self._real_repository.get_revisions(revision_ids)
2616
665
 
2617
666
    def supports_rich_root(self):
2618
 
        return self._format.rich_root_data
 
667
        self._ensure_real()
 
668
        return self._real_repository.supports_rich_root()
2619
669
 
2620
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2621
670
    def iter_reverse_revision_history(self, revision_id):
2622
671
        self._ensure_real()
2623
672
        return self._real_repository.iter_reverse_revision_history(revision_id)
2624
673
 
2625
674
    @property
2626
675
    def _serializer(self):
2627
 
        return self._format._serializer
 
676
        self._ensure_real()
 
677
        return self._real_repository._serializer
2628
678
 
2629
 
    @needs_write_lock
2630
679
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2631
 
        signature = gpg_strategy.sign(plaintext)
2632
 
        self.add_signature_text(revision_id, signature)
2633
 
 
2634
 
    def add_signature_text(self, revision_id, signature):
2635
 
        if self._real_repository:
2636
 
            # If there is a real repository the write group will
2637
 
            # be in the real repository as well, so use that:
2638
 
            self._ensure_real()
2639
 
            return self._real_repository.add_signature_text(
2640
 
                revision_id, signature)
2641
 
        path = self.bzrdir._path_for_remote_call(self._client)
2642
 
        response, handler = self._call_with_body_bytes_expecting_body(
2643
 
            'Repository.add_signature_text', (path, self._lock_token,
2644
 
                revision_id) + tuple(self._write_group_tokens), signature)
2645
 
        handler.cancel_read_body()
2646
 
        self.refresh_data()
2647
 
        if response[0] != 'ok':
2648
 
            raise errors.UnexpectedSmartServerResponse(response)
2649
 
        self._write_group_tokens = response[1:]
 
680
        self._ensure_real()
 
681
        return self._real_repository.store_revision_signature(
 
682
            gpg_strategy, plaintext, revision_id)
2650
683
 
2651
684
    def has_signature_for_revision_id(self, revision_id):
2652
 
        path = self.bzrdir._path_for_remote_call(self._client)
2653
 
        try:
2654
 
            response = self._call('Repository.has_signature_for_revision_id',
2655
 
                path, revision_id)
2656
 
        except errors.UnknownSmartMethod:
2657
 
            self._ensure_real()
2658
 
            return self._real_repository.has_signature_for_revision_id(
2659
 
                revision_id)
2660
 
        if response[0] not in ('yes', 'no'):
2661
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
2662
 
        if response[0] == 'yes':
2663
 
            return True
2664
 
        for fallback in self._fallback_repositories:
2665
 
            if fallback.has_signature_for_revision_id(revision_id):
2666
 
                return True
2667
 
        return False
2668
 
 
2669
 
    @needs_read_lock
2670
 
    def verify_revision_signature(self, revision_id, gpg_strategy):
2671
 
        if not self.has_signature_for_revision_id(revision_id):
2672
 
            return gpg.SIGNATURE_NOT_SIGNED, None
2673
 
        signature = self.get_signature_text(revision_id)
2674
 
 
2675
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
2676
 
        plaintext = testament.as_short_text()
2677
 
 
2678
 
        return gpg_strategy.verify(signature, plaintext)
2679
 
 
2680
 
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2681
 
        self._ensure_real()
2682
 
        return self._real_repository.item_keys_introduced_by(revision_ids,
2683
 
            _files_pb=_files_pb)
2684
 
 
2685
 
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2686
 
        self._ensure_real()
2687
 
        return self._real_repository._find_inconsistent_revision_parents(
2688
 
            revisions_iterator)
2689
 
 
2690
 
    def _check_for_inconsistent_revision_parents(self):
2691
 
        self._ensure_real()
2692
 
        return self._real_repository._check_for_inconsistent_revision_parents()
2693
 
 
2694
 
    def _make_parents_provider(self, other=None):
2695
 
        providers = [self._unstacked_provider]
2696
 
        if other is not None:
2697
 
            providers.insert(0, other)
2698
 
        return graph.StackedParentsProvider(_LazyListJoin(
2699
 
            providers, self._fallback_repositories))
2700
 
 
2701
 
    def _serialise_search_recipe(self, recipe):
2702
 
        """Serialise a graph search recipe.
2703
 
 
2704
 
        :param recipe: A search recipe (start, stop, count).
2705
 
        :return: Serialised bytes.
2706
 
        """
2707
 
        start_keys = ' '.join(recipe[1])
2708
 
        stop_keys = ' '.join(recipe[2])
2709
 
        count = str(recipe[3])
2710
 
        return '\n'.join((start_keys, stop_keys, count))
2711
 
 
2712
 
    def _serialise_search_result(self, search_result):
2713
 
        parts = search_result.get_network_struct()
2714
 
        return '\n'.join(parts)
2715
 
 
2716
 
    def autopack(self):
2717
 
        path = self.bzrdir._path_for_remote_call(self._client)
2718
 
        try:
2719
 
            response = self._call('PackRepository.autopack', path)
2720
 
        except errors.UnknownSmartMethod:
2721
 
            self._ensure_real()
2722
 
            self._real_repository._pack_collection.autopack()
2723
 
            return
2724
 
        self.refresh_data()
2725
 
        if response[0] != 'ok':
2726
 
            raise errors.UnexpectedSmartServerResponse(response)
2727
 
 
2728
 
 
2729
 
class RemoteStreamSink(vf_repository.StreamSink):
2730
 
 
2731
 
    def _insert_real(self, stream, src_format, resume_tokens):
2732
 
        self.target_repo._ensure_real()
2733
 
        sink = self.target_repo._real_repository._get_sink()
2734
 
        result = sink.insert_stream(stream, src_format, resume_tokens)
2735
 
        if not result:
2736
 
            self.target_repo.autopack()
2737
 
        return result
2738
 
 
2739
 
    def insert_stream(self, stream, src_format, resume_tokens):
2740
 
        target = self.target_repo
2741
 
        target._unstacked_provider.missing_keys.clear()
2742
 
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2743
 
        if target._lock_token:
2744
 
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2745
 
            lock_args = (target._lock_token or '',)
2746
 
        else:
2747
 
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
2748
 
            lock_args = ()
2749
 
        client = target._client
2750
 
        medium = client._medium
2751
 
        path = target.bzrdir._path_for_remote_call(client)
2752
 
        # Probe for the verb to use with an empty stream before sending the
2753
 
        # real stream to it.  We do this both to avoid the risk of sending a
2754
 
        # large request that is then rejected, and because we don't want to
2755
 
        # implement a way to buffer, rewind, or restart the stream.
2756
 
        found_verb = False
2757
 
        for verb, required_version in candidate_calls:
2758
 
            if medium._is_remote_before(required_version):
2759
 
                continue
2760
 
            if resume_tokens:
2761
 
                # We've already done the probing (and set _is_remote_before) on
2762
 
                # a previous insert.
2763
 
                found_verb = True
2764
 
                break
2765
 
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2766
 
            try:
2767
 
                response = client.call_with_body_stream(
2768
 
                    (verb, path, '') + lock_args, byte_stream)
2769
 
            except errors.UnknownSmartMethod:
2770
 
                medium._remember_remote_is_before(required_version)
2771
 
            else:
2772
 
                found_verb = True
2773
 
                break
2774
 
        if not found_verb:
2775
 
            # Have to use VFS.
2776
 
            return self._insert_real(stream, src_format, resume_tokens)
2777
 
        self._last_inv_record = None
2778
 
        self._last_substream = None
2779
 
        if required_version < (1, 19):
2780
 
            # Remote side doesn't support inventory deltas.  Wrap the stream to
2781
 
            # make sure we don't send any.  If the stream contains inventory
2782
 
            # deltas we'll interrupt the smart insert_stream request and
2783
 
            # fallback to VFS.
2784
 
            stream = self._stop_stream_if_inventory_delta(stream)
2785
 
        byte_stream = smart_repo._stream_to_byte_stream(
2786
 
            stream, src_format)
2787
 
        resume_tokens = ' '.join(resume_tokens)
2788
 
        response = client.call_with_body_stream(
2789
 
            (verb, path, resume_tokens) + lock_args, byte_stream)
2790
 
        if response[0][0] not in ('ok', 'missing-basis'):
2791
 
            raise errors.UnexpectedSmartServerResponse(response)
2792
 
        if self._last_substream is not None:
2793
 
            # The stream included an inventory-delta record, but the remote
2794
 
            # side isn't new enough to support them.  So we need to send the
2795
 
            # rest of the stream via VFS.
2796
 
            self.target_repo.refresh_data()
2797
 
            return self._resume_stream_with_vfs(response, src_format)
2798
 
        if response[0][0] == 'missing-basis':
2799
 
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2800
 
            resume_tokens = tokens
2801
 
            return resume_tokens, set(missing_keys)
2802
 
        else:
2803
 
            self.target_repo.refresh_data()
2804
 
            return [], set()
2805
 
 
2806
 
    def _resume_stream_with_vfs(self, response, src_format):
2807
 
        """Resume sending a stream via VFS, first resending the record and
2808
 
        substream that couldn't be sent via an insert_stream verb.
2809
 
        """
2810
 
        if response[0][0] == 'missing-basis':
2811
 
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2812
 
            # Ignore missing_keys, we haven't finished inserting yet
2813
 
        else:
2814
 
            tokens = []
2815
 
        def resume_substream():
2816
 
            # Yield the substream that was interrupted.
2817
 
            for record in self._last_substream:
2818
 
                yield record
2819
 
            self._last_substream = None
2820
 
        def resume_stream():
2821
 
            # Finish sending the interrupted substream
2822
 
            yield ('inventory-deltas', resume_substream())
2823
 
            # Then simply continue sending the rest of the stream.
2824
 
            for substream_kind, substream in self._last_stream:
2825
 
                yield substream_kind, substream
2826
 
        return self._insert_real(resume_stream(), src_format, tokens)
2827
 
 
2828
 
    def _stop_stream_if_inventory_delta(self, stream):
2829
 
        """Normally this just lets the original stream pass-through unchanged.
2830
 
 
2831
 
        However if any 'inventory-deltas' substream occurs it will stop
2832
 
        streaming, and store the interrupted substream and stream in
2833
 
        self._last_substream and self._last_stream so that the stream can be
2834
 
        resumed by _resume_stream_with_vfs.
2835
 
        """
2836
 
 
2837
 
        stream_iter = iter(stream)
2838
 
        for substream_kind, substream in stream_iter:
2839
 
            if substream_kind == 'inventory-deltas':
2840
 
                self._last_substream = substream
2841
 
                self._last_stream = stream_iter
2842
 
                return
2843
 
            else:
2844
 
                yield substream_kind, substream
2845
 
 
2846
 
 
2847
 
class RemoteStreamSource(vf_repository.StreamSource):
2848
 
    """Stream data from a remote server."""
2849
 
 
2850
 
    def get_stream(self, search):
2851
 
        if (self.from_repository._fallback_repositories and
2852
 
            self.to_format._fetch_order == 'topological'):
2853
 
            return self._real_stream(self.from_repository, search)
2854
 
        sources = []
2855
 
        seen = set()
2856
 
        repos = [self.from_repository]
2857
 
        while repos:
2858
 
            repo = repos.pop(0)
2859
 
            if repo in seen:
2860
 
                continue
2861
 
            seen.add(repo)
2862
 
            repos.extend(repo._fallback_repositories)
2863
 
            sources.append(repo)
2864
 
        return self.missing_parents_chain(search, sources)
2865
 
 
2866
 
    def get_stream_for_missing_keys(self, missing_keys):
2867
 
        self.from_repository._ensure_real()
2868
 
        real_repo = self.from_repository._real_repository
2869
 
        real_source = real_repo._get_source(self.to_format)
2870
 
        return real_source.get_stream_for_missing_keys(missing_keys)
2871
 
 
2872
 
    def _real_stream(self, repo, search):
2873
 
        """Get a stream for search from repo.
2874
 
 
2875
 
        This never called RemoteStreamSource.get_stream, and is a helper
2876
 
        for RemoteStreamSource._get_stream to allow getting a stream
2877
 
        reliably whether fallback back because of old servers or trying
2878
 
        to stream from a non-RemoteRepository (which the stacked support
2879
 
        code will do).
2880
 
        """
2881
 
        source = repo._get_source(self.to_format)
2882
 
        if isinstance(source, RemoteStreamSource):
2883
 
            repo._ensure_real()
2884
 
            source = repo._real_repository._get_source(self.to_format)
2885
 
        return source.get_stream(search)
2886
 
 
2887
 
    def _get_stream(self, repo, search):
2888
 
        """Core worker to get a stream from repo for search.
2889
 
 
2890
 
        This is used by both get_stream and the stacking support logic. It
2891
 
        deliberately gets a stream for repo which does not need to be
2892
 
        self.from_repository. In the event that repo is not Remote, or
2893
 
        cannot do a smart stream, a fallback is made to the generic
2894
 
        repository._get_stream() interface, via self._real_stream.
2895
 
 
2896
 
        In the event of stacking, streams from _get_stream will not
2897
 
        contain all the data for search - this is normal (see get_stream).
2898
 
 
2899
 
        :param repo: A repository.
2900
 
        :param search: A search.
2901
 
        """
2902
 
        # Fallbacks may be non-smart
2903
 
        if not isinstance(repo, RemoteRepository):
2904
 
            return self._real_stream(repo, search)
2905
 
        client = repo._client
2906
 
        medium = client._medium
2907
 
        path = repo.bzrdir._path_for_remote_call(client)
2908
 
        search_bytes = repo._serialise_search_result(search)
2909
 
        args = (path, self.to_format.network_name())
2910
 
        candidate_verbs = [
2911
 
            ('Repository.get_stream_1.19', (1, 19)),
2912
 
            ('Repository.get_stream', (1, 13))]
2913
 
 
2914
 
        found_verb = False
2915
 
        for verb, version in candidate_verbs:
2916
 
            if medium._is_remote_before(version):
2917
 
                continue
2918
 
            try:
2919
 
                response = repo._call_with_body_bytes_expecting_body(
2920
 
                    verb, args, search_bytes)
2921
 
            except errors.UnknownSmartMethod:
2922
 
                medium._remember_remote_is_before(version)
2923
 
            except errors.UnknownErrorFromSmartServer, e:
2924
 
                if isinstance(search, vf_search.EverythingResult):
2925
 
                    error_verb = e.error_from_smart_server.error_verb
2926
 
                    if error_verb == 'BadSearch':
2927
 
                        # Pre-2.4 servers don't support this sort of search.
2928
 
                        # XXX: perhaps falling back to VFS on BadSearch is a
2929
 
                        # good idea in general?  It might provide a little bit
2930
 
                        # of protection against client-side bugs.
2931
 
                        medium._remember_remote_is_before((2, 4))
2932
 
                        break
2933
 
                raise
2934
 
            else:
2935
 
                response_tuple, response_handler = response
2936
 
                found_verb = True
2937
 
                break
2938
 
        if not found_verb:
2939
 
            return self._real_stream(repo, search)
2940
 
        if response_tuple[0] != 'ok':
2941
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2942
 
        byte_stream = response_handler.read_streamed_body()
2943
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2944
 
            self._record_counter)
2945
 
        if src_format.network_name() != repo._format.network_name():
2946
 
            raise AssertionError(
2947
 
                "Mismatched RemoteRepository and stream src %r, %r" % (
2948
 
                src_format.network_name(), repo._format.network_name()))
2949
 
        return stream
2950
 
 
2951
 
    def missing_parents_chain(self, search, sources):
2952
 
        """Chain multiple streams together to handle stacking.
2953
 
 
2954
 
        :param search: The overall search to satisfy with streams.
2955
 
        :param sources: A list of Repository objects to query.
2956
 
        """
2957
 
        self.from_serialiser = self.from_repository._format._serializer
2958
 
        self.seen_revs = set()
2959
 
        self.referenced_revs = set()
2960
 
        # If there are heads in the search, or the key count is > 0, we are not
2961
 
        # done.
2962
 
        while not search.is_empty() and len(sources) > 1:
2963
 
            source = sources.pop(0)
2964
 
            stream = self._get_stream(source, search)
2965
 
            for kind, substream in stream:
2966
 
                if kind != 'revisions':
2967
 
                    yield kind, substream
2968
 
                else:
2969
 
                    yield kind, self.missing_parents_rev_handler(substream)
2970
 
            search = search.refine(self.seen_revs, self.referenced_revs)
2971
 
            self.seen_revs = set()
2972
 
            self.referenced_revs = set()
2973
 
        if not search.is_empty():
2974
 
            for kind, stream in self._get_stream(sources[0], search):
2975
 
                yield kind, stream
2976
 
 
2977
 
    def missing_parents_rev_handler(self, substream):
2978
 
        for content in substream:
2979
 
            revision_bytes = content.get_bytes_as('fulltext')
2980
 
            revision = self.from_serialiser.read_revision_from_string(
2981
 
                revision_bytes)
2982
 
            self.seen_revs.add(content.key[-1])
2983
 
            self.referenced_revs.update(revision.parent_ids)
2984
 
            yield content
 
685
        self._ensure_real()
 
686
        return self._real_repository.has_signature_for_revision_id(revision_id)
2985
687
 
2986
688
 
2987
689
class RemoteBranchLockableFiles(LockableFiles):
2988
690
    """A 'LockableFiles' implementation that talks to a smart server.
2989
 
 
 
691
    
2990
692
    This is not a public interface class.
2991
693
    """
2992
694
 
3003
705
        self._dir_mode = None
3004
706
        self._file_mode = None
3005
707
 
 
708
    def get(self, path):
 
709
        """'get' a remote path as per the LockableFiles interface.
 
710
 
 
711
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
712
             just retrieve a file, instead we ask the smart server to generate
 
713
             a configuration for us - which is retrieved as an INI file.
 
714
        """
 
715
        if path == 'branch.conf':
 
716
            path = self.bzrdir._path_for_remote_call(self._client)
 
717
            response = self._client.call_expecting_body(
 
718
                'Branch.get_config_file', path)
 
719
            assert response[0][0] == 'ok', \
 
720
                'unexpected response code %s' % (response[0],)
 
721
            return StringIO(response[1].read_body_bytes())
 
722
        else:
 
723
            # VFS fallback.
 
724
            return LockableFiles.get(self, path)
 
725
 
3006
726
 
3007
727
class RemoteBranchFormat(branch.BranchFormat):
3008
728
 
3009
 
    def __init__(self, network_name=None):
3010
 
        super(RemoteBranchFormat, self).__init__()
3011
 
        self._matchingbzrdir = RemoteBzrDirFormat()
3012
 
        self._matchingbzrdir.set_branch_format(self)
3013
 
        self._custom_format = None
3014
 
        self._network_name = network_name
3015
 
 
3016
729
    def __eq__(self, other):
3017
 
        return (isinstance(other, RemoteBranchFormat) and
 
730
        return (isinstance(other, RemoteBranchFormat) and 
3018
731
            self.__dict__ == other.__dict__)
3019
732
 
3020
 
    def _ensure_real(self):
3021
 
        if self._custom_format is None:
3022
 
            try:
3023
 
                self._custom_format = branch.network_format_registry.get(
3024
 
                    self._network_name)
3025
 
            except KeyError:
3026
 
                raise errors.UnknownFormatError(kind='branch',
3027
 
                    format=self._network_name)
3028
 
 
3029
733
    def get_format_description(self):
3030
 
        self._ensure_real()
3031
 
        return 'Remote: ' + self._custom_format.get_format_description()
3032
 
 
3033
 
    def network_name(self):
3034
 
        return self._network_name
3035
 
 
3036
 
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3037
 
        return a_bzrdir.open_branch(name=name, 
3038
 
            ignore_fallbacks=ignore_fallbacks)
3039
 
 
3040
 
    def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3041
 
        # Initialisation when using a local bzrdir object, or a non-vfs init
3042
 
        # method is not available on the server.
3043
 
        # self._custom_format is always set - the start of initialize ensures
3044
 
        # that.
3045
 
        if isinstance(a_bzrdir, RemoteBzrDir):
3046
 
            a_bzrdir._ensure_real()
3047
 
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3048
 
                name, append_revisions_only=append_revisions_only)
3049
 
        else:
3050
 
            # We assume the bzrdir is parameterised; it may not be.
3051
 
            result = self._custom_format.initialize(a_bzrdir, name,
3052
 
                append_revisions_only=append_revisions_only)
3053
 
        if (isinstance(a_bzrdir, RemoteBzrDir) and
3054
 
            not isinstance(result, RemoteBranch)):
3055
 
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3056
 
                                  name=name)
3057
 
        return result
3058
 
 
3059
 
    def initialize(self, a_bzrdir, name=None, repository=None,
3060
 
                   append_revisions_only=None):
3061
 
        # 1) get the network name to use.
3062
 
        if self._custom_format:
3063
 
            network_name = self._custom_format.network_name()
3064
 
        else:
3065
 
            # Select the current bzrlib default and ask for that.
3066
 
            reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3067
 
            reference_format = reference_bzrdir_format.get_branch_format()
3068
 
            self._custom_format = reference_format
3069
 
            network_name = reference_format.network_name()
3070
 
        # Being asked to create on a non RemoteBzrDir:
3071
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
3072
 
            return self._vfs_initialize(a_bzrdir, name=name,
3073
 
                append_revisions_only=append_revisions_only)
3074
 
        medium = a_bzrdir._client._medium
3075
 
        if medium._is_remote_before((1, 13)):
3076
 
            return self._vfs_initialize(a_bzrdir, name=name,
3077
 
                append_revisions_only=append_revisions_only)
3078
 
        # Creating on a remote bzr dir.
3079
 
        # 2) try direct creation via RPC
3080
 
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3081
 
        if name is not None:
3082
 
            # XXX JRV20100304: Support creating colocated branches
3083
 
            raise errors.NoColocatedBranchSupport(self)
3084
 
        verb = 'BzrDir.create_branch'
3085
 
        try:
3086
 
            response = a_bzrdir._call(verb, path, network_name)
3087
 
        except errors.UnknownSmartMethod:
3088
 
            # Fallback - use vfs methods
3089
 
            medium._remember_remote_is_before((1, 13))
3090
 
            return self._vfs_initialize(a_bzrdir, name=name,
3091
 
                    append_revisions_only=append_revisions_only)
3092
 
        if response[0] != 'ok':
3093
 
            raise errors.UnexpectedSmartServerResponse(response)
3094
 
        # Turn the response into a RemoteRepository object.
3095
 
        format = RemoteBranchFormat(network_name=response[1])
3096
 
        repo_format = response_tuple_to_repo_format(response[3:])
3097
 
        repo_path = response[2]
3098
 
        if repository is not None:
3099
 
            remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3100
 
            url_diff = urlutils.relative_url(repository.user_url,
3101
 
                    remote_repo_url)
3102
 
            if url_diff != '.':
3103
 
                raise AssertionError(
3104
 
                    'repository.user_url %r does not match URL from server '
3105
 
                    'response (%r + %r)'
3106
 
                    % (repository.user_url, a_bzrdir.user_url, repo_path))
3107
 
            remote_repo = repository
3108
 
        else:
3109
 
            if repo_path == '':
3110
 
                repo_bzrdir = a_bzrdir
3111
 
            else:
3112
 
                repo_bzrdir = RemoteBzrDir(
3113
 
                    a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3114
 
                    a_bzrdir._client)
3115
 
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3116
 
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3117
 
            format=format, setup_stacking=False, name=name)
3118
 
        if append_revisions_only:
3119
 
            remote_branch.set_append_revisions_only(append_revisions_only)
3120
 
        # XXX: We know this is a new branch, so it must have revno 0, revid
3121
 
        # NULL_REVISION. Creating the branch locked would make this be unable
3122
 
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3123
 
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
3124
 
        return remote_branch
3125
 
 
3126
 
    def make_tags(self, branch):
3127
 
        self._ensure_real()
3128
 
        return self._custom_format.make_tags(branch)
3129
 
 
3130
 
    def supports_tags(self):
3131
 
        # Remote branches might support tags, but we won't know until we
3132
 
        # access the real remote branch.
3133
 
        self._ensure_real()
3134
 
        return self._custom_format.supports_tags()
3135
 
 
3136
 
    def supports_stacking(self):
3137
 
        self._ensure_real()
3138
 
        return self._custom_format.supports_stacking()
3139
 
 
3140
 
    def supports_set_append_revisions_only(self):
3141
 
        self._ensure_real()
3142
 
        return self._custom_format.supports_set_append_revisions_only()
3143
 
 
3144
 
    def _use_default_local_heads_to_fetch(self):
3145
 
        # If the branch format is a metadir format *and* its heads_to_fetch
3146
 
        # implementation is not overridden vs the base class, we can use the
3147
 
        # base class logic rather than use the heads_to_fetch RPC.  This is
3148
 
        # usually cheaper in terms of net round trips, as the last-revision and
3149
 
        # tags info fetched is cached and would be fetched anyway.
3150
 
        self._ensure_real()
3151
 
        if isinstance(self._custom_format, branch.BranchFormatMetadir):
3152
 
            branch_class = self._custom_format._branch_class()
3153
 
            heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3154
 
            if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3155
 
                return True
3156
 
        return False
3157
 
 
3158
 
 
3159
 
class RemoteBranchStore(config.IniFileStore):
3160
 
    """Branch store which attempts to use HPSS calls to retrieve branch store.
3161
 
 
3162
 
    Note that this is specific to bzr-based formats.
3163
 
    """
3164
 
 
3165
 
    def __init__(self, branch):
3166
 
        super(RemoteBranchStore, self).__init__()
3167
 
        self.branch = branch
3168
 
        self.id = "branch"
3169
 
        self._real_store = None
3170
 
 
3171
 
    def lock_write(self, token=None):
3172
 
        return self.branch.lock_write(token)
3173
 
 
3174
 
    def unlock(self):
3175
 
        return self.branch.unlock()
3176
 
 
3177
 
    @needs_write_lock
3178
 
    def save(self):
3179
 
        # We need to be able to override the undecorated implementation
3180
 
        self.save_without_locking()
3181
 
 
3182
 
    def save_without_locking(self):
3183
 
        super(RemoteBranchStore, self).save()
3184
 
 
3185
 
    def external_url(self):
3186
 
        return self.branch.user_url
3187
 
 
3188
 
    def _load_content(self):
3189
 
        path = self.branch._remote_path()
3190
 
        try:
3191
 
            response, handler = self.branch._call_expecting_body(
3192
 
                'Branch.get_config_file', path)
3193
 
        except errors.UnknownSmartMethod:
3194
 
            self._ensure_real()
3195
 
            return self._real_store._load_content()
3196
 
        if len(response) and response[0] != 'ok':
3197
 
            raise errors.UnexpectedSmartServerResponse(response)
3198
 
        return handler.read_body_bytes()
3199
 
 
3200
 
    def _save_content(self, content):
3201
 
        path = self.branch._remote_path()
3202
 
        try:
3203
 
            response, handler = self.branch._call_with_body_bytes_expecting_body(
3204
 
                'Branch.put_config_file', (path,
3205
 
                    self.branch._lock_token, self.branch._repo_lock_token),
3206
 
                content)
3207
 
        except errors.UnknownSmartMethod:
3208
 
            self._ensure_real()
3209
 
            return self._real_store._save_content(content)
3210
 
        handler.cancel_read_body()
3211
 
        if response != ('ok', ):
3212
 
            raise errors.UnexpectedSmartServerResponse(response)
3213
 
 
3214
 
    def _ensure_real(self):
3215
 
        self.branch._ensure_real()
3216
 
        if self._real_store is None:
3217
 
            self._real_store = config.BranchStore(self.branch)
3218
 
 
3219
 
 
3220
 
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
 
734
        return 'Remote BZR Branch'
 
735
 
 
736
    def get_format_string(self):
 
737
        return 'Remote BZR Branch'
 
738
 
 
739
    def open(self, a_bzrdir):
 
740
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
741
        return a_bzrdir.open_branch()
 
742
 
 
743
    def initialize(self, a_bzrdir):
 
744
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
745
        return a_bzrdir.create_branch()
 
746
 
 
747
 
 
748
class RemoteBranch(branch.Branch):
3221
749
    """Branch stored on a server accessed by HPSS RPC.
3222
750
 
3223
751
    At the moment most operations are mapped down to simple file operations.
3224
752
    """
3225
753
 
3226
754
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3227
 
        _client=None, format=None, setup_stacking=True, name=None,
3228
 
        possible_transports=None):
 
755
        _client=None):
3229
756
        """Create a RemoteBranch instance.
3230
757
 
3231
758
        :param real_branch: An optional local implementation of the branch
3232
759
            format, usually accessing the data via the VFS.
3233
760
        :param _client: Private parameter for testing.
3234
 
        :param format: A RemoteBranchFormat object, None to create one
3235
 
            automatically. If supplied it should have a network_name already
3236
 
            supplied.
3237
 
        :param setup_stacking: If True make an RPC call to determine the
3238
 
            stacked (or not) status of the branch. If False assume the branch
3239
 
            is not stacked.
3240
 
        :param name: Colocated branch name
3241
761
        """
3242
762
        # We intentionally don't call the parent class's __init__, because it
3243
763
        # will try to assign to self.tags, which is a property in this subclass.
3244
764
        # And the parent's __init__ doesn't do much anyway.
 
765
        self._revision_history_cache = None
3245
766
        self.bzrdir = remote_bzrdir
3246
767
        if _client is not None:
3247
768
            self._client = _client
3248
769
        else:
3249
 
            self._client = remote_bzrdir._client
 
770
            self._client = client._SmartClient(self.bzrdir._medium)
3250
771
        self.repository = remote_repository
3251
772
        if real_branch is not None:
3252
773
            self._real_branch = real_branch
3260
781
            self._real_branch.repository = self.repository
3261
782
        else:
3262
783
            self._real_branch = None
3263
 
        # Fill out expected attributes of branch for bzrlib API users.
3264
 
        self._clear_cached_state()
3265
 
        # TODO: deprecate self.base in favor of user_url
3266
 
        self.base = self.bzrdir.user_url
3267
 
        self._name = name
 
784
        # Fill out expected attributes of branch for bzrlib api users.
 
785
        self._format = RemoteBranchFormat()
 
786
        self.base = self.bzrdir.root_transport.base
3268
787
        self._control_files = None
3269
788
        self._lock_mode = None
3270
789
        self._lock_token = None
3271
 
        self._repo_lock_token = None
3272
790
        self._lock_count = 0
3273
791
        self._leave_lock = False
3274
 
        # Setup a format: note that we cannot call _ensure_real until all the
3275
 
        # attributes above are set: This code cannot be moved higher up in this
3276
 
        # function.
3277
 
        if format is None:
3278
 
            self._format = RemoteBranchFormat()
3279
 
            if real_branch is not None:
3280
 
                self._format._network_name = \
3281
 
                    self._real_branch._format.network_name()
3282
 
        else:
3283
 
            self._format = format
3284
 
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
3285
 
        # branch.open_branch method.
3286
 
        self._real_ignore_fallbacks = not setup_stacking
3287
 
        if not self._format._network_name:
3288
 
            # Did not get from open_branchV2 - old server.
3289
 
            self._ensure_real()
3290
 
            self._format._network_name = \
3291
 
                self._real_branch._format.network_name()
3292
 
        self.tags = self._format.make_tags(self)
3293
 
        # The base class init is not called, so we duplicate this:
3294
 
        hooks = branch.Branch.hooks['open']
3295
 
        for hook in hooks:
3296
 
            hook(self)
3297
 
        self._is_stacked = False
3298
 
        if setup_stacking:
3299
 
            self._setup_stacking(possible_transports)
3300
 
 
3301
 
    def _setup_stacking(self, possible_transports):
3302
 
        # configure stacking into the remote repository, by reading it from
3303
 
        # the vfs branch.
3304
 
        try:
3305
 
            fallback_url = self.get_stacked_on_url()
3306
 
        except (errors.NotStacked, errors.UnstackableBranchFormat,
3307
 
            errors.UnstackableRepositoryFormat), e:
3308
 
            return
3309
 
        self._is_stacked = True
3310
 
        if possible_transports is None:
3311
 
            possible_transports = []
3312
 
        else:
3313
 
            possible_transports = list(possible_transports)
3314
 
        possible_transports.append(self.bzrdir.root_transport)
3315
 
        self._activate_fallback_location(fallback_url,
3316
 
            possible_transports=possible_transports)
3317
 
 
3318
 
    def _get_config(self):
3319
 
        return RemoteBranchConfig(self)
3320
 
 
3321
 
    def _get_config_store(self):
3322
 
        return RemoteBranchStore(self)
3323
 
 
3324
 
    def _get_real_transport(self):
3325
 
        # if we try vfs access, return the real branch's vfs transport
3326
 
        self._ensure_real()
3327
 
        return self._real_branch._transport
3328
 
 
3329
 
    _transport = property(_get_real_transport)
3330
792
 
3331
793
    def __str__(self):
3332
794
        return "%s(%s)" % (self.__class__.__name__, self.base)
3338
800
 
3339
801
        Used before calls to self._real_branch.
3340
802
        """
3341
 
        if self._real_branch is None:
3342
 
            if not vfs.vfs_enabled():
3343
 
                raise AssertionError('smart server vfs must be enabled '
3344
 
                    'to use vfs implementation')
 
803
        if not self._real_branch:
 
804
            assert vfs.vfs_enabled()
3345
805
            self.bzrdir._ensure_real()
3346
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3347
 
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3348
 
            if self.repository._real_repository is None:
3349
 
                # Give the remote repository the matching real repo.
3350
 
                real_repo = self._real_branch.repository
3351
 
                if isinstance(real_repo, RemoteRepository):
3352
 
                    real_repo._ensure_real()
3353
 
                    real_repo = real_repo._real_repository
3354
 
                self.repository._set_real_repository(real_repo)
3355
 
            # Give the real branch the remote repository to let fast-pathing
3356
 
            # happen.
 
806
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
807
            # Give the remote repository the matching real repo.
 
808
            real_repo = self._real_branch.repository
 
809
            if isinstance(real_repo, RemoteRepository):
 
810
                real_repo._ensure_real()
 
811
                real_repo = real_repo._real_repository
 
812
            self.repository._set_real_repository(real_repo)
 
813
            # Give the branch the remote repository to let fast-pathing happen.
3357
814
            self._real_branch.repository = self.repository
 
815
            # XXX: deal with _lock_mode == 'w'
3358
816
            if self._lock_mode == 'r':
3359
817
                self._real_branch.lock_read()
3360
 
            elif self._lock_mode == 'w':
3361
 
                self._real_branch.lock_write(token=self._lock_token)
3362
 
 
3363
 
    def _translate_error(self, err, **context):
3364
 
        self.repository._translate_error(err, branch=self, **context)
3365
 
 
3366
 
    def _clear_cached_state(self):
3367
 
        super(RemoteBranch, self)._clear_cached_state()
3368
 
        if self._real_branch is not None:
3369
 
            self._real_branch._clear_cached_state()
3370
 
 
3371
 
    def _clear_cached_state_of_remote_branch_only(self):
3372
 
        """Like _clear_cached_state, but doesn't clear the cache of
3373
 
        self._real_branch.
3374
 
 
3375
 
        This is useful when falling back to calling a method of
3376
 
        self._real_branch that changes state.  In that case the underlying
3377
 
        branch changes, so we need to invalidate this RemoteBranch's cache of
3378
 
        it.  However, there's no need to invalidate the _real_branch's cache
3379
 
        too, in fact doing so might harm performance.
3380
 
        """
3381
 
        super(RemoteBranch, self)._clear_cached_state()
3382
818
 
3383
819
    @property
3384
820
    def control_files(self):
3389
825
                self.bzrdir, self._client)
3390
826
        return self._control_files
3391
827
 
 
828
    def _get_checkout_format(self):
 
829
        self._ensure_real()
 
830
        return self._real_branch._get_checkout_format()
 
831
 
3392
832
    def get_physical_lock_status(self):
3393
833
        """See Branch.get_physical_lock_status()."""
3394
 
        try:
3395
 
            response = self._client.call('Branch.get_physical_lock_status',
3396
 
                self._remote_path())
3397
 
        except errors.UnknownSmartMethod:
3398
 
            self._ensure_real()
3399
 
            return self._real_branch.get_physical_lock_status()
3400
 
        if response[0] not in ('yes', 'no'):
3401
 
            raise errors.UnexpectedSmartServerResponse(response)
3402
 
        return (response[0] == 'yes')
3403
 
 
3404
 
    def get_stacked_on_url(self):
3405
 
        """Get the URL this branch is stacked against.
3406
 
 
3407
 
        :raises NotStacked: If the branch is not stacked.
3408
 
        :raises UnstackableBranchFormat: If the branch does not support
3409
 
            stacking.
3410
 
        :raises UnstackableRepositoryFormat: If the repository does not support
3411
 
            stacking.
3412
 
        """
3413
 
        try:
3414
 
            # there may not be a repository yet, so we can't use
3415
 
            # self._translate_error, so we can't use self._call either.
3416
 
            response = self._client.call('Branch.get_stacked_on_url',
3417
 
                self._remote_path())
3418
 
        except errors.ErrorFromSmartServer, err:
3419
 
            # there may not be a repository yet, so we can't call through
3420
 
            # its _translate_error
3421
 
            _translate_error(err, branch=self)
3422
 
        except errors.UnknownSmartMethod, err:
3423
 
            self._ensure_real()
3424
 
            return self._real_branch.get_stacked_on_url()
3425
 
        if response[0] != 'ok':
3426
 
            raise errors.UnexpectedSmartServerResponse(response)
3427
 
        return response[1]
3428
 
 
3429
 
    def set_stacked_on_url(self, url):
3430
 
        branch.Branch.set_stacked_on_url(self, url)
3431
 
        if not url:
3432
 
            self._is_stacked = False
3433
 
        else:
3434
 
            self._is_stacked = True
3435
 
 
3436
 
    def _vfs_get_tags_bytes(self):
3437
 
        self._ensure_real()
3438
 
        return self._real_branch._get_tags_bytes()
3439
 
 
3440
 
    @needs_read_lock
3441
 
    def _get_tags_bytes(self):
3442
 
        if self._tags_bytes is None:
3443
 
            self._tags_bytes = self._get_tags_bytes_via_hpss()
3444
 
        return self._tags_bytes
3445
 
 
3446
 
    def _get_tags_bytes_via_hpss(self):
3447
 
        medium = self._client._medium
3448
 
        if medium._is_remote_before((1, 13)):
3449
 
            return self._vfs_get_tags_bytes()
3450
 
        try:
3451
 
            response = self._call('Branch.get_tags_bytes', self._remote_path())
3452
 
        except errors.UnknownSmartMethod:
3453
 
            medium._remember_remote_is_before((1, 13))
3454
 
            return self._vfs_get_tags_bytes()
3455
 
        return response[0]
3456
 
 
3457
 
    def _vfs_set_tags_bytes(self, bytes):
3458
 
        self._ensure_real()
3459
 
        return self._real_branch._set_tags_bytes(bytes)
3460
 
 
3461
 
    def _set_tags_bytes(self, bytes):
3462
 
        if self.is_locked():
3463
 
            self._tags_bytes = bytes
3464
 
        medium = self._client._medium
3465
 
        if medium._is_remote_before((1, 18)):
3466
 
            self._vfs_set_tags_bytes(bytes)
3467
 
            return
3468
 
        try:
3469
 
            args = (
3470
 
                self._remote_path(), self._lock_token, self._repo_lock_token)
3471
 
            response = self._call_with_body_bytes(
3472
 
                'Branch.set_tags_bytes', args, bytes)
3473
 
        except errors.UnknownSmartMethod:
3474
 
            medium._remember_remote_is_before((1, 18))
3475
 
            self._vfs_set_tags_bytes(bytes)
 
834
        # should be an API call to the server, as branches must be lockable.
 
835
        self._ensure_real()
 
836
        return self._real_branch.get_physical_lock_status()
3476
837
 
3477
838
    def lock_read(self):
3478
 
        """Lock the branch for read operations.
3479
 
 
3480
 
        :return: A bzrlib.lock.LogicalLockResult.
3481
 
        """
3482
 
        self.repository.lock_read()
3483
839
        if not self._lock_mode:
3484
 
            self._note_lock('r')
3485
840
            self._lock_mode = 'r'
3486
841
            self._lock_count = 1
3487
842
            if self._real_branch is not None:
3488
843
                self._real_branch.lock_read()
3489
844
        else:
3490
845
            self._lock_count += 1
3491
 
        return lock.LogicalLockResult(self.unlock)
3492
846
 
3493
847
    def _remote_lock_write(self, token):
3494
848
        if token is None:
3495
849
            branch_token = repo_token = ''
3496
850
        else:
3497
851
            branch_token = token
3498
 
            repo_token = self.repository.lock_write().repository_token
 
852
            repo_token = self.repository.lock_write()
3499
853
            self.repository.unlock()
3500
 
        err_context = {'token': token}
3501
 
        try:
3502
 
            response = self._call(
3503
 
                'Branch.lock_write', self._remote_path(), branch_token,
3504
 
                repo_token or '', **err_context)
3505
 
        except errors.LockContention, e:
3506
 
            # The LockContention from the server doesn't have any
3507
 
            # information about the lock_url. We re-raise LockContention
3508
 
            # with valid lock_url.
3509
 
            raise errors.LockContention('(remote lock)',
3510
 
                self.repository.base.split('.bzr/')[0])
3511
 
        if response[0] != 'ok':
 
854
        path = self.bzrdir._path_for_remote_call(self._client)
 
855
        response = self._client.call('Branch.lock_write', path, branch_token,
 
856
                                     repo_token)
 
857
        if response[0] == 'ok':
 
858
            ok, branch_token, repo_token = response
 
859
            return branch_token, repo_token
 
860
        elif response[0] == 'LockContention':
 
861
            raise errors.LockContention('(remote lock)')
 
862
        elif response[0] == 'TokenMismatch':
 
863
            raise errors.TokenMismatch(token, '(remote token)')
 
864
        elif response[0] == 'UnlockableTransport':
 
865
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
866
        elif response[0] == 'ReadOnlyError':
 
867
            raise errors.ReadOnlyError(self)
 
868
        else:
3512
869
            raise errors.UnexpectedSmartServerResponse(response)
3513
 
        ok, branch_token, repo_token = response
3514
 
        return branch_token, repo_token
3515
 
 
 
870
            
3516
871
    def lock_write(self, token=None):
3517
872
        if not self._lock_mode:
3518
 
            self._note_lock('w')
3519
 
            # Lock the branch and repo in one remote call.
3520
873
            remote_tokens = self._remote_lock_write(token)
3521
874
            self._lock_token, self._repo_lock_token = remote_tokens
3522
 
            if not self._lock_token:
3523
 
                raise SmartProtocolError('Remote server did not return a token!')
3524
 
            # Tell the self.repository object that it is locked.
3525
 
            self.repository.lock_write(
3526
 
                self._repo_lock_token, _skip_rpc=True)
3527
 
 
 
875
            assert self._lock_token, 'Remote server did not return a token!'
 
876
            # TODO: We really, really, really don't want to call _ensure_real
 
877
            # here, but it's the easiest way to ensure coherency between the
 
878
            # state of the RemoteBranch and RemoteRepository objects and the
 
879
            # physical locks.  If we don't materialise the real objects here,
 
880
            # then getting everything in the right state later is complex, so
 
881
            # for now we just do it the lazy way.
 
882
            #   -- Andrew Bennetts, 2007-02-22.
 
883
            self._ensure_real()
3528
884
            if self._real_branch is not None:
3529
 
                self._real_branch.lock_write(token=self._lock_token)
 
885
                self._real_branch.repository.lock_write(
 
886
                    token=self._repo_lock_token)
 
887
                try:
 
888
                    self._real_branch.lock_write(token=self._lock_token)
 
889
                finally:
 
890
                    self._real_branch.repository.unlock()
3530
891
            if token is not None:
3531
892
                self._leave_lock = True
3532
893
            else:
 
894
                # XXX: this case seems to be unreachable; token cannot be None.
3533
895
                self._leave_lock = False
3534
896
            self._lock_mode = 'w'
3535
897
            self._lock_count = 1
3536
898
        elif self._lock_mode == 'r':
3537
 
            raise errors.ReadOnlyError(self)
 
899
            raise errors.ReadOnlyTransaction
3538
900
        else:
3539
901
            if token is not None:
3540
 
                # A token was given to lock_write, and we're relocking, so
3541
 
                # check that the given token actually matches the one we
3542
 
                # already have.
 
902
                # A token was given to lock_write, and we're relocking, so check
 
903
                # that the given token actually matches the one we already have.
3543
904
                if token != self._lock_token:
3544
905
                    raise errors.TokenMismatch(token, self._lock_token)
3545
906
            self._lock_count += 1
3546
 
            # Re-lock the repository too.
3547
 
            self.repository.lock_write(self._repo_lock_token)
3548
 
        return BranchWriteLockResult(self.unlock, self._lock_token or None)
 
907
        return self._lock_token
3549
908
 
3550
909
    def _unlock(self, branch_token, repo_token):
3551
 
        err_context = {'token': str((branch_token, repo_token))}
3552
 
        response = self._call(
3553
 
            'Branch.unlock', self._remote_path(), branch_token,
3554
 
            repo_token or '', **err_context)
 
910
        path = self.bzrdir._path_for_remote_call(self._client)
 
911
        response = self._client.call('Branch.unlock', path, branch_token,
 
912
                                     repo_token)
3555
913
        if response == ('ok',):
3556
914
            return
3557
 
        raise errors.UnexpectedSmartServerResponse(response)
 
915
        elif response[0] == 'TokenMismatch':
 
916
            raise errors.TokenMismatch(
 
917
                str((branch_token, repo_token)), '(remote tokens)')
 
918
        else:
 
919
            raise errors.UnexpectedSmartServerResponse(response)
3558
920
 
3559
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
3560
921
    def unlock(self):
3561
 
        try:
3562
 
            self._lock_count -= 1
3563
 
            if not self._lock_count:
3564
 
                self._clear_cached_state()
3565
 
                mode = self._lock_mode
3566
 
                self._lock_mode = None
3567
 
                if self._real_branch is not None:
3568
 
                    if (not self._leave_lock and mode == 'w' and
3569
 
                        self._repo_lock_token):
3570
 
                        # If this RemoteBranch will remove the physical lock
3571
 
                        # for the repository, make sure the _real_branch
3572
 
                        # doesn't do it first.  (Because the _real_branch's
3573
 
                        # repository is set to be the RemoteRepository.)
3574
 
                        self._real_branch.repository.leave_lock_in_place()
3575
 
                    self._real_branch.unlock()
3576
 
                if mode != 'w':
3577
 
                    # Only write-locked branched need to make a remote method
3578
 
                    # call to perform the unlock.
3579
 
                    return
3580
 
                if not self._lock_token:
3581
 
                    raise AssertionError('Locked, but no token!')
3582
 
                branch_token = self._lock_token
3583
 
                repo_token = self._repo_lock_token
3584
 
                self._lock_token = None
3585
 
                self._repo_lock_token = None
 
922
        self._lock_count -= 1
 
923
        if not self._lock_count:
 
924
            self._clear_cached_state()
 
925
            mode = self._lock_mode
 
926
            self._lock_mode = None
 
927
            if self._real_branch is not None:
3586
928
                if not self._leave_lock:
3587
 
                    self._unlock(branch_token, repo_token)
3588
 
        finally:
3589
 
            self.repository.unlock()
 
929
                    # If this RemoteBranch will remove the physical lock for the
 
930
                    # repository, make sure the _real_branch doesn't do it
 
931
                    # first.  (Because the _real_branch's repository is set to
 
932
                    # be the RemoteRepository.)
 
933
                    self._real_branch.repository.leave_lock_in_place()
 
934
                self._real_branch.unlock()
 
935
            if mode != 'w':
 
936
                # Only write-locked branched need to make a remote method call
 
937
                # to perfom the unlock.
 
938
                return
 
939
            assert self._lock_token, 'Locked, but no token!'
 
940
            branch_token = self._lock_token
 
941
            repo_token = self._repo_lock_token
 
942
            self._lock_token = None
 
943
            self._repo_lock_token = None
 
944
            if not self._leave_lock:
 
945
                self._unlock(branch_token, repo_token)
3590
946
 
3591
947
    def break_lock(self):
3592
 
        try:
3593
 
            response = self._call(
3594
 
                'Branch.break_lock', self._remote_path())
3595
 
        except errors.UnknownSmartMethod:
3596
 
            self._ensure_real()
3597
 
            return self._real_branch.break_lock()
3598
 
        if response != ('ok',):
3599
 
            raise errors.UnexpectedSmartServerResponse(response)
 
948
        self._ensure_real()
 
949
        return self._real_branch.break_lock()
3600
950
 
3601
951
    def leave_lock_in_place(self):
3602
 
        if not self._lock_token:
3603
 
            raise NotImplementedError(self.leave_lock_in_place)
3604
952
        self._leave_lock = True
3605
953
 
3606
954
    def dont_leave_lock_in_place(self):
3607
 
        if not self._lock_token:
3608
 
            raise NotImplementedError(self.dont_leave_lock_in_place)
3609
955
        self._leave_lock = False
3610
956
 
3611
 
    @needs_read_lock
3612
 
    def get_rev_id(self, revno, history=None):
3613
 
        if revno == 0:
3614
 
            return _mod_revision.NULL_REVISION
3615
 
        last_revision_info = self.last_revision_info()
3616
 
        ok, result = self.repository.get_rev_id_for_revno(
3617
 
            revno, last_revision_info)
3618
 
        if ok:
3619
 
            return result
3620
 
        missing_parent = result[1]
3621
 
        # Either the revision named by the server is missing, or its parent
3622
 
        # is.  Call get_parent_map to determine which, so that we report a
3623
 
        # useful error.
3624
 
        parent_map = self.repository.get_parent_map([missing_parent])
3625
 
        if missing_parent in parent_map:
3626
 
            missing_parent = parent_map[missing_parent]
3627
 
        raise errors.RevisionNotPresent(missing_parent, self.repository)
3628
 
 
3629
 
    def _read_last_revision_info(self):
3630
 
        response = self._call('Branch.last_revision_info', self._remote_path())
3631
 
        if response[0] != 'ok':
3632
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
957
    def last_revision_info(self):
 
958
        """See Branch.last_revision_info()."""
 
959
        path = self.bzrdir._path_for_remote_call(self._client)
 
960
        response = self._client.call('Branch.last_revision_info', path)
 
961
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
3633
962
        revno = int(response[1])
3634
963
        last_revision = response[2]
3635
964
        return (revno, last_revision)
3636
965
 
3637
966
    def _gen_revision_history(self):
3638
967
        """See Branch._gen_revision_history()."""
3639
 
        if self._is_stacked:
3640
 
            self._ensure_real()
3641
 
            return self._real_branch._gen_revision_history()
3642
 
        response_tuple, response_handler = self._call_expecting_body(
3643
 
            'Branch.revision_history', self._remote_path())
3644
 
        if response_tuple[0] != 'ok':
3645
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3646
 
        result = response_handler.read_body_bytes().split('\x00')
 
968
        path = self.bzrdir._path_for_remote_call(self._client)
 
969
        response = self._client.call_expecting_body(
 
970
            'Branch.revision_history', path)
 
971
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
972
                                        % (response[0],))
 
973
        result = response[1].read_body_bytes().split('\x00')
3647
974
        if result == ['']:
3648
975
            return []
3649
976
        return result
3650
977
 
3651
 
    def _remote_path(self):
3652
 
        return self.bzrdir._path_for_remote_call(self._client)
3653
 
 
3654
 
    def _set_last_revision_descendant(self, revision_id, other_branch,
3655
 
            allow_diverged=False, allow_overwrite_descendant=False):
3656
 
        # This performs additional work to meet the hook contract; while its
3657
 
        # undesirable, we have to synthesise the revno to call the hook, and
3658
 
        # not calling the hook is worse as it means changes can't be prevented.
3659
 
        # Having calculated this though, we can't just call into
3660
 
        # set_last_revision_info as a simple call, because there is a set_rh
3661
 
        # hook that some folk may still be using.
3662
 
        old_revno, old_revid = self.last_revision_info()
3663
 
        history = self._lefthand_history(revision_id)
3664
 
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3665
 
        err_context = {'other_branch': other_branch}
3666
 
        response = self._call('Branch.set_last_revision_ex',
3667
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
3668
 
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3669
 
            **err_context)
3670
 
        self._clear_cached_state()
3671
 
        if len(response) != 3 and response[0] != 'ok':
3672
 
            raise errors.UnexpectedSmartServerResponse(response)
3673
 
        new_revno, new_revision_id = response[1:]
3674
 
        self._last_revision_info_cache = new_revno, new_revision_id
3675
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3676
 
        if self._real_branch is not None:
3677
 
            cache = new_revno, new_revision_id
3678
 
            self._real_branch._last_revision_info_cache = cache
3679
 
 
3680
 
    def _set_last_revision(self, revision_id):
3681
 
        old_revno, old_revid = self.last_revision_info()
3682
 
        # This performs additional work to meet the hook contract; while its
3683
 
        # undesirable, we have to synthesise the revno to call the hook, and
3684
 
        # not calling the hook is worse as it means changes can't be prevented.
3685
 
        # Having calculated this though, we can't just call into
3686
 
        # set_last_revision_info as a simple call, because there is a set_rh
3687
 
        # hook that some folk may still be using.
3688
 
        history = self._lefthand_history(revision_id)
3689
 
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3690
 
        self._clear_cached_state()
3691
 
        response = self._call('Branch.set_last_revision',
3692
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
3693
 
            revision_id)
3694
 
        if response != ('ok',):
3695
 
            raise errors.UnexpectedSmartServerResponse(response)
3696
 
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3697
 
 
3698
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3699
978
    @needs_write_lock
3700
979
    def set_revision_history(self, rev_history):
3701
 
        """See Branch.set_revision_history."""
3702
 
        self._set_revision_history(rev_history)
3703
 
 
3704
 
    @needs_write_lock
3705
 
    def _set_revision_history(self, rev_history):
3706
980
        # Send just the tip revision of the history; the server will generate
3707
981
        # the full history from that.  If the revision doesn't exist in this
3708
982
        # branch, NoSuchRevision will be raised.
 
983
        path = self.bzrdir._path_for_remote_call(self._client)
3709
984
        if rev_history == []:
3710
985
            rev_id = 'null:'
3711
986
        else:
3712
987
            rev_id = rev_history[-1]
3713
 
        self._set_last_revision(rev_id)
3714
 
        for hook in branch.Branch.hooks['set_rh']:
3715
 
            hook(self, rev_history)
 
988
        self._clear_cached_state()
 
989
        response = self._client.call('Branch.set_last_revision',
 
990
            path, self._lock_token, self._repo_lock_token, rev_id)
 
991
        if response[0] == 'NoSuchRevision':
 
992
            raise NoSuchRevision(self, rev_id)
 
993
        else:
 
994
            assert response == ('ok',), (
 
995
                'unexpected response code %r' % (response,))
3716
996
        self._cache_revision_history(rev_history)
3717
997
 
3718
 
    def _get_parent_location(self):
3719
 
        medium = self._client._medium
3720
 
        if medium._is_remote_before((1, 13)):
3721
 
            return self._vfs_get_parent_location()
3722
 
        try:
3723
 
            response = self._call('Branch.get_parent', self._remote_path())
3724
 
        except errors.UnknownSmartMethod:
3725
 
            medium._remember_remote_is_before((1, 13))
3726
 
            return self._vfs_get_parent_location()
3727
 
        if len(response) != 1:
3728
 
            raise errors.UnexpectedSmartServerResponse(response)
3729
 
        parent_location = response[0]
3730
 
        if parent_location == '':
3731
 
            return None
3732
 
        return parent_location
3733
 
 
3734
 
    def _vfs_get_parent_location(self):
3735
 
        self._ensure_real()
3736
 
        return self._real_branch._get_parent_location()
3737
 
 
3738
 
    def _set_parent_location(self, url):
3739
 
        medium = self._client._medium
3740
 
        if medium._is_remote_before((1, 15)):
3741
 
            return self._vfs_set_parent_location(url)
3742
 
        try:
3743
 
            call_url = url or ''
3744
 
            if type(call_url) is not str:
3745
 
                raise AssertionError('url must be a str or None (%s)' % url)
3746
 
            response = self._call('Branch.set_parent_location',
3747
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
3748
 
                call_url)
3749
 
        except errors.UnknownSmartMethod:
3750
 
            medium._remember_remote_is_before((1, 15))
3751
 
            return self._vfs_set_parent_location(url)
3752
 
        if response != ():
3753
 
            raise errors.UnexpectedSmartServerResponse(response)
3754
 
 
3755
 
    def _vfs_set_parent_location(self, url):
3756
 
        self._ensure_real()
3757
 
        return self._real_branch._set_parent_location(url)
 
998
    def get_parent(self):
 
999
        self._ensure_real()
 
1000
        return self._real_branch.get_parent()
 
1001
        
 
1002
    def set_parent(self, url):
 
1003
        self._ensure_real()
 
1004
        return self._real_branch.set_parent(url)
 
1005
        
 
1006
    def get_config(self):
 
1007
        return RemoteBranchConfig(self)
 
1008
 
 
1009
    def sprout(self, to_bzrdir, revision_id=None):
 
1010
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1011
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1012
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1013
        # to_bzrdir.create_branch...
 
1014
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
1015
        self.copy_content_into(result, revision_id=revision_id)
 
1016
        result.set_parent(self.bzrdir.root_transport.base)
 
1017
        return result
 
1018
 
 
1019
    @needs_write_lock
 
1020
    def append_revision(self, *revision_ids):
 
1021
        self._ensure_real()
 
1022
        return self._real_branch.append_revision(*revision_ids)
3758
1023
 
3759
1024
    @needs_write_lock
3760
1025
    def pull(self, source, overwrite=False, stop_revision=None,
3761
1026
             **kwargs):
3762
 
        self._clear_cached_state_of_remote_branch_only()
 
1027
        # FIXME: This asks the real branch to run the hooks, which means
 
1028
        # they're called with the wrong target branch parameter. 
 
1029
        # The test suite specifically allows this at present but it should be
 
1030
        # fixed.  It should get a _override_hook_target branch,
 
1031
        # as push does.  -- mbp 20070405
3763
1032
        self._ensure_real()
3764
 
        return self._real_branch.pull(
 
1033
        self._real_branch.pull(
3765
1034
            source, overwrite=overwrite, stop_revision=stop_revision,
3766
 
            _override_hook_target=self, **kwargs)
 
1035
            **kwargs)
3767
1036
 
3768
1037
    @needs_read_lock
3769
 
    def push(self, target, overwrite=False, stop_revision=None, lossy=False):
 
1038
    def push(self, target, overwrite=False, stop_revision=None):
3770
1039
        self._ensure_real()
3771
1040
        return self._real_branch.push(
3772
 
            target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
 
1041
            target, overwrite=overwrite, stop_revision=stop_revision,
3773
1042
            _override_hook_source_branch=self)
3774
1043
 
3775
1044
    def is_locked(self):
3776
1045
        return self._lock_count >= 1
3777
1046
 
3778
 
    @needs_read_lock
3779
 
    def revision_id_to_dotted_revno(self, revision_id):
3780
 
        """Given a revision id, return its dotted revno.
3781
 
 
3782
 
        :return: a tuple like (1,) or (400,1,3).
3783
 
        """
3784
 
        try:
3785
 
            response = self._call('Branch.revision_id_to_revno',
3786
 
                self._remote_path(), revision_id)
3787
 
        except errors.UnknownSmartMethod:
3788
 
            self._ensure_real()
3789
 
            return self._real_branch.revision_id_to_dotted_revno(revision_id)
3790
 
        if response[0] == 'ok':
3791
 
            return tuple([int(x) for x in response[1:]])
3792
 
        else:
3793
 
            raise errors.UnexpectedSmartServerResponse(response)
3794
 
 
3795
 
    @needs_read_lock
3796
 
    def revision_id_to_revno(self, revision_id):
3797
 
        """Given a revision id on the branch mainline, return its revno.
3798
 
 
3799
 
        :return: an integer
3800
 
        """
3801
 
        try:
3802
 
            response = self._call('Branch.revision_id_to_revno',
3803
 
                self._remote_path(), revision_id)
3804
 
        except errors.UnknownSmartMethod:
3805
 
            self._ensure_real()
3806
 
            return self._real_branch.revision_id_to_revno(revision_id)
3807
 
        if response[0] == 'ok':
3808
 
            if len(response) == 2:
3809
 
                return int(response[1])
3810
 
            raise NoSuchRevision(self, revision_id)
3811
 
        else:
3812
 
            raise errors.UnexpectedSmartServerResponse(response)
3813
 
 
3814
 
    @needs_write_lock
3815
1047
    def set_last_revision_info(self, revno, revision_id):
3816
 
        # XXX: These should be returned by the set_last_revision_info verb
3817
 
        old_revno, old_revid = self.last_revision_info()
3818
 
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
3819
 
        if not revision_id or not isinstance(revision_id, basestring):
3820
 
            raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3821
 
        try:
3822
 
            response = self._call('Branch.set_last_revision_info',
3823
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
3824
 
                str(revno), revision_id)
3825
 
        except errors.UnknownSmartMethod:
3826
 
            self._ensure_real()
3827
 
            self._clear_cached_state_of_remote_branch_only()
3828
 
            self._real_branch.set_last_revision_info(revno, revision_id)
3829
 
            self._last_revision_info_cache = revno, revision_id
3830
 
            return
3831
 
        if response == ('ok',):
3832
 
            self._clear_cached_state()
3833
 
            self._last_revision_info_cache = revno, revision_id
3834
 
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3835
 
            # Update the _real_branch's cache too.
3836
 
            if self._real_branch is not None:
3837
 
                cache = self._last_revision_info_cache
3838
 
                self._real_branch._last_revision_info_cache = cache
3839
 
        else:
3840
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1048
        self._ensure_real()
 
1049
        self._clear_cached_state()
 
1050
        return self._real_branch.set_last_revision_info(revno, revision_id)
3841
1051
 
3842
 
    @needs_write_lock
3843
1052
    def generate_revision_history(self, revision_id, last_rev=None,
3844
1053
                                  other_branch=None):
3845
 
        medium = self._client._medium
3846
 
        if not medium._is_remote_before((1, 6)):
3847
 
            # Use a smart method for 1.6 and above servers
3848
 
            try:
3849
 
                self._set_last_revision_descendant(revision_id, other_branch,
3850
 
                    allow_diverged=True, allow_overwrite_descendant=True)
3851
 
                return
3852
 
            except errors.UnknownSmartMethod:
3853
 
                medium._remember_remote_is_before((1, 6))
3854
 
        self._clear_cached_state_of_remote_branch_only()
3855
 
        self._set_revision_history(self._lefthand_history(revision_id,
3856
 
            last_rev=last_rev,other_branch=other_branch))
 
1054
        self._ensure_real()
 
1055
        return self._real_branch.generate_revision_history(
 
1056
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
1057
 
 
1058
    @property
 
1059
    def tags(self):
 
1060
        self._ensure_real()
 
1061
        return self._real_branch.tags
3857
1062
 
3858
1063
    def set_push_location(self, location):
3859
1064
        self._ensure_real()
3860
1065
        return self._real_branch.set_push_location(location)
3861
1066
 
3862
 
    def heads_to_fetch(self):
3863
 
        if self._format._use_default_local_heads_to_fetch():
3864
 
            # We recognise this format, and its heads-to-fetch implementation
3865
 
            # is the default one (tip + tags).  In this case it's cheaper to
3866
 
            # just use the default implementation rather than a special RPC as
3867
 
            # the tip and tags data is cached.
3868
 
            return branch.Branch.heads_to_fetch(self)
3869
 
        medium = self._client._medium
3870
 
        if medium._is_remote_before((2, 4)):
3871
 
            return self._vfs_heads_to_fetch()
3872
 
        try:
3873
 
            return self._rpc_heads_to_fetch()
3874
 
        except errors.UnknownSmartMethod:
3875
 
            medium._remember_remote_is_before((2, 4))
3876
 
            return self._vfs_heads_to_fetch()
3877
 
 
3878
 
    def _rpc_heads_to_fetch(self):
3879
 
        response = self._call('Branch.heads_to_fetch', self._remote_path())
3880
 
        if len(response) != 2:
3881
 
            raise errors.UnexpectedSmartServerResponse(response)
3882
 
        must_fetch, if_present_fetch = response
3883
 
        return set(must_fetch), set(if_present_fetch)
3884
 
 
3885
 
    def _vfs_heads_to_fetch(self):
 
1067
    def update_revisions(self, other, stop_revision=None):
3886
1068
        self._ensure_real()
3887
 
        return self._real_branch.heads_to_fetch()
3888
 
 
3889
 
 
3890
 
class RemoteConfig(object):
3891
 
    """A Config that reads and writes from smart verbs.
3892
 
 
3893
 
    It is a low-level object that considers config data to be name/value pairs
3894
 
    that may be associated with a section. Assigning meaning to the these
3895
 
    values is done at higher levels like bzrlib.config.TreeConfig.
3896
 
    """
3897
 
 
3898
 
    def get_option(self, name, section=None, default=None):
3899
 
        """Return the value associated with a named option.
3900
 
 
3901
 
        :param name: The name of the value
3902
 
        :param section: The section the option is in (if any)
3903
 
        :param default: The value to return if the value is not set
3904
 
        :return: The value or default value
3905
 
        """
3906
 
        try:
3907
 
            configobj = self._get_configobj()
3908
 
            section_obj = None
3909
 
            if section is None:
3910
 
                section_obj = configobj
3911
 
            else:
3912
 
                try:
3913
 
                    section_obj = configobj[section]
3914
 
                except KeyError:
3915
 
                    pass
3916
 
            if section_obj is None:
3917
 
                value = default
3918
 
            else:
3919
 
                value = section_obj.get(name, default)
3920
 
        except errors.UnknownSmartMethod:
3921
 
            value = self._vfs_get_option(name, section, default)
3922
 
        for hook in config.OldConfigHooks['get']:
3923
 
            hook(self, name, value)
3924
 
        return value
3925
 
 
3926
 
    def _response_to_configobj(self, response):
3927
 
        if len(response[0]) and response[0][0] != 'ok':
3928
 
            raise errors.UnexpectedSmartServerResponse(response)
3929
 
        lines = response[1].read_body_bytes().splitlines()
3930
 
        conf = config.ConfigObj(lines, encoding='utf-8')
3931
 
        for hook in config.OldConfigHooks['load']:
3932
 
            hook(self)
3933
 
        return conf
3934
 
 
3935
 
 
3936
 
class RemoteBranchConfig(RemoteConfig):
3937
 
    """A RemoteConfig for Branches."""
3938
 
 
3939
 
    def __init__(self, branch):
3940
 
        self._branch = branch
3941
 
 
3942
 
    def _get_configobj(self):
3943
 
        path = self._branch._remote_path()
3944
 
        response = self._branch._client.call_expecting_body(
3945
 
            'Branch.get_config_file', path)
3946
 
        return self._response_to_configobj(response)
3947
 
 
3948
 
    def set_option(self, value, name, section=None):
3949
 
        """Set the value associated with a named option.
3950
 
 
3951
 
        :param value: The value to set
3952
 
        :param name: The name of the value to set
3953
 
        :param section: The section the option is in (if any)
3954
 
        """
3955
 
        medium = self._branch._client._medium
3956
 
        if medium._is_remote_before((1, 14)):
3957
 
            return self._vfs_set_option(value, name, section)
3958
 
        if isinstance(value, dict):
3959
 
            if medium._is_remote_before((2, 2)):
3960
 
                return self._vfs_set_option(value, name, section)
3961
 
            return self._set_config_option_dict(value, name, section)
3962
 
        else:
3963
 
            return self._set_config_option(value, name, section)
3964
 
 
3965
 
    def _set_config_option(self, value, name, section):
3966
 
        try:
3967
 
            path = self._branch._remote_path()
3968
 
            response = self._branch._client.call('Branch.set_config_option',
3969
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
3970
 
                value.encode('utf8'), name, section or '')
3971
 
        except errors.UnknownSmartMethod:
3972
 
            medium = self._branch._client._medium
3973
 
            medium._remember_remote_is_before((1, 14))
3974
 
            return self._vfs_set_option(value, name, section)
3975
 
        if response != ():
3976
 
            raise errors.UnexpectedSmartServerResponse(response)
3977
 
 
3978
 
    def _serialize_option_dict(self, option_dict):
3979
 
        utf8_dict = {}
3980
 
        for key, value in option_dict.items():
3981
 
            if isinstance(key, unicode):
3982
 
                key = key.encode('utf8')
3983
 
            if isinstance(value, unicode):
3984
 
                value = value.encode('utf8')
3985
 
            utf8_dict[key] = value
3986
 
        return bencode.bencode(utf8_dict)
3987
 
 
3988
 
    def _set_config_option_dict(self, value, name, section):
3989
 
        try:
3990
 
            path = self._branch._remote_path()
3991
 
            serialised_dict = self._serialize_option_dict(value)
3992
 
            response = self._branch._client.call(
3993
 
                'Branch.set_config_option_dict',
3994
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
3995
 
                serialised_dict, name, section or '')
3996
 
        except errors.UnknownSmartMethod:
3997
 
            medium = self._branch._client._medium
3998
 
            medium._remember_remote_is_before((2, 2))
3999
 
            return self._vfs_set_option(value, name, section)
4000
 
        if response != ():
4001
 
            raise errors.UnexpectedSmartServerResponse(response)
4002
 
 
4003
 
    def _real_object(self):
4004
 
        self._branch._ensure_real()
4005
 
        return self._branch._real_branch
4006
 
 
4007
 
    def _vfs_set_option(self, value, name, section=None):
4008
 
        return self._real_object()._get_config().set_option(
4009
 
            value, name, section)
4010
 
 
4011
 
 
4012
 
class RemoteBzrDirConfig(RemoteConfig):
4013
 
    """A RemoteConfig for BzrDirs."""
4014
 
 
4015
 
    def __init__(self, bzrdir):
4016
 
        self._bzrdir = bzrdir
4017
 
 
4018
 
    def _get_configobj(self):
4019
 
        medium = self._bzrdir._client._medium
4020
 
        verb = 'BzrDir.get_config_file'
4021
 
        if medium._is_remote_before((1, 15)):
4022
 
            raise errors.UnknownSmartMethod(verb)
4023
 
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4024
 
        response = self._bzrdir._call_expecting_body(
4025
 
            verb, path)
4026
 
        return self._response_to_configobj(response)
4027
 
 
4028
 
    def _vfs_get_option(self, name, section, default):
4029
 
        return self._real_object()._get_config().get_option(
4030
 
            name, section, default)
4031
 
 
4032
 
    def set_option(self, value, name, section=None):
4033
 
        """Set the value associated with a named option.
4034
 
 
4035
 
        :param value: The value to set
4036
 
        :param name: The name of the value to set
4037
 
        :param section: The section the option is in (if any)
4038
 
        """
4039
 
        return self._real_object()._get_config().set_option(
4040
 
            value, name, section)
4041
 
 
4042
 
    def _real_object(self):
4043
 
        self._bzrdir._ensure_real()
4044
 
        return self._bzrdir._real_bzrdir
 
1069
        return self._real_branch.update_revisions(
 
1070
            other, stop_revision=stop_revision)
 
1071
 
 
1072
 
 
1073
class RemoteBranchConfig(BranchConfig):
 
1074
 
 
1075
    def username(self):
 
1076
        self.branch._ensure_real()
 
1077
        return self.branch._real_branch.get_config().username()
 
1078
 
 
1079
    def _get_branch_data_config(self):
 
1080
        self.branch._ensure_real()
 
1081
        if self._branch_data_config is None:
 
1082
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1083
        return self._branch_data_config
4045
1084
 
4046
1085
 
4047
1086
def _extract_tar(tar, to_dir):
4051
1090
    """
4052
1091
    for tarinfo in tar:
4053
1092
        tar.extract(tarinfo, to_dir)
4054
 
 
4055
 
 
4056
 
error_translators = registry.Registry()
4057
 
no_context_error_translators = registry.Registry()
4058
 
 
4059
 
 
4060
 
def _translate_error(err, **context):
4061
 
    """Translate an ErrorFromSmartServer into a more useful error.
4062
 
 
4063
 
    Possible context keys:
4064
 
      - branch
4065
 
      - repository
4066
 
      - bzrdir
4067
 
      - token
4068
 
      - other_branch
4069
 
      - path
4070
 
 
4071
 
    If the error from the server doesn't match a known pattern, then
4072
 
    UnknownErrorFromSmartServer is raised.
4073
 
    """
4074
 
    def find(name):
4075
 
        try:
4076
 
            return context[name]
4077
 
        except KeyError, key_err:
4078
 
            mutter('Missing key %r in context %r', key_err.args[0], context)
4079
 
            raise err
4080
 
    def get_path():
4081
 
        """Get the path from the context if present, otherwise use first error
4082
 
        arg.
4083
 
        """
4084
 
        try:
4085
 
            return context['path']
4086
 
        except KeyError, key_err:
4087
 
            try:
4088
 
                return err.error_args[0]
4089
 
            except IndexError, idx_err:
4090
 
                mutter(
4091
 
                    'Missing key %r in context %r', key_err.args[0], context)
4092
 
                raise err
4093
 
 
4094
 
    try:
4095
 
        translator = error_translators.get(err.error_verb)
4096
 
    except KeyError:
4097
 
        pass
4098
 
    else:
4099
 
        raise translator(err, find, get_path)
4100
 
    try:
4101
 
        translator = no_context_error_translators.get(err.error_verb)
4102
 
    except KeyError:
4103
 
        raise errors.UnknownErrorFromSmartServer(err)
4104
 
    else:
4105
 
        raise translator(err)
4106
 
 
4107
 
 
4108
 
error_translators.register('NoSuchRevision',
4109
 
    lambda err, find, get_path: NoSuchRevision(
4110
 
        find('branch'), err.error_args[0]))
4111
 
error_translators.register('nosuchrevision',
4112
 
    lambda err, find, get_path: NoSuchRevision(
4113
 
        find('repository'), err.error_args[0]))
4114
 
 
4115
 
def _translate_nobranch_error(err, find, get_path):
4116
 
    if len(err.error_args) >= 1:
4117
 
        extra = err.error_args[0]
4118
 
    else:
4119
 
        extra = None
4120
 
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4121
 
        detail=extra)
4122
 
 
4123
 
error_translators.register('nobranch', _translate_nobranch_error)
4124
 
error_translators.register('norepository',
4125
 
    lambda err, find, get_path: errors.NoRepositoryPresent(
4126
 
        find('bzrdir')))
4127
 
error_translators.register('UnlockableTransport',
4128
 
    lambda err, find, get_path: errors.UnlockableTransport(
4129
 
        find('bzrdir').root_transport))
4130
 
error_translators.register('TokenMismatch',
4131
 
    lambda err, find, get_path: errors.TokenMismatch(
4132
 
        find('token'), '(remote token)'))
4133
 
error_translators.register('Diverged',
4134
 
    lambda err, find, get_path: errors.DivergedBranches(
4135
 
        find('branch'), find('other_branch')))
4136
 
error_translators.register('NotStacked',
4137
 
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4138
 
 
4139
 
def _translate_PermissionDenied(err, find, get_path):
4140
 
    path = get_path()
4141
 
    if len(err.error_args) >= 2:
4142
 
        extra = err.error_args[1]
4143
 
    else:
4144
 
        extra = None
4145
 
    return errors.PermissionDenied(path, extra=extra)
4146
 
 
4147
 
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4148
 
error_translators.register('ReadError',
4149
 
    lambda err, find, get_path: errors.ReadError(get_path()))
4150
 
error_translators.register('NoSuchFile',
4151
 
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
4152
 
error_translators.register('TokenLockingNotSupported',
4153
 
    lambda err, find, get_path: errors.TokenLockingNotSupported(
4154
 
        find('repository')))
4155
 
error_translators.register('UnsuspendableWriteGroup',
4156
 
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4157
 
        repository=find('repository')))
4158
 
error_translators.register('UnresumableWriteGroup',
4159
 
    lambda err, find, get_path: errors.UnresumableWriteGroup(
4160
 
        repository=find('repository'), write_groups=err.error_args[0],
4161
 
        reason=err.error_args[1]))
4162
 
no_context_error_translators.register('IncompatibleRepositories',
4163
 
    lambda err: errors.IncompatibleRepositories(
4164
 
        err.error_args[0], err.error_args[1], err.error_args[2]))
4165
 
no_context_error_translators.register('LockContention',
4166
 
    lambda err: errors.LockContention('(remote lock)'))
4167
 
no_context_error_translators.register('LockFailed',
4168
 
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4169
 
no_context_error_translators.register('TipChangeRejected',
4170
 
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4171
 
no_context_error_translators.register('UnstackableBranchFormat',
4172
 
    lambda err: errors.UnstackableBranchFormat(*err.error_args))
4173
 
no_context_error_translators.register('UnstackableRepositoryFormat',
4174
 
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4175
 
no_context_error_translators.register('FileExists',
4176
 
    lambda err: errors.FileExists(err.error_args[0]))
4177
 
no_context_error_translators.register('DirectoryNotEmpty',
4178
 
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4179
 
 
4180
 
def _translate_short_readv_error(err):
4181
 
    args = err.error_args
4182
 
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4183
 
        int(args[3]))
4184
 
 
4185
 
no_context_error_translators.register('ShortReadvError',
4186
 
    _translate_short_readv_error)
4187
 
 
4188
 
def _translate_unicode_error(err):
4189
 
        encoding = str(err.error_args[0]) # encoding must always be a string
4190
 
        val = err.error_args[1]
4191
 
        start = int(err.error_args[2])
4192
 
        end = int(err.error_args[3])
4193
 
        reason = str(err.error_args[4]) # reason must always be a string
4194
 
        if val.startswith('u:'):
4195
 
            val = val[2:].decode('utf-8')
4196
 
        elif val.startswith('s:'):
4197
 
            val = val[2:].decode('base64')
4198
 
        if err.error_verb == 'UnicodeDecodeError':
4199
 
            raise UnicodeDecodeError(encoding, val, start, end, reason)
4200
 
        elif err.error_verb == 'UnicodeEncodeError':
4201
 
            raise UnicodeEncodeError(encoding, val, start, end, reason)
4202
 
 
4203
 
no_context_error_translators.register('UnicodeEncodeError',
4204
 
    _translate_unicode_error)
4205
 
no_context_error_translators.register('UnicodeDecodeError',
4206
 
    _translate_unicode_error)
4207
 
no_context_error_translators.register('ReadOnlyError',
4208
 
    lambda err: errors.TransportNotPossible('readonly transport'))
4209
 
no_context_error_translators.register('MemoryError',
4210
 
    lambda err: errors.BzrError("remote server out of memory\n"
4211
 
        "Retry non-remotely, or contact the server admin for details."))
4212
 
no_context_error_translators.register('RevisionNotPresent',
4213
 
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4214
 
 
4215
 
no_context_error_translators.register('BzrCheckError',
4216
 
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
4217