/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin von Gagern
  • Date: 2010-04-20 08:47:38 UTC
  • mfrom: (5167 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5195.
  • Revision ID: martin.vgagern@gmx.net-20100420084738-ygymnqmdllzrhpfn
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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., 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.
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
16
 
20
17
import bz2
21
18
 
22
19
from bzrlib import (
 
20
    bencode,
23
21
    branch,
 
22
    bzrdir,
 
23
    config,
24
24
    debug,
25
25
    errors,
26
26
    graph,
 
27
    lock,
27
28
    lockdir,
28
29
    repository,
 
30
    repository as _mod_repository,
29
31
    revision,
 
32
    revision as _mod_revision,
 
33
    static_tuple,
30
34
    symbol_versioning,
31
 
    urlutils,
32
35
)
33
36
from bzrlib.branch import BranchReferenceFormat
34
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
36
39
from bzrlib.errors import (
37
40
    NoSuchRevision,
38
41
    SmartProtocolError,
39
42
    )
40
43
from bzrlib.lockable_files import LockableFiles
41
 
from bzrlib.smart import client, vfs
 
44
from bzrlib.smart import client, vfs, repository as smart_repo
42
45
from bzrlib.revision import ensure_null, NULL_REVISION
43
46
from bzrlib.trace import mutter, note, warning
44
47
 
45
48
 
 
49
class _RpcHelper(object):
 
50
    """Mixin class that helps with issuing RPCs."""
 
51
 
 
52
    def _call(self, method, *args, **err_context):
 
53
        try:
 
54
            return self._client.call(method, *args)
 
55
        except errors.ErrorFromSmartServer, err:
 
56
            self._translate_error(err, **err_context)
 
57
 
 
58
    def _call_expecting_body(self, method, *args, **err_context):
 
59
        try:
 
60
            return self._client.call_expecting_body(method, *args)
 
61
        except errors.ErrorFromSmartServer, err:
 
62
            self._translate_error(err, **err_context)
 
63
 
 
64
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
 
65
        try:
 
66
            return self._client.call_with_body_bytes(method, args, body_bytes)
 
67
        except errors.ErrorFromSmartServer, err:
 
68
            self._translate_error(err, **err_context)
 
69
 
 
70
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
 
71
                                             **err_context):
 
72
        try:
 
73
            return self._client.call_with_body_bytes_expecting_body(
 
74
                method, args, body_bytes)
 
75
        except errors.ErrorFromSmartServer, err:
 
76
            self._translate_error(err, **err_context)
 
77
 
 
78
 
 
79
def response_tuple_to_repo_format(response):
 
80
    """Convert a response tuple describing a repository format to a format."""
 
81
    format = RemoteRepositoryFormat()
 
82
    format._rich_root_data = (response[0] == 'yes')
 
83
    format._supports_tree_reference = (response[1] == 'yes')
 
84
    format._supports_external_lookups = (response[2] == 'yes')
 
85
    format._network_name = response[3]
 
86
    return format
 
87
 
 
88
 
46
89
# Note: RemoteBzrDirFormat is in bzrdir.py
47
90
 
48
 
class RemoteBzrDir(BzrDir):
 
91
class RemoteBzrDir(BzrDir, _RpcHelper):
49
92
    """Control directory on a remote server, accessed via bzr:// or similar."""
50
93
 
51
 
    def __init__(self, transport, _client=None):
 
94
    def __init__(self, transport, format, _client=None, _force_probe=False):
52
95
        """Construct a RemoteBzrDir.
53
96
 
54
97
        :param _client: Private parameter for testing. Disables probing and the
55
98
            use of a real bzrdir.
56
99
        """
57
 
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
100
        BzrDir.__init__(self, transport, format)
58
101
        # this object holds a delegated bzrdir that uses file-level operations
59
102
        # to talk to the other side
60
103
        self._real_bzrdir = None
 
104
        self._has_working_tree = None
 
105
        # 1-shot cache for the call pattern 'create_branch; open_branch' - see
 
106
        # create_branch for details.
 
107
        self._next_open_branch_result = None
61
108
 
62
109
        if _client is None:
63
110
            medium = transport.get_smart_medium()
64
111
            self._client = client._SmartClient(medium)
65
112
        else:
66
113
            self._client = _client
67
 
            return
68
 
 
 
114
            if not _force_probe:
 
115
                return
 
116
 
 
117
        self._probe_bzrdir()
 
118
 
 
119
    def __repr__(self):
 
120
        return '%s(%r)' % (self.__class__.__name__, self._client)
 
121
 
 
122
    def _probe_bzrdir(self):
 
123
        medium = self._client._medium
69
124
        path = self._path_for_remote_call(self._client)
70
 
        response = self._client.call('BzrDir.open', path)
 
125
        if medium._is_remote_before((2, 1)):
 
126
            self._rpc_open(path)
 
127
            return
 
128
        try:
 
129
            self._rpc_open_2_1(path)
 
130
            return
 
131
        except errors.UnknownSmartMethod:
 
132
            medium._remember_remote_is_before((2, 1))
 
133
            self._rpc_open(path)
 
134
 
 
135
    def _rpc_open_2_1(self, path):
 
136
        response = self._call('BzrDir.open_2.1', path)
 
137
        if response == ('no',):
 
138
            raise errors.NotBranchError(path=self.root_transport.base)
 
139
        elif response[0] == 'yes':
 
140
            if response[1] == 'yes':
 
141
                self._has_working_tree = True
 
142
            elif response[1] == 'no':
 
143
                self._has_working_tree = False
 
144
            else:
 
145
                raise errors.UnexpectedSmartServerResponse(response)
 
146
        else:
 
147
            raise errors.UnexpectedSmartServerResponse(response)
 
148
 
 
149
    def _rpc_open(self, path):
 
150
        response = self._call('BzrDir.open', path)
71
151
        if response not in [('yes',), ('no',)]:
72
152
            raise errors.UnexpectedSmartServerResponse(response)
73
153
        if response == ('no',):
74
 
            raise errors.NotBranchError(path=transport.base)
 
154
            raise errors.NotBranchError(path=self.root_transport.base)
75
155
 
76
156
    def _ensure_real(self):
77
157
        """Ensure that there is a _real_bzrdir set.
79
159
        Used before calls to self._real_bzrdir.
80
160
        """
81
161
        if not self._real_bzrdir:
 
162
            if 'hpssvfs' in debug.debug_flags:
 
163
                import traceback
 
164
                warning('VFS BzrDir access triggered\n%s',
 
165
                    ''.join(traceback.format_stack()))
82
166
            self._real_bzrdir = BzrDir.open_from_transport(
83
167
                self.root_transport, _server_formats=False)
84
 
 
85
 
    def cloning_metadir(self, stacked=False):
86
 
        self._ensure_real()
87
 
        return self._real_bzrdir.cloning_metadir(stacked)
 
168
            self._format._network_name = \
 
169
                self._real_bzrdir._format.network_name()
88
170
 
89
171
    def _translate_error(self, err, **context):
90
172
        _translate_error(err, bzrdir=self, **context)
91
 
        
 
173
 
 
174
    def break_lock(self):
 
175
        # Prevent aliasing problems in the next_open_branch_result cache.
 
176
        # See create_branch for rationale.
 
177
        self._next_open_branch_result = None
 
178
        return BzrDir.break_lock(self)
 
179
 
 
180
    def _vfs_cloning_metadir(self, require_stacking=False):
 
181
        self._ensure_real()
 
182
        return self._real_bzrdir.cloning_metadir(
 
183
            require_stacking=require_stacking)
 
184
 
 
185
    def cloning_metadir(self, require_stacking=False):
 
186
        medium = self._client._medium
 
187
        if medium._is_remote_before((1, 13)):
 
188
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
189
        verb = 'BzrDir.cloning_metadir'
 
190
        if require_stacking:
 
191
            stacking = 'True'
 
192
        else:
 
193
            stacking = 'False'
 
194
        path = self._path_for_remote_call(self._client)
 
195
        try:
 
196
            response = self._call(verb, path, stacking)
 
197
        except errors.UnknownSmartMethod:
 
198
            medium._remember_remote_is_before((1, 13))
 
199
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
 
200
        except errors.UnknownErrorFromSmartServer, err:
 
201
            if err.error_tuple != ('BranchReference',):
 
202
                raise
 
203
            # We need to resolve the branch reference to determine the
 
204
            # cloning_metadir.  This causes unnecessary RPCs to open the
 
205
            # referenced branch (and bzrdir, etc) but only when the caller
 
206
            # didn't already resolve the branch reference.
 
207
            referenced_branch = self.open_branch()
 
208
            return referenced_branch.bzrdir.cloning_metadir()
 
209
        if len(response) != 3:
 
210
            raise errors.UnexpectedSmartServerResponse(response)
 
211
        control_name, repo_name, branch_info = response
 
212
        if len(branch_info) != 2:
 
213
            raise errors.UnexpectedSmartServerResponse(response)
 
214
        branch_ref, branch_name = branch_info
 
215
        format = bzrdir.network_format_registry.get(control_name)
 
216
        if repo_name:
 
217
            format.repository_format = repository.network_format_registry.get(
 
218
                repo_name)
 
219
        if branch_ref == 'ref':
 
220
            # XXX: we need possible_transports here to avoid reopening the
 
221
            # connection to the referenced location
 
222
            ref_bzrdir = BzrDir.open(branch_name)
 
223
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
 
224
            format.set_branch_format(branch_format)
 
225
        elif branch_ref == 'branch':
 
226
            if branch_name:
 
227
                format.set_branch_format(
 
228
                    branch.network_format_registry.get(branch_name))
 
229
        else:
 
230
            raise errors.UnexpectedSmartServerResponse(response)
 
231
        return format
 
232
 
92
233
    def create_repository(self, shared=False):
93
 
        self._ensure_real()
94
 
        self._real_bzrdir.create_repository(shared=shared)
95
 
        return self.open_repository()
 
234
        # as per meta1 formats - just delegate to the format object which may
 
235
        # be parameterised.
 
236
        result = self._format.repository_format.initialize(self, shared)
 
237
        if not isinstance(result, RemoteRepository):
 
238
            return self.open_repository()
 
239
        else:
 
240
            return result
96
241
 
97
242
    def destroy_repository(self):
98
243
        """See BzrDir.destroy_repository"""
99
244
        self._ensure_real()
100
245
        self._real_bzrdir.destroy_repository()
101
246
 
102
 
    def create_branch(self):
103
 
        self._ensure_real()
104
 
        real_branch = self._real_bzrdir.create_branch()
105
 
        return RemoteBranch(self, self.find_repository(), real_branch)
 
247
    def create_branch(self, name=None):
 
248
        # as per meta1 formats - just delegate to the format object which may
 
249
        # be parameterised.
 
250
        real_branch = self._format.get_branch_format().initialize(self,
 
251
            name=name)
 
252
        if not isinstance(real_branch, RemoteBranch):
 
253
            result = RemoteBranch(self, self.find_repository(), real_branch,
 
254
                                  name=name)
 
255
        else:
 
256
            result = real_branch
 
257
        # BzrDir.clone_on_transport() uses the result of create_branch but does
 
258
        # not return it to its callers; we save approximately 8% of our round
 
259
        # trips by handing the branch we created back to the first caller to
 
260
        # open_branch rather than probing anew. Long term we need a API in
 
261
        # bzrdir that doesn't discard result objects (like result_branch).
 
262
        # RBC 20090225
 
263
        self._next_open_branch_result = result
 
264
        return result
106
265
 
107
 
    def destroy_branch(self):
 
266
    def destroy_branch(self, name=None):
108
267
        """See BzrDir.destroy_branch"""
109
268
        self._ensure_real()
110
 
        self._real_bzrdir.destroy_branch()
 
269
        self._real_bzrdir.destroy_branch(name=name)
 
270
        self._next_open_branch_result = None
111
271
 
112
272
    def create_workingtree(self, revision_id=None, from_branch=None):
113
273
        raise errors.NotLocalUrl(self.transport.base)
122
282
 
123
283
    def get_branch_reference(self):
124
284
        """See BzrDir.get_branch_reference()."""
 
285
        response = self._get_branch_reference()
 
286
        if response[0] == 'ref':
 
287
            return response[1]
 
288
        else:
 
289
            return None
 
290
 
 
291
    def _get_branch_reference(self):
125
292
        path = self._path_for_remote_call(self._client)
126
 
        try:
127
 
            response = self._client.call('BzrDir.open_branch', path)
128
 
        except errors.ErrorFromSmartServer, err:
129
 
            self._translate_error(err)
130
 
        if response[0] == 'ok':
131
 
            if response[1] == '':
132
 
                # branch at this location.
133
 
                return None
134
 
            else:
135
 
                # a branch reference, use the existing BranchReference logic.
136
 
                return response[1]
137
 
        else:
 
293
        medium = self._client._medium
 
294
        candidate_calls = [
 
295
            ('BzrDir.open_branchV3', (2, 1)),
 
296
            ('BzrDir.open_branchV2', (1, 13)),
 
297
            ('BzrDir.open_branch', None),
 
298
            ]
 
299
        for verb, required_version in candidate_calls:
 
300
            if required_version and medium._is_remote_before(required_version):
 
301
                continue
 
302
            try:
 
303
                response = self._call(verb, path)
 
304
            except errors.UnknownSmartMethod:
 
305
                if required_version is None:
 
306
                    raise
 
307
                medium._remember_remote_is_before(required_version)
 
308
            else:
 
309
                break
 
310
        if verb == 'BzrDir.open_branch':
 
311
            if response[0] != 'ok':
 
312
                raise errors.UnexpectedSmartServerResponse(response)
 
313
            if response[1] != '':
 
314
                return ('ref', response[1])
 
315
            else:
 
316
                return ('branch', '')
 
317
        if response[0] not in ('ref', 'branch'):
138
318
            raise errors.UnexpectedSmartServerResponse(response)
 
319
        return response
139
320
 
140
321
    def _get_tree_branch(self):
141
322
        """See BzrDir._get_tree_branch()."""
142
323
        return None, self.open_branch()
143
324
 
144
 
    def open_branch(self, _unsupported=False):
145
 
        if _unsupported:
 
325
    def open_branch(self, name=None, unsupported=False,
 
326
                    ignore_fallbacks=False):
 
327
        if unsupported:
146
328
            raise NotImplementedError('unsupported flag support not implemented yet.')
147
 
        reference_url = self.get_branch_reference()
148
 
        if reference_url is None:
149
 
            # branch at this location.
150
 
            return RemoteBranch(self, self.find_repository())
151
 
        else:
 
329
        if self._next_open_branch_result is not None:
 
330
            # See create_branch for details.
 
331
            result = self._next_open_branch_result
 
332
            self._next_open_branch_result = None
 
333
            return result
 
334
        response = self._get_branch_reference()
 
335
        if response[0] == 'ref':
152
336
            # a branch reference, use the existing BranchReference logic.
153
337
            format = BranchReferenceFormat()
154
 
            return format.open(self, _found=True, location=reference_url)
155
 
                
 
338
            return format.open(self, name=name, _found=True,
 
339
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
340
        branch_format_name = response[1]
 
341
        if not branch_format_name:
 
342
            branch_format_name = None
 
343
        format = RemoteBranchFormat(network_name=branch_format_name)
 
344
        return RemoteBranch(self, self.find_repository(), format=format,
 
345
            setup_stacking=not ignore_fallbacks, name=name)
 
346
 
 
347
    def _open_repo_v1(self, path):
 
348
        verb = 'BzrDir.find_repository'
 
349
        response = self._call(verb, path)
 
350
        if response[0] != 'ok':
 
351
            raise errors.UnexpectedSmartServerResponse(response)
 
352
        # servers that only support the v1 method don't support external
 
353
        # references either.
 
354
        self._ensure_real()
 
355
        repo = self._real_bzrdir.open_repository()
 
356
        response = response + ('no', repo._format.network_name())
 
357
        return response, repo
 
358
 
 
359
    def _open_repo_v2(self, path):
 
360
        verb = 'BzrDir.find_repositoryV2'
 
361
        response = self._call(verb, path)
 
362
        if response[0] != 'ok':
 
363
            raise errors.UnexpectedSmartServerResponse(response)
 
364
        self._ensure_real()
 
365
        repo = self._real_bzrdir.open_repository()
 
366
        response = response + (repo._format.network_name(),)
 
367
        return response, repo
 
368
 
 
369
    def _open_repo_v3(self, path):
 
370
        verb = 'BzrDir.find_repositoryV3'
 
371
        medium = self._client._medium
 
372
        if medium._is_remote_before((1, 13)):
 
373
            raise errors.UnknownSmartMethod(verb)
 
374
        try:
 
375
            response = self._call(verb, path)
 
376
        except errors.UnknownSmartMethod:
 
377
            medium._remember_remote_is_before((1, 13))
 
378
            raise
 
379
        if response[0] != 'ok':
 
380
            raise errors.UnexpectedSmartServerResponse(response)
 
381
        return response, None
 
382
 
156
383
    def open_repository(self):
157
384
        path = self._path_for_remote_call(self._client)
158
 
        verb = 'BzrDir.find_repositoryV2'
159
 
        try:
 
385
        response = None
 
386
        for probe in [self._open_repo_v3, self._open_repo_v2,
 
387
            self._open_repo_v1]:
160
388
            try:
161
 
                response = self._client.call(verb, path)
 
389
                response, real_repo = probe(path)
 
390
                break
162
391
            except errors.UnknownSmartMethod:
163
 
                verb = 'BzrDir.find_repository'
164
 
                response = self._client.call(verb, path)
165
 
        except errors.ErrorFromSmartServer, err:
166
 
            self._translate_error(err)
 
392
                pass
 
393
        if response is None:
 
394
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
167
395
        if response[0] != 'ok':
168
396
            raise errors.UnexpectedSmartServerResponse(response)
169
 
        if verb == 'BzrDir.find_repository':
170
 
            # servers that don't support the V2 method don't support external
171
 
            # references either.
172
 
            response = response + ('no', )
173
 
        if not (len(response) == 5):
 
397
        if len(response) != 6:
174
398
            raise SmartProtocolError('incorrect response length %s' % (response,))
175
399
        if response[1] == '':
176
 
            format = RemoteRepositoryFormat()
177
 
            format.rich_root_data = (response[2] == 'yes')
178
 
            format.supports_tree_reference = (response[3] == 'yes')
179
 
            # No wire format to check this yet.
180
 
            format.supports_external_lookups = (response[4] == 'yes')
 
400
            # repo is at this dir.
 
401
            format = response_tuple_to_repo_format(response[2:])
181
402
            # Used to support creating a real format instance when needed.
182
403
            format._creating_bzrdir = self
183
 
            return RemoteRepository(self, format)
 
404
            remote_repo = RemoteRepository(self, format)
 
405
            format._creating_repo = remote_repo
 
406
            if real_repo is not None:
 
407
                remote_repo._set_real_repository(real_repo)
 
408
            return remote_repo
184
409
        else:
185
410
            raise errors.NoRepositoryPresent(self)
186
411
 
 
412
    def has_workingtree(self):
 
413
        if self._has_working_tree is None:
 
414
            self._ensure_real()
 
415
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
416
        return self._has_working_tree
 
417
 
187
418
    def open_workingtree(self, recommend_upgrade=True):
188
 
        self._ensure_real()
189
 
        if self._real_bzrdir.has_workingtree():
 
419
        if self.has_workingtree():
190
420
            raise errors.NotLocalUrl(self.root_transport)
191
421
        else:
192
422
            raise errors.NoWorkingTree(self.root_transport.base)
195
425
        """Return the path to be used for this bzrdir in a remote call."""
196
426
        return client.remote_path_from_transport(self.root_transport)
197
427
 
198
 
    def get_branch_transport(self, branch_format):
 
428
    def get_branch_transport(self, branch_format, name=None):
199
429
        self._ensure_real()
200
 
        return self._real_bzrdir.get_branch_transport(branch_format)
 
430
        return self._real_bzrdir.get_branch_transport(branch_format, name=name)
201
431
 
202
432
    def get_repository_transport(self, repository_format):
203
433
        self._ensure_real()
213
443
 
214
444
    def needs_format_conversion(self, format=None):
215
445
        """Upgrading of remote bzrdirs is not supported yet."""
 
446
        if format is None:
 
447
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
448
                % 'needs_format_conversion(format=None)')
216
449
        return False
217
450
 
218
451
    def clone(self, url, revision_id=None, force_new_repo=False,
221
454
        return self._real_bzrdir.clone(url, revision_id=revision_id,
222
455
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
223
456
 
224
 
    def get_config(self):
225
 
        self._ensure_real()
226
 
        return self._real_bzrdir.get_config()
 
457
    def _get_config(self):
 
458
        return RemoteBzrDirConfig(self)
227
459
 
228
460
 
229
461
class RemoteRepositoryFormat(repository.RepositoryFormat):
237
469
    the attributes rich_root_data and supports_tree_reference are set
238
470
    on a per instance basis, and are not set (and should not be) at
239
471
    the class level.
 
472
 
 
473
    :ivar _custom_format: If set, a specific concrete repository format that
 
474
        will be used when initializing a repository with this
 
475
        RemoteRepositoryFormat.
 
476
    :ivar _creating_repo: If set, the repository object that this
 
477
        RemoteRepositoryFormat was created for: it can be called into
 
478
        to obtain data like the network name.
240
479
    """
241
480
 
242
481
    _matchingbzrdir = RemoteBzrDirFormat()
243
482
 
244
 
    def initialize(self, a_bzrdir, shared=False):
245
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
483
    def __init__(self):
 
484
        repository.RepositoryFormat.__init__(self)
 
485
        self._custom_format = None
 
486
        self._network_name = None
 
487
        self._creating_bzrdir = None
 
488
        self._supports_chks = None
 
489
        self._supports_external_lookups = None
 
490
        self._supports_tree_reference = None
 
491
        self._rich_root_data = None
 
492
 
 
493
    def __repr__(self):
 
494
        return "%s(_network_name=%r)" % (self.__class__.__name__,
 
495
            self._network_name)
 
496
 
 
497
    @property
 
498
    def fast_deltas(self):
 
499
        self._ensure_real()
 
500
        return self._custom_format.fast_deltas
 
501
 
 
502
    @property
 
503
    def rich_root_data(self):
 
504
        if self._rich_root_data is None:
 
505
            self._ensure_real()
 
506
            self._rich_root_data = self._custom_format.rich_root_data
 
507
        return self._rich_root_data
 
508
 
 
509
    @property
 
510
    def supports_chks(self):
 
511
        if self._supports_chks is None:
 
512
            self._ensure_real()
 
513
            self._supports_chks = self._custom_format.supports_chks
 
514
        return self._supports_chks
 
515
 
 
516
    @property
 
517
    def supports_external_lookups(self):
 
518
        if self._supports_external_lookups is None:
 
519
            self._ensure_real()
 
520
            self._supports_external_lookups = \
 
521
                self._custom_format.supports_external_lookups
 
522
        return self._supports_external_lookups
 
523
 
 
524
    @property
 
525
    def supports_tree_reference(self):
 
526
        if self._supports_tree_reference is None:
 
527
            self._ensure_real()
 
528
            self._supports_tree_reference = \
 
529
                self._custom_format.supports_tree_reference
 
530
        return self._supports_tree_reference
 
531
 
 
532
    def _vfs_initialize(self, a_bzrdir, shared):
 
533
        """Helper for common code in initialize."""
 
534
        if self._custom_format:
 
535
            # Custom format requested
 
536
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
 
537
        elif self._creating_bzrdir is not None:
 
538
            # Use the format that the repository we were created to back
 
539
            # has.
246
540
            prior_repo = self._creating_bzrdir.open_repository()
247
541
            prior_repo._ensure_real()
248
 
            return prior_repo._real_repository._format.initialize(
 
542
            result = prior_repo._real_repository._format.initialize(
249
543
                a_bzrdir, shared=shared)
250
 
        return a_bzrdir.create_repository(shared=shared)
251
 
    
 
544
        else:
 
545
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
 
546
            # support remote initialization.
 
547
            # We delegate to a real object at this point (as RemoteBzrDir
 
548
            # delegate to the repository format which would lead to infinite
 
549
            # recursion if we just called a_bzrdir.create_repository.
 
550
            a_bzrdir._ensure_real()
 
551
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
 
552
        if not isinstance(result, RemoteRepository):
 
553
            return self.open(a_bzrdir)
 
554
        else:
 
555
            return result
 
556
 
 
557
    def initialize(self, a_bzrdir, shared=False):
 
558
        # Being asked to create on a non RemoteBzrDir:
 
559
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
560
            return self._vfs_initialize(a_bzrdir, shared)
 
561
        medium = a_bzrdir._client._medium
 
562
        if medium._is_remote_before((1, 13)):
 
563
            return self._vfs_initialize(a_bzrdir, shared)
 
564
        # Creating on a remote bzr dir.
 
565
        # 1) get the network name to use.
 
566
        if self._custom_format:
 
567
            network_name = self._custom_format.network_name()
 
568
        elif self._network_name:
 
569
            network_name = self._network_name
 
570
        else:
 
571
            # Select the current bzrlib default and ask for that.
 
572
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
573
            reference_format = reference_bzrdir_format.repository_format
 
574
            network_name = reference_format.network_name()
 
575
        # 2) try direct creation via RPC
 
576
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
577
        verb = 'BzrDir.create_repository'
 
578
        if shared:
 
579
            shared_str = 'True'
 
580
        else:
 
581
            shared_str = 'False'
 
582
        try:
 
583
            response = a_bzrdir._call(verb, path, network_name, shared_str)
 
584
        except errors.UnknownSmartMethod:
 
585
            # Fallback - use vfs methods
 
586
            medium._remember_remote_is_before((1, 13))
 
587
            return self._vfs_initialize(a_bzrdir, shared)
 
588
        else:
 
589
            # Turn the response into a RemoteRepository object.
 
590
            format = response_tuple_to_repo_format(response[1:])
 
591
            # Used to support creating a real format instance when needed.
 
592
            format._creating_bzrdir = a_bzrdir
 
593
            remote_repo = RemoteRepository(a_bzrdir, format)
 
594
            format._creating_repo = remote_repo
 
595
            return remote_repo
 
596
 
252
597
    def open(self, a_bzrdir):
253
598
        if not isinstance(a_bzrdir, RemoteBzrDir):
254
599
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
255
600
        return a_bzrdir.open_repository()
256
601
 
 
602
    def _ensure_real(self):
 
603
        if self._custom_format is None:
 
604
            self._custom_format = repository.network_format_registry.get(
 
605
                self._network_name)
 
606
 
 
607
    @property
 
608
    def _fetch_order(self):
 
609
        self._ensure_real()
 
610
        return self._custom_format._fetch_order
 
611
 
 
612
    @property
 
613
    def _fetch_uses_deltas(self):
 
614
        self._ensure_real()
 
615
        return self._custom_format._fetch_uses_deltas
 
616
 
 
617
    @property
 
618
    def _fetch_reconcile(self):
 
619
        self._ensure_real()
 
620
        return self._custom_format._fetch_reconcile
 
621
 
257
622
    def get_format_description(self):
258
 
        return 'bzr remote repository'
 
623
        self._ensure_real()
 
624
        return 'Remote: ' + self._custom_format.get_format_description()
259
625
 
260
626
    def __eq__(self, other):
261
 
        return self.__class__ == other.__class__
262
 
 
263
 
    def check_conversion_target(self, target_format):
264
 
        if self.rich_root_data and not target_format.rich_root_data:
265
 
            raise errors.BadConversionTarget(
266
 
                'Does not support rich root data.', target_format)
267
 
        if (self.supports_tree_reference and
268
 
            not getattr(target_format, 'supports_tree_reference', False)):
269
 
            raise errors.BadConversionTarget(
270
 
                'Does not support nested trees', target_format)
271
 
 
272
 
 
273
 
class RemoteRepository(object):
 
627
        return self.__class__ is other.__class__
 
628
 
 
629
    def network_name(self):
 
630
        if self._network_name:
 
631
            return self._network_name
 
632
        self._creating_repo._ensure_real()
 
633
        return self._creating_repo._real_repository._format.network_name()
 
634
 
 
635
    @property
 
636
    def pack_compresses(self):
 
637
        self._ensure_real()
 
638
        return self._custom_format.pack_compresses
 
639
 
 
640
    @property
 
641
    def _serializer(self):
 
642
        self._ensure_real()
 
643
        return self._custom_format._serializer
 
644
 
 
645
 
 
646
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
274
647
    """Repository accessed over rpc.
275
648
 
276
649
    For the moment most operations are performed using local transport-backed
279
652
 
280
653
    def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
281
654
        """Create a RemoteRepository instance.
282
 
        
 
655
 
283
656
        :param remote_bzrdir: The bzrdir hosting this repository.
284
657
        :param format: The RemoteFormat object to use.
285
658
        :param real_repository: If not None, a local implementation of the
302
675
        self._lock_token = None
303
676
        self._lock_count = 0
304
677
        self._leave_lock = False
305
 
        # A cache of looked up revision parent data; reset at unlock time.
306
 
        self._parents_map = None
307
 
        if 'hpss' in debug.debug_flags:
308
 
            self._requested_parents = None
 
678
        # Cache of revision parents; misses are cached during read locks, and
 
679
        # write locks when no _real_repository has been set.
 
680
        self._unstacked_provider = graph.CachingParentsProvider(
 
681
            get_parent_map=self._get_parent_map_rpc)
 
682
        self._unstacked_provider.disable_cache()
309
683
        # For tests:
310
684
        # These depend on the actual remote format, so force them off for
311
685
        # maximum compatibility. XXX: In future these should depend on the
323
697
 
324
698
    __repr__ = __str__
325
699
 
326
 
    def abort_write_group(self):
 
700
    def abort_write_group(self, suppress_errors=False):
327
701
        """Complete a write group on the decorated repository.
328
 
        
329
 
        Smart methods peform operations in a single step so this api
 
702
 
 
703
        Smart methods perform operations in a single step so this API
330
704
        is not really applicable except as a compatibility thunk
331
705
        for older plugins that don't use e.g. the CommitBuilder
332
706
        facility.
333
 
        """
334
 
        self._ensure_real()
335
 
        return self._real_repository.abort_write_group()
 
707
 
 
708
        :param suppress_errors: see Repository.abort_write_group.
 
709
        """
 
710
        self._ensure_real()
 
711
        return self._real_repository.abort_write_group(
 
712
            suppress_errors=suppress_errors)
 
713
 
 
714
    @property
 
715
    def chk_bytes(self):
 
716
        """Decorate the real repository for now.
 
717
 
 
718
        In the long term a full blown network facility is needed to avoid
 
719
        creating a real repository object locally.
 
720
        """
 
721
        self._ensure_real()
 
722
        return self._real_repository.chk_bytes
336
723
 
337
724
    def commit_write_group(self):
338
725
        """Complete a write group on the decorated repository.
339
 
        
340
 
        Smart methods peform operations in a single step so this api
 
726
 
 
727
        Smart methods perform operations in a single step so this API
341
728
        is not really applicable except as a compatibility thunk
342
729
        for older plugins that don't use e.g. the CommitBuilder
343
730
        facility.
345
732
        self._ensure_real()
346
733
        return self._real_repository.commit_write_group()
347
734
 
 
735
    def resume_write_group(self, tokens):
 
736
        self._ensure_real()
 
737
        return self._real_repository.resume_write_group(tokens)
 
738
 
 
739
    def suspend_write_group(self):
 
740
        self._ensure_real()
 
741
        return self._real_repository.suspend_write_group()
 
742
 
 
743
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
 
744
        self._ensure_real()
 
745
        return self._real_repository.get_missing_parent_inventories(
 
746
            check_for_missing_texts=check_for_missing_texts)
 
747
 
 
748
    def _get_rev_id_for_revno_vfs(self, revno, known_pair):
 
749
        self._ensure_real()
 
750
        return self._real_repository.get_rev_id_for_revno(
 
751
            revno, known_pair)
 
752
 
 
753
    def get_rev_id_for_revno(self, revno, known_pair):
 
754
        """See Repository.get_rev_id_for_revno."""
 
755
        path = self.bzrdir._path_for_remote_call(self._client)
 
756
        try:
 
757
            if self._client._medium._is_remote_before((1, 17)):
 
758
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
759
            response = self._call(
 
760
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
761
        except errors.UnknownSmartMethod:
 
762
            self._client._medium._remember_remote_is_before((1, 17))
 
763
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
 
764
        if response[0] == 'ok':
 
765
            return True, response[1]
 
766
        elif response[0] == 'history-incomplete':
 
767
            known_pair = response[1:3]
 
768
            for fallback in self._fallback_repositories:
 
769
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
 
770
                if found:
 
771
                    return True, result
 
772
                else:
 
773
                    known_pair = result
 
774
            # Not found in any fallbacks
 
775
            return False, known_pair
 
776
        else:
 
777
            raise errors.UnexpectedSmartServerResponse(response)
 
778
 
348
779
    def _ensure_real(self):
349
780
        """Ensure that there is a _real_repository set.
350
781
 
351
782
        Used before calls to self._real_repository.
 
783
 
 
784
        Note that _ensure_real causes many roundtrips to the server which are
 
785
        not desirable, and prevents the use of smart one-roundtrip RPC's to
 
786
        perform complex operations (such as accessing parent data, streaming
 
787
        revisions etc). Adding calls to _ensure_real should only be done when
 
788
        bringing up new functionality, adding fallbacks for smart methods that
 
789
        require a fallback path, and never to replace an existing smart method
 
790
        invocation. If in doubt chat to the bzr network team.
352
791
        """
353
792
        if self._real_repository is None:
 
793
            if 'hpssvfs' in debug.debug_flags:
 
794
                import traceback
 
795
                warning('VFS Repository access triggered\n%s',
 
796
                    ''.join(traceback.format_stack()))
 
797
            self._unstacked_provider.missing_keys.clear()
354
798
            self.bzrdir._ensure_real()
355
799
            self._set_real_repository(
356
800
                self.bzrdir._real_bzrdir.open_repository())
383
827
        self._ensure_real()
384
828
        return self._real_repository._generate_text_key_index()
385
829
 
386
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
387
 
    def get_revision_graph(self, revision_id=None):
388
 
        """See Repository.get_revision_graph()."""
389
 
        return self._get_revision_graph(revision_id)
390
 
 
391
830
    def _get_revision_graph(self, revision_id):
392
831
        """Private method for using with old (< 1.2) servers to fallback."""
393
832
        if revision_id is None:
396
835
            return {}
397
836
 
398
837
        path = self.bzrdir._path_for_remote_call(self._client)
399
 
        try:
400
 
            response = self._client.call_expecting_body(
401
 
                'Repository.get_revision_graph', path, revision_id)
402
 
        except errors.ErrorFromSmartServer, err:
403
 
            self._translate_error(err)
 
838
        response = self._call_expecting_body(
 
839
            'Repository.get_revision_graph', path, revision_id)
404
840
        response_tuple, response_handler = response
405
841
        if response_tuple[0] != 'ok':
406
842
            raise errors.UnexpectedSmartServerResponse(response_tuple)
413
849
        for line in lines:
414
850
            d = tuple(line.split())
415
851
            revision_graph[d[0]] = d[1:]
416
 
            
 
852
 
417
853
        return revision_graph
418
854
 
 
855
    def _get_sink(self):
 
856
        """See Repository._get_sink()."""
 
857
        return RemoteStreamSink(self)
 
858
 
 
859
    def _get_source(self, to_format):
 
860
        """Return a source for streaming from this repository."""
 
861
        return RemoteStreamSource(self, to_format)
 
862
 
 
863
    @needs_read_lock
419
864
    def has_revision(self, revision_id):
420
 
        """See Repository.has_revision()."""
421
 
        if revision_id == NULL_REVISION:
422
 
            # The null revision is always present.
423
 
            return True
424
 
        path = self.bzrdir._path_for_remote_call(self._client)
425
 
        response = self._client.call(
426
 
            'Repository.has_revision', path, revision_id)
427
 
        if response[0] not in ('yes', 'no'):
428
 
            raise errors.UnexpectedSmartServerResponse(response)
429
 
        if response[0] == 'yes':
430
 
            return True
431
 
        for fallback_repo in self._fallback_repositories:
432
 
            if fallback_repo.has_revision(revision_id):
433
 
                return True
434
 
        return False
 
865
        """True if this repository has a copy of the revision."""
 
866
        # Copy of bzrlib.repository.Repository.has_revision
 
867
        return revision_id in self.has_revisions((revision_id,))
435
868
 
 
869
    @needs_read_lock
436
870
    def has_revisions(self, revision_ids):
437
 
        """See Repository.has_revisions()."""
438
 
        # FIXME: This does many roundtrips, particularly when there are
439
 
        # fallback repositories.  -- mbp 20080905
440
 
        result = set()
441
 
        for revision_id in revision_ids:
442
 
            if self.has_revision(revision_id):
443
 
                result.add(revision_id)
 
871
        """Probe to find out the presence of multiple revisions.
 
872
 
 
873
        :param revision_ids: An iterable of revision_ids.
 
874
        :return: A set of the revision_ids that were present.
 
875
        """
 
876
        # Copy of bzrlib.repository.Repository.has_revisions
 
877
        parent_map = self.get_parent_map(revision_ids)
 
878
        result = set(parent_map)
 
879
        if _mod_revision.NULL_REVISION in revision_ids:
 
880
            result.add(_mod_revision.NULL_REVISION)
444
881
        return result
445
882
 
 
883
    def _has_same_fallbacks(self, other_repo):
 
884
        """Returns true if the repositories have the same fallbacks."""
 
885
        # XXX: copied from Repository; it should be unified into a base class
 
886
        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
 
887
        my_fb = self._fallback_repositories
 
888
        other_fb = other_repo._fallback_repositories
 
889
        if len(my_fb) != len(other_fb):
 
890
            return False
 
891
        for f, g in zip(my_fb, other_fb):
 
892
            if not f.has_same_location(g):
 
893
                return False
 
894
        return True
 
895
 
446
896
    def has_same_location(self, other):
447
 
        return (self.__class__ == other.__class__ and
 
897
        # TODO: Move to RepositoryBase and unify with the regular Repository
 
898
        # one; unfortunately the tests rely on slightly different behaviour at
 
899
        # present -- mbp 20090710
 
900
        return (self.__class__ is other.__class__ and
448
901
                self.bzrdir.transport.base == other.bzrdir.transport.base)
449
 
        
 
902
 
450
903
    def get_graph(self, other_repository=None):
451
904
        """Return the graph for this repository format"""
452
 
        parents_provider = self
453
 
        if (other_repository is not None and
454
 
            other_repository.bzrdir.transport.base !=
455
 
            self.bzrdir.transport.base):
456
 
            parents_provider = graph._StackedParentsProvider(
457
 
                [parents_provider, other_repository._make_parents_provider()])
 
905
        parents_provider = self._make_parents_provider(other_repository)
458
906
        return graph.Graph(parents_provider)
459
907
 
 
908
    @needs_read_lock
 
909
    def get_known_graph_ancestry(self, revision_ids):
 
910
        """Return the known graph for a set of revision ids and their ancestors.
 
911
        """
 
912
        st = static_tuple.StaticTuple
 
913
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
914
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
915
        return graph.GraphThunkIdsToKeys(known_graph)
 
916
 
460
917
    def gather_stats(self, revid=None, committers=None):
461
918
        """See Repository.gather_stats()."""
462
919
        path = self.bzrdir._path_for_remote_call(self._client)
469
926
            fmt_committers = 'no'
470
927
        else:
471
928
            fmt_committers = 'yes'
472
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
929
        response_tuple, response_handler = self._call_expecting_body(
473
930
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
474
931
        if response_tuple[0] != 'ok':
475
932
            raise errors.UnexpectedSmartServerResponse(response_tuple)
514
971
    def is_shared(self):
515
972
        """See Repository.is_shared()."""
516
973
        path = self.bzrdir._path_for_remote_call(self._client)
517
 
        response = self._client.call('Repository.is_shared', path)
 
974
        response = self._call('Repository.is_shared', path)
518
975
        if response[0] not in ('yes', 'no'):
519
976
            raise SmartProtocolError('unexpected response code %s' % (response,))
520
977
        return response[0] == 'yes'
522
979
    def is_write_locked(self):
523
980
        return self._lock_mode == 'w'
524
981
 
 
982
    def _warn_if_deprecated(self, branch=None):
 
983
        # If we have a real repository, the check will be done there, if we
 
984
        # don't the check will be done remotely.
 
985
        pass
 
986
 
525
987
    def lock_read(self):
526
988
        # wrong eventually - want a local lock cache context
527
989
        if not self._lock_mode:
 
990
            self._note_lock('r')
528
991
            self._lock_mode = 'r'
529
992
            self._lock_count = 1
530
 
            self._parents_map = {}
531
 
            if 'hpss' in debug.debug_flags:
532
 
                self._requested_parents = set()
 
993
            self._unstacked_provider.enable_cache(cache_misses=True)
533
994
            if self._real_repository is not None:
534
995
                self._real_repository.lock_read()
 
996
            for repo in self._fallback_repositories:
 
997
                repo.lock_read()
535
998
        else:
536
999
            self._lock_count += 1
537
1000
 
539
1002
        path = self.bzrdir._path_for_remote_call(self._client)
540
1003
        if token is None:
541
1004
            token = ''
542
 
        try:
543
 
            response = self._client.call('Repository.lock_write', path, token)
544
 
        except errors.ErrorFromSmartServer, err:
545
 
            self._translate_error(err, token=token)
546
 
 
 
1005
        err_context = {'token': token}
 
1006
        response = self._call('Repository.lock_write', path, token,
 
1007
                              **err_context)
547
1008
        if response[0] == 'ok':
548
1009
            ok, token = response
549
1010
            return token
552
1013
 
553
1014
    def lock_write(self, token=None, _skip_rpc=False):
554
1015
        if not self._lock_mode:
 
1016
            self._note_lock('w')
555
1017
            if _skip_rpc:
556
1018
                if self._lock_token is not None:
557
1019
                    if token != self._lock_token:
571
1033
                self._leave_lock = False
572
1034
            self._lock_mode = 'w'
573
1035
            self._lock_count = 1
574
 
            self._parents_map = {}
575
 
            if 'hpss' in debug.debug_flags:
576
 
                self._requested_parents = set()
 
1036
            cache_misses = self._real_repository is None
 
1037
            self._unstacked_provider.enable_cache(cache_misses=cache_misses)
 
1038
            for repo in self._fallback_repositories:
 
1039
                # Writes don't affect fallback repos
 
1040
                repo.lock_read()
577
1041
        elif self._lock_mode == 'r':
578
1042
            raise errors.ReadOnlyError(self)
579
1043
        else:
597
1061
            implemented operations.
598
1062
        """
599
1063
        if self._real_repository is not None:
600
 
            raise AssertionError('_real_repository is already set')
 
1064
            # Replacing an already set real repository.
 
1065
            # We cannot do this [currently] if the repository is locked -
 
1066
            # synchronised state might be lost.
 
1067
            if self.is_locked():
 
1068
                raise AssertionError('_real_repository is already set')
601
1069
        if isinstance(repository, RemoteRepository):
602
1070
            raise AssertionError()
603
1071
        self._real_repository = repository
 
1072
        # three code paths happen here:
 
1073
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
 
1074
        # up stacking. In this case self._fallback_repositories is [], and the
 
1075
        # real repo is already setup. Preserve the real repo and
 
1076
        # RemoteRepository.add_fallback_repository will avoid adding
 
1077
        # duplicates.
 
1078
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
 
1079
        # ensure_real is triggered from a branch, the real repository to
 
1080
        # set already has a matching list with separate instances, but
 
1081
        # as they are also RemoteRepositories we don't worry about making the
 
1082
        # lists be identical.
 
1083
        # 3) new servers, RemoteRepository.ensure_real is triggered before
 
1084
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
 
1085
        # and need to populate it.
 
1086
        if (self._fallback_repositories and
 
1087
            len(self._real_repository._fallback_repositories) !=
 
1088
            len(self._fallback_repositories)):
 
1089
            if len(self._real_repository._fallback_repositories):
 
1090
                raise AssertionError(
 
1091
                    "cannot cleanly remove existing _fallback_repositories")
604
1092
        for fb in self._fallback_repositories:
605
1093
            self._real_repository.add_fallback_repository(fb)
606
1094
        if self._lock_mode == 'w':
612
1100
 
613
1101
    def start_write_group(self):
614
1102
        """Start a write group on the decorated repository.
615
 
        
616
 
        Smart methods peform operations in a single step so this api
 
1103
 
 
1104
        Smart methods perform operations in a single step so this API
617
1105
        is not really applicable except as a compatibility thunk
618
1106
        for older plugins that don't use e.g. the CommitBuilder
619
1107
        facility.
626
1114
        if not token:
627
1115
            # with no token the remote repository is not persistently locked.
628
1116
            return
629
 
        try:
630
 
            response = self._client.call('Repository.unlock', path, token)
631
 
        except errors.ErrorFromSmartServer, err:
632
 
            self._translate_error(err, token=token)
 
1117
        err_context = {'token': token}
 
1118
        response = self._call('Repository.unlock', path, token,
 
1119
                              **err_context)
633
1120
        if response == ('ok',):
634
1121
            return
635
1122
        else:
636
1123
            raise errors.UnexpectedSmartServerResponse(response)
637
1124
 
 
1125
    @only_raises(errors.LockNotHeld, errors.LockBroken)
638
1126
    def unlock(self):
 
1127
        if not self._lock_count:
 
1128
            return lock.cant_unlock_not_held(self)
639
1129
        self._lock_count -= 1
640
1130
        if self._lock_count > 0:
641
1131
            return
642
 
        self._parents_map = None
643
 
        if 'hpss' in debug.debug_flags:
644
 
            self._requested_parents = None
 
1132
        self._unstacked_provider.disable_cache()
645
1133
        old_mode = self._lock_mode
646
1134
        self._lock_mode = None
647
1135
        try:
657
1145
            # problem releasing the vfs-based lock.
658
1146
            if old_mode == 'w':
659
1147
                # Only write-locked repositories need to make a remote method
660
 
                # call to perfom the unlock.
 
1148
                # call to perform the unlock.
661
1149
                old_token = self._lock_token
662
1150
                self._lock_token = None
663
1151
                if not self._leave_lock:
664
1152
                    self._unlock(old_token)
 
1153
        # Fallbacks are always 'lock_read()' so we don't pay attention to
 
1154
        # self._leave_lock
 
1155
        for repo in self._fallback_repositories:
 
1156
            repo.unlock()
665
1157
 
666
1158
    def break_lock(self):
667
1159
        # should hand off to the network
670
1162
 
671
1163
    def _get_tarball(self, compression):
672
1164
        """Return a TemporaryFile containing a repository tarball.
673
 
        
 
1165
 
674
1166
        Returns None if the server does not support sending tarballs.
675
1167
        """
676
1168
        import tempfile
677
1169
        path = self.bzrdir._path_for_remote_call(self._client)
678
1170
        try:
679
 
            response, protocol = self._client.call_expecting_body(
 
1171
            response, protocol = self._call_expecting_body(
680
1172
                'Repository.tarball', path, compression)
681
1173
        except errors.UnknownSmartMethod:
682
1174
            protocol.cancel_read_body()
722
1214
 
723
1215
    def add_fallback_repository(self, repository):
724
1216
        """Add a repository to use for looking up data not held locally.
725
 
        
 
1217
 
726
1218
        :param repository: A repository.
727
1219
        """
728
 
        # XXX: At the moment the RemoteRepository will allow fallbacks
729
 
        # unconditionally - however, a _real_repository will usually exist,
730
 
        # and may raise an error if it's not accommodated by the underlying
731
 
        # format.  Eventually we should check when opening the repository
732
 
        # whether it's willing to allow them or not.
733
 
        #
 
1220
        if not self._format.supports_external_lookups:
 
1221
            raise errors.UnstackableRepositoryFormat(
 
1222
                self._format.network_name(), self.base)
734
1223
        # We need to accumulate additional repositories here, to pass them in
735
1224
        # on various RPC's.
 
1225
        #
 
1226
        if self.is_locked():
 
1227
            # We will call fallback.unlock() when we transition to the unlocked
 
1228
            # state, so always add a lock here. If a caller passes us a locked
 
1229
            # repository, they are responsible for unlocking it later.
 
1230
            repository.lock_read()
 
1231
        self._check_fallback_repository(repository)
736
1232
        self._fallback_repositories.append(repository)
737
 
        # They are also seen by the fallback repository.  If it doesn't exist
738
 
        # yet they'll be added then.  This implicitly copies them.
739
 
        self._ensure_real()
 
1233
        # If self._real_repository was parameterised already (e.g. because a
 
1234
        # _real_branch had its get_stacked_on_url method called), then the
 
1235
        # repository to be added may already be in the _real_repositories list.
 
1236
        if self._real_repository is not None:
 
1237
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
 
1238
                self._real_repository._fallback_repositories]
 
1239
            if repository.bzrdir.root_transport.base not in fallback_locations:
 
1240
                self._real_repository.add_fallback_repository(repository)
 
1241
 
 
1242
    def _check_fallback_repository(self, repository):
 
1243
        """Check that this repository can fallback to repository safely.
 
1244
 
 
1245
        Raise an error if not.
 
1246
 
 
1247
        :param repository: A repository to fallback to.
 
1248
        """
 
1249
        return _mod_repository.InterRepository._assert_same_model(
 
1250
            self, repository)
740
1251
 
741
1252
    def add_inventory(self, revid, inv, parents):
742
1253
        self._ensure_real()
743
1254
        return self._real_repository.add_inventory(revid, inv, parents)
744
1255
 
 
1256
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
 
1257
            parents, basis_inv=None, propagate_caches=False):
 
1258
        self._ensure_real()
 
1259
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
 
1260
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1261
            propagate_caches=propagate_caches)
 
1262
 
745
1263
    def add_revision(self, rev_id, rev, inv=None, config=None):
746
1264
        self._ensure_real()
747
1265
        return self._real_repository.add_revision(
752
1270
        self._ensure_real()
753
1271
        return self._real_repository.get_inventory(revision_id)
754
1272
 
755
 
    def iter_inventories(self, revision_ids):
 
1273
    def iter_inventories(self, revision_ids, ordering=None):
756
1274
        self._ensure_real()
757
 
        return self._real_repository.iter_inventories(revision_ids)
 
1275
        return self._real_repository.iter_inventories(revision_ids, ordering)
758
1276
 
759
1277
    @needs_read_lock
760
1278
    def get_revision(self, revision_id):
775
1293
        self._ensure_real()
776
1294
        return self._real_repository.make_working_trees()
777
1295
 
 
1296
    def refresh_data(self):
 
1297
        """Re-read any data needed to to synchronise with disk.
 
1298
 
 
1299
        This method is intended to be called after another repository instance
 
1300
        (such as one used by a smart server) has inserted data into the
 
1301
        repository. It may not be called during a write group, but may be
 
1302
        called at any other time.
 
1303
        """
 
1304
        if self.is_in_write_group():
 
1305
            raise errors.InternalBzrError(
 
1306
                "May not refresh_data while in a write group.")
 
1307
        if self._real_repository is not None:
 
1308
            self._real_repository.refresh_data()
 
1309
 
778
1310
    def revision_ids_to_search_result(self, result_set):
779
1311
        """Convert a set of revision ids to a graph SearchResult."""
780
1312
        result_parents = set()
791
1323
    @needs_read_lock
792
1324
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
793
1325
        """Return the revision ids that other has that this does not.
794
 
        
 
1326
 
795
1327
        These are returned in topological order.
796
1328
 
797
1329
        revision_id: only return revision ids included by revision_id.
799
1331
        return repository.InterRepository.get(
800
1332
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
801
1333
 
802
 
    def fetch(self, source, revision_id=None, pb=None):
803
 
        if self.has_same_location(source):
 
1334
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1335
            fetch_spec=None):
 
1336
        # No base implementation to use as RemoteRepository is not a subclass
 
1337
        # of Repository; so this is a copy of Repository.fetch().
 
1338
        if fetch_spec is not None and revision_id is not None:
 
1339
            raise AssertionError(
 
1340
                "fetch_spec and revision_id are mutually exclusive.")
 
1341
        if self.is_in_write_group():
 
1342
            raise errors.InternalBzrError(
 
1343
                "May not fetch while in a write group.")
 
1344
        # fast path same-url fetch operations
 
1345
        if (self.has_same_location(source)
 
1346
            and fetch_spec is None
 
1347
            and self._has_same_fallbacks(source)):
804
1348
            # check that last_revision is in 'from' and then return a
805
1349
            # no-operation.
806
1350
            if (revision_id is not None and
807
1351
                not revision.is_null(revision_id)):
808
1352
                self.get_revision(revision_id)
809
1353
            return 0, []
810
 
        self._ensure_real()
811
 
        return self._real_repository.fetch(
812
 
            source, revision_id=revision_id, pb=pb)
 
1354
        # if there is no specific appropriate InterRepository, this will get
 
1355
        # the InterRepository base class, which raises an
 
1356
        # IncompatibleRepositories when asked to fetch.
 
1357
        inter = repository.InterRepository.get(source, self)
 
1358
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1359
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
813
1360
 
814
1361
    def create_bundle(self, target, base, fileobj, format=None):
815
1362
        self._ensure_real()
828
1375
        self._ensure_real()
829
1376
        return self._real_repository._get_versioned_file_checker(
830
1377
            revisions, revision_versions_cache)
831
 
        
 
1378
 
832
1379
    def iter_files_bytes(self, desired_files):
833
1380
        """See Repository.iter_file_bytes.
834
1381
        """
835
1382
        self._ensure_real()
836
1383
        return self._real_repository.iter_files_bytes(desired_files)
837
1384
 
838
 
    @property
839
 
    def _fetch_order(self):
840
 
        """Decorate the real repository for now.
841
 
 
842
 
        In the long term getting this back from the remote repository as part
843
 
        of open would be more efficient.
844
 
        """
845
 
        self._ensure_real()
846
 
        return self._real_repository._fetch_order
847
 
 
848
 
    @property
849
 
    def _fetch_uses_deltas(self):
850
 
        """Decorate the real repository for now.
851
 
 
852
 
        In the long term getting this back from the remote repository as part
853
 
        of open would be more efficient.
854
 
        """
855
 
        self._ensure_real()
856
 
        return self._real_repository._fetch_uses_deltas
857
 
 
858
 
    @property
859
 
    def _fetch_reconcile(self):
860
 
        """Decorate the real repository for now.
861
 
 
862
 
        In the long term getting this back from the remote repository as part
863
 
        of open would be more efficient.
864
 
        """
865
 
        self._ensure_real()
866
 
        return self._real_repository._fetch_reconcile
867
 
 
868
 
    def get_parent_map(self, keys):
 
1385
    def get_parent_map(self, revision_ids):
869
1386
        """See bzrlib.Graph.get_parent_map()."""
870
 
        # Hack to build up the caching logic.
871
 
        ancestry = self._parents_map
872
 
        if ancestry is None:
873
 
            # Repository is not locked, so there's no cache.
874
 
            missing_revisions = set(keys)
875
 
            ancestry = {}
876
 
        else:
877
 
            missing_revisions = set(key for key in keys if key not in ancestry)
878
 
        if missing_revisions:
879
 
            parent_map = self._get_parent_map(missing_revisions)
880
 
            if 'hpss' in debug.debug_flags:
881
 
                mutter('retransmitted revisions: %d of %d',
882
 
                        len(set(ancestry).intersection(parent_map)),
883
 
                        len(parent_map))
884
 
            ancestry.update(parent_map)
885
 
        present_keys = [k for k in keys if k in ancestry]
886
 
        if 'hpss' in debug.debug_flags:
887
 
            if self._requested_parents is not None and len(ancestry) != 0:
888
 
                self._requested_parents.update(present_keys)
889
 
                mutter('Current RemoteRepository graph hit rate: %d%%',
890
 
                    100.0 * len(self._requested_parents) / len(ancestry))
891
 
        return dict((k, ancestry[k]) for k in present_keys)
 
1387
        return self._make_parents_provider().get_parent_map(revision_ids)
892
1388
 
893
 
    def _get_parent_map(self, keys):
 
1389
    def _get_parent_map_rpc(self, keys):
894
1390
        """Helper for get_parent_map that performs the RPC."""
895
1391
        medium = self._client._medium
896
1392
        if medium._is_remote_before((1, 2)):
897
1393
            # We already found out that the server can't understand
898
1394
            # Repository.get_parent_map requests, so just fetch the whole
899
1395
            # graph.
900
 
            # XXX: Note that this will issue a deprecation warning. This is ok
901
 
            # :- its because we're working with a deprecated server anyway, and
902
 
            # the user will almost certainly have seen a warning about the
903
 
            # server version already.
904
 
            rg = self.get_revision_graph()
905
 
            # There is an api discrepency between get_parent_map and
 
1396
            #
 
1397
            # Note that this reads the whole graph, when only some keys are
 
1398
            # wanted.  On this old server there's no way (?) to get them all
 
1399
            # in one go, and the user probably will have seen a warning about
 
1400
            # the server being old anyhow.
 
1401
            rg = self._get_revision_graph(None)
 
1402
            # There is an API discrepancy between get_parent_map and
906
1403
            # get_revision_graph. Specifically, a "key:()" pair in
907
1404
            # get_revision_graph just means a node has no parents. For
908
1405
            # "get_parent_map" it means the node is a ghost. So fix up the
938
1435
        # TODO: Manage this incrementally to avoid covering the same path
939
1436
        # repeatedly. (The server will have to on each request, but the less
940
1437
        # work done the better).
941
 
        parents_map = self._parents_map
 
1438
        #
 
1439
        # Negative caching notes:
 
1440
        # new server sends missing when a request including the revid
 
1441
        # 'include-missing:' is present in the request.
 
1442
        # missing keys are serialised as missing:X, and we then call
 
1443
        # provider.note_missing(X) for-all X
 
1444
        parents_map = self._unstacked_provider.get_cached_map()
942
1445
        if parents_map is None:
943
1446
            # Repository is not locked, so there's no cache.
944
1447
            parents_map = {}
 
1448
        # start_set is all the keys in the cache
945
1449
        start_set = set(parents_map)
 
1450
        # result set is all the references to keys in the cache
946
1451
        result_parents = set()
947
1452
        for parents in parents_map.itervalues():
948
1453
            result_parents.update(parents)
949
1454
        stop_keys = result_parents.difference(start_set)
 
1455
        # We don't need to send ghosts back to the server as a position to
 
1456
        # stop either.
 
1457
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1458
        key_count = len(parents_map)
 
1459
        if (NULL_REVISION in result_parents
 
1460
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1461
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1462
            # in our cache of "missing" keys we need to increment our key count
 
1463
            # by 1, because the reconsitituted SearchResult on the server will
 
1464
            # still consider NULL_REVISION to be an included key.
 
1465
            key_count += 1
950
1466
        included_keys = start_set.intersection(result_parents)
951
1467
        start_set.difference_update(included_keys)
952
 
        recipe = (start_set, stop_keys, len(parents_map))
 
1468
        recipe = ('manual', start_set, stop_keys, key_count)
953
1469
        body = self._serialise_search_recipe(recipe)
954
1470
        path = self.bzrdir._path_for_remote_call(self._client)
955
1471
        for key in keys:
957
1473
                raise ValueError(
958
1474
                    "key %r not a plain string" % (key,))
959
1475
        verb = 'Repository.get_parent_map'
960
 
        args = (path,) + tuple(keys)
 
1476
        args = (path, 'include-missing:') + tuple(keys)
961
1477
        try:
962
 
            response = self._client.call_with_body_bytes_expecting_body(
963
 
                verb, args, self._serialise_search_recipe(recipe))
 
1478
            response = self._call_with_body_bytes_expecting_body(
 
1479
                verb, args, body)
964
1480
        except errors.UnknownSmartMethod:
965
1481
            # Server does not support this method, so get the whole graph.
966
1482
            # Worse, we have to force a disconnection, because the server now
973
1489
            # To avoid having to disconnect repeatedly, we keep track of the
974
1490
            # fact the server doesn't understand remote methods added in 1.2.
975
1491
            medium._remember_remote_is_before((1, 2))
976
 
            return self.get_revision_graph(None)
 
1492
            # Recurse just once and we should use the fallback code.
 
1493
            return self._get_parent_map_rpc(keys)
977
1494
        response_tuple, response_handler = response
978
1495
        if response_tuple[0] not in ['ok']:
979
1496
            response_handler.cancel_read_body()
990
1507
                if len(d) > 1:
991
1508
                    revision_graph[d[0]] = d[1:]
992
1509
                else:
993
 
                    # No parents - so give the Graph result (NULL_REVISION,).
994
 
                    revision_graph[d[0]] = (NULL_REVISION,)
 
1510
                    # No parents:
 
1511
                    if d[0].startswith('missing:'):
 
1512
                        revid = d[0][8:]
 
1513
                        self._unstacked_provider.note_missing_key(revid)
 
1514
                    else:
 
1515
                        # no parents - so give the Graph result
 
1516
                        # (NULL_REVISION,).
 
1517
                        revision_graph[d[0]] = (NULL_REVISION,)
995
1518
            return revision_graph
996
1519
 
997
1520
    @needs_read_lock
1000
1523
        return self._real_repository.get_signature_text(revision_id)
1001
1524
 
1002
1525
    @needs_read_lock
1003
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
1004
 
    def get_revision_graph_with_ghosts(self, revision_ids=None):
1005
 
        self._ensure_real()
1006
 
        return self._real_repository.get_revision_graph_with_ghosts(
1007
 
            revision_ids=revision_ids)
1008
 
 
1009
 
    @needs_read_lock
1010
 
    def get_inventory_xml(self, revision_id):
1011
 
        self._ensure_real()
1012
 
        return self._real_repository.get_inventory_xml(revision_id)
1013
 
 
1014
 
    def deserialise_inventory(self, revision_id, xml):
1015
 
        self._ensure_real()
1016
 
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
1526
    def _get_inventory_xml(self, revision_id):
 
1527
        self._ensure_real()
 
1528
        return self._real_repository._get_inventory_xml(revision_id)
1017
1529
 
1018
1530
    def reconcile(self, other=None, thorough=False):
1019
1531
        self._ensure_real()
1020
1532
        return self._real_repository.reconcile(other=other, thorough=thorough)
1021
 
        
 
1533
 
1022
1534
    def all_revision_ids(self):
1023
1535
        self._ensure_real()
1024
1536
        return self._real_repository.all_revision_ids()
1025
 
    
1026
 
    @needs_read_lock
1027
 
    def get_deltas_for_revisions(self, revisions):
1028
 
        self._ensure_real()
1029
 
        return self._real_repository.get_deltas_for_revisions(revisions)
1030
 
 
1031
 
    @needs_read_lock
1032
 
    def get_revision_delta(self, revision_id):
1033
 
        self._ensure_real()
1034
 
        return self._real_repository.get_revision_delta(revision_id)
 
1537
 
 
1538
    @needs_read_lock
 
1539
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1540
        self._ensure_real()
 
1541
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1542
            specific_fileids=specific_fileids)
 
1543
 
 
1544
    @needs_read_lock
 
1545
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1546
        self._ensure_real()
 
1547
        return self._real_repository.get_revision_delta(revision_id,
 
1548
            specific_fileids=specific_fileids)
1035
1549
 
1036
1550
    @needs_read_lock
1037
1551
    def revision_trees(self, revision_ids):
1044
1558
        return self._real_repository.get_revision_reconcile(revision_id)
1045
1559
 
1046
1560
    @needs_read_lock
1047
 
    def check(self, revision_ids=None):
 
1561
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1048
1562
        self._ensure_real()
1049
 
        return self._real_repository.check(revision_ids=revision_ids)
 
1563
        return self._real_repository.check(revision_ids=revision_ids,
 
1564
            callback_refs=callback_refs, check_repo=check_repo)
1050
1565
 
1051
1566
    def copy_content_into(self, destination, revision_id=None):
1052
1567
        self._ensure_real()
1092
1607
        return self._real_repository.inventories
1093
1608
 
1094
1609
    @needs_write_lock
1095
 
    def pack(self):
 
1610
    def pack(self, hint=None, clean_obsolete_packs=False):
1096
1611
        """Compress the data within the repository.
1097
1612
 
1098
1613
        This is not currently implemented within the smart server.
1099
1614
        """
1100
1615
        self._ensure_real()
1101
 
        return self._real_repository.pack()
 
1616
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1102
1617
 
1103
1618
    @property
1104
1619
    def revisions(self):
1113
1628
        return self._real_repository.revisions
1114
1629
 
1115
1630
    def set_make_working_trees(self, new_value):
1116
 
        self._ensure_real()
1117
 
        self._real_repository.set_make_working_trees(new_value)
 
1631
        if new_value:
 
1632
            new_value_str = "True"
 
1633
        else:
 
1634
            new_value_str = "False"
 
1635
        path = self.bzrdir._path_for_remote_call(self._client)
 
1636
        try:
 
1637
            response = self._call(
 
1638
                'Repository.set_make_working_trees', path, new_value_str)
 
1639
        except errors.UnknownSmartMethod:
 
1640
            self._ensure_real()
 
1641
            self._real_repository.set_make_working_trees(new_value)
 
1642
        else:
 
1643
            if response[0] != 'ok':
 
1644
                raise errors.UnexpectedSmartServerResponse(response)
1118
1645
 
1119
1646
    @property
1120
1647
    def signatures(self):
1147
1674
        return self._real_repository.get_revisions(revision_ids)
1148
1675
 
1149
1676
    def supports_rich_root(self):
1150
 
        self._ensure_real()
1151
 
        return self._real_repository.supports_rich_root()
 
1677
        return self._format.rich_root_data
1152
1678
 
1153
1679
    def iter_reverse_revision_history(self, revision_id):
1154
1680
        self._ensure_real()
1156
1682
 
1157
1683
    @property
1158
1684
    def _serializer(self):
1159
 
        self._ensure_real()
1160
 
        return self._real_repository._serializer
 
1685
        return self._format._serializer
1161
1686
 
1162
1687
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1163
1688
        self._ensure_real()
1182
1707
        self._ensure_real()
1183
1708
        return self._real_repository.revision_graph_can_have_wrong_parents()
1184
1709
 
1185
 
    def _find_inconsistent_revision_parents(self):
 
1710
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1186
1711
        self._ensure_real()
1187
 
        return self._real_repository._find_inconsistent_revision_parents()
 
1712
        return self._real_repository._find_inconsistent_revision_parents(
 
1713
            revisions_iterator)
1188
1714
 
1189
1715
    def _check_for_inconsistent_revision_parents(self):
1190
1716
        self._ensure_real()
1191
1717
        return self._real_repository._check_for_inconsistent_revision_parents()
1192
1718
 
1193
 
    def _make_parents_provider(self):
1194
 
        return self
 
1719
    def _make_parents_provider(self, other=None):
 
1720
        providers = [self._unstacked_provider]
 
1721
        if other is not None:
 
1722
            providers.insert(0, other)
 
1723
        providers.extend(r._make_parents_provider() for r in
 
1724
                         self._fallback_repositories)
 
1725
        return graph.StackedParentsProvider(providers)
1195
1726
 
1196
1727
    def _serialise_search_recipe(self, recipe):
1197
1728
        """Serialise a graph search recipe.
1199
1730
        :param recipe: A search recipe (start, stop, count).
1200
1731
        :return: Serialised bytes.
1201
1732
        """
1202
 
        start_keys = ' '.join(recipe[0])
1203
 
        stop_keys = ' '.join(recipe[1])
1204
 
        count = str(recipe[2])
 
1733
        start_keys = ' '.join(recipe[1])
 
1734
        stop_keys = ' '.join(recipe[2])
 
1735
        count = str(recipe[3])
1205
1736
        return '\n'.join((start_keys, stop_keys, count))
1206
1737
 
 
1738
    def _serialise_search_result(self, search_result):
 
1739
        if isinstance(search_result, graph.PendingAncestryResult):
 
1740
            parts = ['ancestry-of']
 
1741
            parts.extend(search_result.heads)
 
1742
        else:
 
1743
            recipe = search_result.get_recipe()
 
1744
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1745
        return '\n'.join(parts)
 
1746
 
 
1747
    def autopack(self):
 
1748
        path = self.bzrdir._path_for_remote_call(self._client)
 
1749
        try:
 
1750
            response = self._call('PackRepository.autopack', path)
 
1751
        except errors.UnknownSmartMethod:
 
1752
            self._ensure_real()
 
1753
            self._real_repository._pack_collection.autopack()
 
1754
            return
 
1755
        self.refresh_data()
 
1756
        if response[0] != 'ok':
 
1757
            raise errors.UnexpectedSmartServerResponse(response)
 
1758
 
 
1759
 
 
1760
class RemoteStreamSink(repository.StreamSink):
 
1761
 
 
1762
    def _insert_real(self, stream, src_format, resume_tokens):
 
1763
        self.target_repo._ensure_real()
 
1764
        sink = self.target_repo._real_repository._get_sink()
 
1765
        result = sink.insert_stream(stream, src_format, resume_tokens)
 
1766
        if not result:
 
1767
            self.target_repo.autopack()
 
1768
        return result
 
1769
 
 
1770
    def insert_stream(self, stream, src_format, resume_tokens):
 
1771
        target = self.target_repo
 
1772
        target._unstacked_provider.missing_keys.clear()
 
1773
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
 
1774
        if target._lock_token:
 
1775
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1776
            lock_args = (target._lock_token or '',)
 
1777
        else:
 
1778
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
 
1779
            lock_args = ()
 
1780
        client = target._client
 
1781
        medium = client._medium
 
1782
        path = target.bzrdir._path_for_remote_call(client)
 
1783
        # Probe for the verb to use with an empty stream before sending the
 
1784
        # real stream to it.  We do this both to avoid the risk of sending a
 
1785
        # large request that is then rejected, and because we don't want to
 
1786
        # implement a way to buffer, rewind, or restart the stream.
 
1787
        found_verb = False
 
1788
        for verb, required_version in candidate_calls:
 
1789
            if medium._is_remote_before(required_version):
 
1790
                continue
 
1791
            if resume_tokens:
 
1792
                # We've already done the probing (and set _is_remote_before) on
 
1793
                # a previous insert.
 
1794
                found_verb = True
 
1795
                break
 
1796
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
 
1797
            try:
 
1798
                response = client.call_with_body_stream(
 
1799
                    (verb, path, '') + lock_args, byte_stream)
 
1800
            except errors.UnknownSmartMethod:
 
1801
                medium._remember_remote_is_before(required_version)
 
1802
            else:
 
1803
                found_verb = True
 
1804
                break
 
1805
        if not found_verb:
 
1806
            # Have to use VFS.
 
1807
            return self._insert_real(stream, src_format, resume_tokens)
 
1808
        self._last_inv_record = None
 
1809
        self._last_substream = None
 
1810
        if required_version < (1, 19):
 
1811
            # Remote side doesn't support inventory deltas.  Wrap the stream to
 
1812
            # make sure we don't send any.  If the stream contains inventory
 
1813
            # deltas we'll interrupt the smart insert_stream request and
 
1814
            # fallback to VFS.
 
1815
            stream = self._stop_stream_if_inventory_delta(stream)
 
1816
        byte_stream = smart_repo._stream_to_byte_stream(
 
1817
            stream, src_format)
 
1818
        resume_tokens = ' '.join(resume_tokens)
 
1819
        response = client.call_with_body_stream(
 
1820
            (verb, path, resume_tokens) + lock_args, byte_stream)
 
1821
        if response[0][0] not in ('ok', 'missing-basis'):
 
1822
            raise errors.UnexpectedSmartServerResponse(response)
 
1823
        if self._last_substream is not None:
 
1824
            # The stream included an inventory-delta record, but the remote
 
1825
            # side isn't new enough to support them.  So we need to send the
 
1826
            # rest of the stream via VFS.
 
1827
            self.target_repo.refresh_data()
 
1828
            return self._resume_stream_with_vfs(response, src_format)
 
1829
        if response[0][0] == 'missing-basis':
 
1830
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1831
            resume_tokens = tokens
 
1832
            return resume_tokens, set(missing_keys)
 
1833
        else:
 
1834
            self.target_repo.refresh_data()
 
1835
            return [], set()
 
1836
 
 
1837
    def _resume_stream_with_vfs(self, response, src_format):
 
1838
        """Resume sending a stream via VFS, first resending the record and
 
1839
        substream that couldn't be sent via an insert_stream verb.
 
1840
        """
 
1841
        if response[0][0] == 'missing-basis':
 
1842
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
 
1843
            # Ignore missing_keys, we haven't finished inserting yet
 
1844
        else:
 
1845
            tokens = []
 
1846
        def resume_substream():
 
1847
            # Yield the substream that was interrupted.
 
1848
            for record in self._last_substream:
 
1849
                yield record
 
1850
            self._last_substream = None
 
1851
        def resume_stream():
 
1852
            # Finish sending the interrupted substream
 
1853
            yield ('inventory-deltas', resume_substream())
 
1854
            # Then simply continue sending the rest of the stream.
 
1855
            for substream_kind, substream in self._last_stream:
 
1856
                yield substream_kind, substream
 
1857
        return self._insert_real(resume_stream(), src_format, tokens)
 
1858
 
 
1859
    def _stop_stream_if_inventory_delta(self, stream):
 
1860
        """Normally this just lets the original stream pass-through unchanged.
 
1861
 
 
1862
        However if any 'inventory-deltas' substream occurs it will stop
 
1863
        streaming, and store the interrupted substream and stream in
 
1864
        self._last_substream and self._last_stream so that the stream can be
 
1865
        resumed by _resume_stream_with_vfs.
 
1866
        """
 
1867
                    
 
1868
        stream_iter = iter(stream)
 
1869
        for substream_kind, substream in stream_iter:
 
1870
            if substream_kind == 'inventory-deltas':
 
1871
                self._last_substream = substream
 
1872
                self._last_stream = stream_iter
 
1873
                return
 
1874
            else:
 
1875
                yield substream_kind, substream
 
1876
            
 
1877
 
 
1878
class RemoteStreamSource(repository.StreamSource):
 
1879
    """Stream data from a remote server."""
 
1880
 
 
1881
    def get_stream(self, search):
 
1882
        if (self.from_repository._fallback_repositories and
 
1883
            self.to_format._fetch_order == 'topological'):
 
1884
            return self._real_stream(self.from_repository, search)
 
1885
        sources = []
 
1886
        seen = set()
 
1887
        repos = [self.from_repository]
 
1888
        while repos:
 
1889
            repo = repos.pop(0)
 
1890
            if repo in seen:
 
1891
                continue
 
1892
            seen.add(repo)
 
1893
            repos.extend(repo._fallback_repositories)
 
1894
            sources.append(repo)
 
1895
        return self.missing_parents_chain(search, sources)
 
1896
 
 
1897
    def get_stream_for_missing_keys(self, missing_keys):
 
1898
        self.from_repository._ensure_real()
 
1899
        real_repo = self.from_repository._real_repository
 
1900
        real_source = real_repo._get_source(self.to_format)
 
1901
        return real_source.get_stream_for_missing_keys(missing_keys)
 
1902
 
 
1903
    def _real_stream(self, repo, search):
 
1904
        """Get a stream for search from repo.
 
1905
        
 
1906
        This never called RemoteStreamSource.get_stream, and is a heler
 
1907
        for RemoteStreamSource._get_stream to allow getting a stream 
 
1908
        reliably whether fallback back because of old servers or trying
 
1909
        to stream from a non-RemoteRepository (which the stacked support
 
1910
        code will do).
 
1911
        """
 
1912
        source = repo._get_source(self.to_format)
 
1913
        if isinstance(source, RemoteStreamSource):
 
1914
            repo._ensure_real()
 
1915
            source = repo._real_repository._get_source(self.to_format)
 
1916
        return source.get_stream(search)
 
1917
 
 
1918
    def _get_stream(self, repo, search):
 
1919
        """Core worker to get a stream from repo for search.
 
1920
 
 
1921
        This is used by both get_stream and the stacking support logic. It
 
1922
        deliberately gets a stream for repo which does not need to be
 
1923
        self.from_repository. In the event that repo is not Remote, or
 
1924
        cannot do a smart stream, a fallback is made to the generic
 
1925
        repository._get_stream() interface, via self._real_stream.
 
1926
 
 
1927
        In the event of stacking, streams from _get_stream will not
 
1928
        contain all the data for search - this is normal (see get_stream).
 
1929
 
 
1930
        :param repo: A repository.
 
1931
        :param search: A search.
 
1932
        """
 
1933
        # Fallbacks may be non-smart
 
1934
        if not isinstance(repo, RemoteRepository):
 
1935
            return self._real_stream(repo, search)
 
1936
        client = repo._client
 
1937
        medium = client._medium
 
1938
        path = repo.bzrdir._path_for_remote_call(client)
 
1939
        search_bytes = repo._serialise_search_result(search)
 
1940
        args = (path, self.to_format.network_name())
 
1941
        candidate_verbs = [
 
1942
            ('Repository.get_stream_1.19', (1, 19)),
 
1943
            ('Repository.get_stream', (1, 13))]
 
1944
        found_verb = False
 
1945
        for verb, version in candidate_verbs:
 
1946
            if medium._is_remote_before(version):
 
1947
                continue
 
1948
            try:
 
1949
                response = repo._call_with_body_bytes_expecting_body(
 
1950
                    verb, args, search_bytes)
 
1951
            except errors.UnknownSmartMethod:
 
1952
                medium._remember_remote_is_before(version)
 
1953
            else:
 
1954
                response_tuple, response_handler = response
 
1955
                found_verb = True
 
1956
                break
 
1957
        if not found_verb:
 
1958
            return self._real_stream(repo, search)
 
1959
        if response_tuple[0] != 'ok':
 
1960
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1961
        byte_stream = response_handler.read_streamed_body()
 
1962
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
 
1963
        if src_format.network_name() != repo._format.network_name():
 
1964
            raise AssertionError(
 
1965
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
1966
                src_format.network_name(), repo._format.network_name()))
 
1967
        return stream
 
1968
 
 
1969
    def missing_parents_chain(self, search, sources):
 
1970
        """Chain multiple streams together to handle stacking.
 
1971
 
 
1972
        :param search: The overall search to satisfy with streams.
 
1973
        :param sources: A list of Repository objects to query.
 
1974
        """
 
1975
        self.from_serialiser = self.from_repository._format._serializer
 
1976
        self.seen_revs = set()
 
1977
        self.referenced_revs = set()
 
1978
        # If there are heads in the search, or the key count is > 0, we are not
 
1979
        # done.
 
1980
        while not search.is_empty() and len(sources) > 1:
 
1981
            source = sources.pop(0)
 
1982
            stream = self._get_stream(source, search)
 
1983
            for kind, substream in stream:
 
1984
                if kind != 'revisions':
 
1985
                    yield kind, substream
 
1986
                else:
 
1987
                    yield kind, self.missing_parents_rev_handler(substream)
 
1988
            search = search.refine(self.seen_revs, self.referenced_revs)
 
1989
            self.seen_revs = set()
 
1990
            self.referenced_revs = set()
 
1991
        if not search.is_empty():
 
1992
            for kind, stream in self._get_stream(sources[0], search):
 
1993
                yield kind, stream
 
1994
 
 
1995
    def missing_parents_rev_handler(self, substream):
 
1996
        for content in substream:
 
1997
            revision_bytes = content.get_bytes_as('fulltext')
 
1998
            revision = self.from_serialiser.read_revision_from_string(
 
1999
                revision_bytes)
 
2000
            self.seen_revs.add(content.key[-1])
 
2001
            self.referenced_revs.update(revision.parent_ids)
 
2002
            yield content
 
2003
 
1207
2004
 
1208
2005
class RemoteBranchLockableFiles(LockableFiles):
1209
2006
    """A 'LockableFiles' implementation that talks to a smart server.
1210
 
    
 
2007
 
1211
2008
    This is not a public interface class.
1212
2009
    """
1213
2010
 
1227
2024
 
1228
2025
class RemoteBranchFormat(branch.BranchFormat):
1229
2026
 
 
2027
    def __init__(self, network_name=None):
 
2028
        super(RemoteBranchFormat, self).__init__()
 
2029
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2030
        self._matchingbzrdir.set_branch_format(self)
 
2031
        self._custom_format = None
 
2032
        self._network_name = network_name
 
2033
 
1230
2034
    def __eq__(self, other):
1231
 
        return (isinstance(other, RemoteBranchFormat) and 
 
2035
        return (isinstance(other, RemoteBranchFormat) and
1232
2036
            self.__dict__ == other.__dict__)
1233
2037
 
 
2038
    def _ensure_real(self):
 
2039
        if self._custom_format is None:
 
2040
            self._custom_format = branch.network_format_registry.get(
 
2041
                self._network_name)
 
2042
 
1234
2043
    def get_format_description(self):
1235
 
        return 'Remote BZR Branch'
1236
 
 
1237
 
    def get_format_string(self):
1238
 
        return 'Remote BZR Branch'
1239
 
 
1240
 
    def open(self, a_bzrdir):
1241
 
        return a_bzrdir.open_branch()
1242
 
 
1243
 
    def initialize(self, a_bzrdir):
1244
 
        return a_bzrdir.create_branch()
 
2044
        self._ensure_real()
 
2045
        return 'Remote: ' + self._custom_format.get_format_description()
 
2046
 
 
2047
    def network_name(self):
 
2048
        return self._network_name
 
2049
 
 
2050
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2051
        return a_bzrdir.open_branch(name=name, 
 
2052
            ignore_fallbacks=ignore_fallbacks)
 
2053
 
 
2054
    def _vfs_initialize(self, a_bzrdir, name):
 
2055
        # Initialisation when using a local bzrdir object, or a non-vfs init
 
2056
        # method is not available on the server.
 
2057
        # self._custom_format is always set - the start of initialize ensures
 
2058
        # that.
 
2059
        if isinstance(a_bzrdir, RemoteBzrDir):
 
2060
            a_bzrdir._ensure_real()
 
2061
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2062
                name)
 
2063
        else:
 
2064
            # We assume the bzrdir is parameterised; it may not be.
 
2065
            result = self._custom_format.initialize(a_bzrdir, name)
 
2066
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
2067
            not isinstance(result, RemoteBranch)):
 
2068
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
 
2069
                                  name=name)
 
2070
        return result
 
2071
 
 
2072
    def initialize(self, a_bzrdir, name=None):
 
2073
        # 1) get the network name to use.
 
2074
        if self._custom_format:
 
2075
            network_name = self._custom_format.network_name()
 
2076
        else:
 
2077
            # Select the current bzrlib default and ask for that.
 
2078
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
2079
            reference_format = reference_bzrdir_format.get_branch_format()
 
2080
            self._custom_format = reference_format
 
2081
            network_name = reference_format.network_name()
 
2082
        # Being asked to create on a non RemoteBzrDir:
 
2083
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
2084
            return self._vfs_initialize(a_bzrdir, name=name)
 
2085
        medium = a_bzrdir._client._medium
 
2086
        if medium._is_remote_before((1, 13)):
 
2087
            return self._vfs_initialize(a_bzrdir, name=name)
 
2088
        # Creating on a remote bzr dir.
 
2089
        # 2) try direct creation via RPC
 
2090
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2091
        if name is not None:
 
2092
            # XXX JRV20100304: Support creating colocated branches
 
2093
            raise errors.NoColocatedBranchSupport(self)
 
2094
        verb = 'BzrDir.create_branch'
 
2095
        try:
 
2096
            response = a_bzrdir._call(verb, path, network_name)
 
2097
        except errors.UnknownSmartMethod:
 
2098
            # Fallback - use vfs methods
 
2099
            medium._remember_remote_is_before((1, 13))
 
2100
            return self._vfs_initialize(a_bzrdir, name=name)
 
2101
        if response[0] != 'ok':
 
2102
            raise errors.UnexpectedSmartServerResponse(response)
 
2103
        # Turn the response into a RemoteRepository object.
 
2104
        format = RemoteBranchFormat(network_name=response[1])
 
2105
        repo_format = response_tuple_to_repo_format(response[3:])
 
2106
        if response[2] == '':
 
2107
            repo_bzrdir = a_bzrdir
 
2108
        else:
 
2109
            repo_bzrdir = RemoteBzrDir(
 
2110
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2111
                a_bzrdir._client)
 
2112
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2113
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2114
            format=format, setup_stacking=False, name=name)
 
2115
        # XXX: We know this is a new branch, so it must have revno 0, revid
 
2116
        # NULL_REVISION. Creating the branch locked would make this be unable
 
2117
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
 
2118
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
 
2119
        return remote_branch
 
2120
 
 
2121
    def make_tags(self, branch):
 
2122
        self._ensure_real()
 
2123
        return self._custom_format.make_tags(branch)
1245
2124
 
1246
2125
    def supports_tags(self):
1247
2126
        # Remote branches might support tags, but we won't know until we
1248
2127
        # access the real remote branch.
1249
 
        return True
1250
 
 
1251
 
 
1252
 
class RemoteBranch(branch.Branch):
 
2128
        self._ensure_real()
 
2129
        return self._custom_format.supports_tags()
 
2130
 
 
2131
    def supports_stacking(self):
 
2132
        self._ensure_real()
 
2133
        return self._custom_format.supports_stacking()
 
2134
 
 
2135
    def supports_set_append_revisions_only(self):
 
2136
        self._ensure_real()
 
2137
        return self._custom_format.supports_set_append_revisions_only()
 
2138
 
 
2139
 
 
2140
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1253
2141
    """Branch stored on a server accessed by HPSS RPC.
1254
2142
 
1255
2143
    At the moment most operations are mapped down to simple file operations.
1256
2144
    """
1257
2145
 
1258
2146
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1259
 
        _client=None):
 
2147
        _client=None, format=None, setup_stacking=True, name=None):
1260
2148
        """Create a RemoteBranch instance.
1261
2149
 
1262
2150
        :param real_branch: An optional local implementation of the branch
1263
2151
            format, usually accessing the data via the VFS.
1264
2152
        :param _client: Private parameter for testing.
 
2153
        :param format: A RemoteBranchFormat object, None to create one
 
2154
            automatically. If supplied it should have a network_name already
 
2155
            supplied.
 
2156
        :param setup_stacking: If True make an RPC call to determine the
 
2157
            stacked (or not) status of the branch. If False assume the branch
 
2158
            is not stacked.
 
2159
        :param name: Colocated branch name
1265
2160
        """
1266
2161
        # We intentionally don't call the parent class's __init__, because it
1267
2162
        # will try to assign to self.tags, which is a property in this subclass.
1268
2163
        # And the parent's __init__ doesn't do much anyway.
1269
 
        self._revision_id_to_revno_cache = None
1270
 
        self._revision_history_cache = None
1271
 
        self._last_revision_info_cache = None
1272
2164
        self.bzrdir = remote_bzrdir
1273
2165
        if _client is not None:
1274
2166
            self._client = _client
1287
2179
            self._real_branch.repository = self.repository
1288
2180
        else:
1289
2181
            self._real_branch = None
1290
 
        # Fill out expected attributes of branch for bzrlib api users.
1291
 
        self._format = RemoteBranchFormat()
 
2182
        # Fill out expected attributes of branch for bzrlib API users.
 
2183
        self._clear_cached_state()
1292
2184
        self.base = self.bzrdir.root_transport.base
 
2185
        self._name = name
1293
2186
        self._control_files = None
1294
2187
        self._lock_mode = None
1295
2188
        self._lock_token = None
1296
2189
        self._repo_lock_token = None
1297
2190
        self._lock_count = 0
1298
2191
        self._leave_lock = False
 
2192
        # Setup a format: note that we cannot call _ensure_real until all the
 
2193
        # attributes above are set: This code cannot be moved higher up in this
 
2194
        # function.
 
2195
        if format is None:
 
2196
            self._format = RemoteBranchFormat()
 
2197
            if real_branch is not None:
 
2198
                self._format._network_name = \
 
2199
                    self._real_branch._format.network_name()
 
2200
        else:
 
2201
            self._format = format
 
2202
        # when we do _ensure_real we may need to pass ignore_fallbacks to the
 
2203
        # branch.open_branch method.
 
2204
        self._real_ignore_fallbacks = not setup_stacking
 
2205
        if not self._format._network_name:
 
2206
            # Did not get from open_branchV2 - old server.
 
2207
            self._ensure_real()
 
2208
            self._format._network_name = \
 
2209
                self._real_branch._format.network_name()
 
2210
        self.tags = self._format.make_tags(self)
1299
2211
        # The base class init is not called, so we duplicate this:
1300
2212
        hooks = branch.Branch.hooks['open']
1301
2213
        for hook in hooks:
1302
2214
            hook(self)
1303
 
        self._setup_stacking()
 
2215
        self._is_stacked = False
 
2216
        if setup_stacking:
 
2217
            self._setup_stacking()
1304
2218
 
1305
2219
    def _setup_stacking(self):
1306
2220
        # configure stacking into the remote repository, by reading it from
1310
2224
        except (errors.NotStacked, errors.UnstackableBranchFormat,
1311
2225
            errors.UnstackableRepositoryFormat), e:
1312
2226
            return
1313
 
        # it's relative to this branch...
1314
 
        fallback_url = urlutils.join(self.base, fallback_url)
1315
 
        transports = [self.bzrdir.root_transport]
1316
 
        if self._real_branch is not None:
1317
 
            transports.append(self._real_branch._transport)
1318
 
        fallback_bzrdir = BzrDir.open(fallback_url, transports)
1319
 
        fallback_repo = fallback_bzrdir.open_repository()
1320
 
        self.repository.add_fallback_repository(fallback_repo)
 
2227
        self._is_stacked = True
 
2228
        self._activate_fallback_location(fallback_url)
 
2229
 
 
2230
    def _get_config(self):
 
2231
        return RemoteBranchConfig(self)
1321
2232
 
1322
2233
    def _get_real_transport(self):
1323
2234
        # if we try vfs access, return the real branch's vfs transport
1341
2252
                raise AssertionError('smart server vfs must be enabled '
1342
2253
                    'to use vfs implementation')
1343
2254
            self.bzrdir._ensure_real()
1344
 
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
2255
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
 
2256
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
1345
2257
            if self.repository._real_repository is None:
1346
2258
                # Give the remote repository the matching real repo.
1347
2259
                real_repo = self._real_branch.repository
1376
2288
        too, in fact doing so might harm performance.
1377
2289
        """
1378
2290
        super(RemoteBranch, self)._clear_cached_state()
1379
 
        
 
2291
 
1380
2292
    @property
1381
2293
    def control_files(self):
1382
2294
        # Defer actually creating RemoteBranchLockableFiles until its needed,
1406
2318
            stacking.
1407
2319
        """
1408
2320
        try:
 
2321
            # there may not be a repository yet, so we can't use
 
2322
            # self._translate_error, so we can't use self._call either.
1409
2323
            response = self._client.call('Branch.get_stacked_on_url',
1410
2324
                self._remote_path())
1411
 
            if response[0] != 'ok':
1412
 
                raise errors.UnexpectedSmartServerResponse(response)
1413
 
            return response[1]
1414
2325
        except errors.ErrorFromSmartServer, err:
1415
2326
            # there may not be a repository yet, so we can't call through
1416
2327
            # its _translate_error
1418
2329
        except errors.UnknownSmartMethod, err:
1419
2330
            self._ensure_real()
1420
2331
            return self._real_branch.get_stacked_on_url()
 
2332
        if response[0] != 'ok':
 
2333
            raise errors.UnexpectedSmartServerResponse(response)
 
2334
        return response[1]
 
2335
 
 
2336
    def set_stacked_on_url(self, url):
 
2337
        branch.Branch.set_stacked_on_url(self, url)
 
2338
        if not url:
 
2339
            self._is_stacked = False
 
2340
        else:
 
2341
            self._is_stacked = True
 
2342
        
 
2343
    def _vfs_get_tags_bytes(self):
 
2344
        self._ensure_real()
 
2345
        return self._real_branch._get_tags_bytes()
 
2346
 
 
2347
    def _get_tags_bytes(self):
 
2348
        medium = self._client._medium
 
2349
        if medium._is_remote_before((1, 13)):
 
2350
            return self._vfs_get_tags_bytes()
 
2351
        try:
 
2352
            response = self._call('Branch.get_tags_bytes', self._remote_path())
 
2353
        except errors.UnknownSmartMethod:
 
2354
            medium._remember_remote_is_before((1, 13))
 
2355
            return self._vfs_get_tags_bytes()
 
2356
        return response[0]
 
2357
 
 
2358
    def _vfs_set_tags_bytes(self, bytes):
 
2359
        self._ensure_real()
 
2360
        return self._real_branch._set_tags_bytes(bytes)
 
2361
 
 
2362
    def _set_tags_bytes(self, bytes):
 
2363
        medium = self._client._medium
 
2364
        if medium._is_remote_before((1, 18)):
 
2365
            self._vfs_set_tags_bytes(bytes)
 
2366
            return
 
2367
        try:
 
2368
            args = (
 
2369
                self._remote_path(), self._lock_token, self._repo_lock_token)
 
2370
            response = self._call_with_body_bytes(
 
2371
                'Branch.set_tags_bytes', args, bytes)
 
2372
        except errors.UnknownSmartMethod:
 
2373
            medium._remember_remote_is_before((1, 18))
 
2374
            self._vfs_set_tags_bytes(bytes)
1421
2375
 
1422
2376
    def lock_read(self):
1423
2377
        self.repository.lock_read()
1424
2378
        if not self._lock_mode:
 
2379
            self._note_lock('r')
1425
2380
            self._lock_mode = 'r'
1426
2381
            self._lock_count = 1
1427
2382
            if self._real_branch is not None:
1436
2391
            branch_token = token
1437
2392
            repo_token = self.repository.lock_write()
1438
2393
            self.repository.unlock()
1439
 
        try:
1440
 
            response = self._client.call(
1441
 
                'Branch.lock_write', self._remote_path(),
1442
 
                branch_token, repo_token or '')
1443
 
        except errors.ErrorFromSmartServer, err:
1444
 
            self._translate_error(err, token=token)
 
2394
        err_context = {'token': token}
 
2395
        response = self._call(
 
2396
            'Branch.lock_write', self._remote_path(), branch_token,
 
2397
            repo_token or '', **err_context)
1445
2398
        if response[0] != 'ok':
1446
2399
            raise errors.UnexpectedSmartServerResponse(response)
1447
2400
        ok, branch_token, repo_token = response
1448
2401
        return branch_token, repo_token
1449
 
            
 
2402
 
1450
2403
    def lock_write(self, token=None):
1451
2404
        if not self._lock_mode:
 
2405
            self._note_lock('w')
1452
2406
            # Lock the branch and repo in one remote call.
1453
2407
            remote_tokens = self._remote_lock_write(token)
1454
2408
            self._lock_token, self._repo_lock_token = remote_tokens
1481
2435
        return self._lock_token or None
1482
2436
 
1483
2437
    def _unlock(self, branch_token, repo_token):
1484
 
        try:
1485
 
            response = self._client.call('Branch.unlock', self._remote_path(), branch_token,
1486
 
                                         repo_token or '')
1487
 
        except errors.ErrorFromSmartServer, err:
1488
 
            self._translate_error(err, token=str((branch_token, repo_token)))
 
2438
        err_context = {'token': str((branch_token, repo_token))}
 
2439
        response = self._call(
 
2440
            'Branch.unlock', self._remote_path(), branch_token,
 
2441
            repo_token or '', **err_context)
1489
2442
        if response == ('ok',):
1490
2443
            return
1491
2444
        raise errors.UnexpectedSmartServerResponse(response)
1492
2445
 
 
2446
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1493
2447
    def unlock(self):
1494
2448
        try:
1495
2449
            self._lock_count -= 1
1508
2462
                    self._real_branch.unlock()
1509
2463
                if mode != 'w':
1510
2464
                    # Only write-locked branched need to make a remote method
1511
 
                    # call to perfom the unlock.
 
2465
                    # call to perform the unlock.
1512
2466
                    return
1513
2467
                if not self._lock_token:
1514
2468
                    raise AssertionError('Locked, but no token!')
1535
2489
            raise NotImplementedError(self.dont_leave_lock_in_place)
1536
2490
        self._leave_lock = False
1537
2491
 
 
2492
    @needs_read_lock
 
2493
    def get_rev_id(self, revno, history=None):
 
2494
        if revno == 0:
 
2495
            return _mod_revision.NULL_REVISION
 
2496
        last_revision_info = self.last_revision_info()
 
2497
        ok, result = self.repository.get_rev_id_for_revno(
 
2498
            revno, last_revision_info)
 
2499
        if ok:
 
2500
            return result
 
2501
        missing_parent = result[1]
 
2502
        # Either the revision named by the server is missing, or its parent
 
2503
        # is.  Call get_parent_map to determine which, so that we report a
 
2504
        # useful error.
 
2505
        parent_map = self.repository.get_parent_map([missing_parent])
 
2506
        if missing_parent in parent_map:
 
2507
            missing_parent = parent_map[missing_parent]
 
2508
        raise errors.RevisionNotPresent(missing_parent, self.repository)
 
2509
 
1538
2510
    def _last_revision_info(self):
1539
 
        response = self._client.call('Branch.last_revision_info', self._remote_path())
 
2511
        response = self._call('Branch.last_revision_info', self._remote_path())
1540
2512
        if response[0] != 'ok':
1541
2513
            raise SmartProtocolError('unexpected response code %s' % (response,))
1542
2514
        revno = int(response[1])
1545
2517
 
1546
2518
    def _gen_revision_history(self):
1547
2519
        """See Branch._gen_revision_history()."""
1548
 
        response_tuple, response_handler = self._client.call_expecting_body(
 
2520
        if self._is_stacked:
 
2521
            self._ensure_real()
 
2522
            return self._real_branch._gen_revision_history()
 
2523
        response_tuple, response_handler = self._call_expecting_body(
1549
2524
            'Branch.revision_history', self._remote_path())
1550
2525
        if response_tuple[0] != 'ok':
1551
2526
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1559
2534
 
1560
2535
    def _set_last_revision_descendant(self, revision_id, other_branch,
1561
2536
            allow_diverged=False, allow_overwrite_descendant=False):
1562
 
        try:
1563
 
            response = self._client.call('Branch.set_last_revision_ex',
1564
 
                self._remote_path(), self._lock_token, self._repo_lock_token, revision_id,
1565
 
                int(allow_diverged), int(allow_overwrite_descendant))
1566
 
        except errors.ErrorFromSmartServer, err:
1567
 
            self._translate_error(err, other_branch=other_branch)
 
2537
        # This performs additional work to meet the hook contract; while its
 
2538
        # undesirable, we have to synthesise the revno to call the hook, and
 
2539
        # not calling the hook is worse as it means changes can't be prevented.
 
2540
        # Having calculated this though, we can't just call into
 
2541
        # set_last_revision_info as a simple call, because there is a set_rh
 
2542
        # hook that some folk may still be using.
 
2543
        old_revno, old_revid = self.last_revision_info()
 
2544
        history = self._lefthand_history(revision_id)
 
2545
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
 
2546
        err_context = {'other_branch': other_branch}
 
2547
        response = self._call('Branch.set_last_revision_ex',
 
2548
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2549
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
2550
            **err_context)
1568
2551
        self._clear_cached_state()
1569
2552
        if len(response) != 3 and response[0] != 'ok':
1570
2553
            raise errors.UnexpectedSmartServerResponse(response)
1571
2554
        new_revno, new_revision_id = response[1:]
1572
2555
        self._last_revision_info_cache = new_revno, new_revision_id
 
2556
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1573
2557
        if self._real_branch is not None:
1574
2558
            cache = new_revno, new_revision_id
1575
2559
            self._real_branch._last_revision_info_cache = cache
1576
2560
 
1577
2561
    def _set_last_revision(self, revision_id):
 
2562
        old_revno, old_revid = self.last_revision_info()
 
2563
        # This performs additional work to meet the hook contract; while its
 
2564
        # undesirable, we have to synthesise the revno to call the hook, and
 
2565
        # not calling the hook is worse as it means changes can't be prevented.
 
2566
        # Having calculated this though, we can't just call into
 
2567
        # set_last_revision_info as a simple call, because there is a set_rh
 
2568
        # hook that some folk may still be using.
 
2569
        history = self._lefthand_history(revision_id)
 
2570
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1578
2571
        self._clear_cached_state()
1579
 
        try:
1580
 
            response = self._client.call('Branch.set_last_revision',
1581
 
                self._remote_path(), self._lock_token, self._repo_lock_token, revision_id)
1582
 
        except errors.ErrorFromSmartServer, err:
1583
 
            self._translate_error(err)
 
2572
        response = self._call('Branch.set_last_revision',
 
2573
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2574
            revision_id)
1584
2575
        if response != ('ok',):
1585
2576
            raise errors.UnexpectedSmartServerResponse(response)
 
2577
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1586
2578
 
1587
2579
    @needs_write_lock
1588
2580
    def set_revision_history(self, rev_history):
1594
2586
        else:
1595
2587
            rev_id = rev_history[-1]
1596
2588
        self._set_last_revision(rev_id)
 
2589
        for hook in branch.Branch.hooks['set_rh']:
 
2590
            hook(self, rev_history)
1597
2591
        self._cache_revision_history(rev_history)
1598
2592
 
1599
 
    def get_parent(self):
1600
 
        self._ensure_real()
1601
 
        return self._real_branch.get_parent()
1602
 
        
1603
 
    def set_parent(self, url):
1604
 
        self._ensure_real()
1605
 
        return self._real_branch.set_parent(url)
1606
 
        
1607
 
    def set_stacked_on_url(self, stacked_location):
1608
 
        """Set the URL this branch is stacked against.
1609
 
 
1610
 
        :raises UnstackableBranchFormat: If the branch does not support
1611
 
            stacking.
1612
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1613
 
            stacking.
1614
 
        """
1615
 
        self._ensure_real()
1616
 
        return self._real_branch.set_stacked_on_url(stacked_location)
1617
 
 
1618
 
    def sprout(self, to_bzrdir, revision_id=None):
1619
 
        # Like Branch.sprout, except that it sprouts a branch in the default
1620
 
        # format, because RemoteBranches can't be created at arbitrary URLs.
1621
 
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1622
 
        # to_bzrdir.create_branch...
1623
 
        self._ensure_real()
1624
 
        result = self._real_branch._format.initialize(to_bzrdir)
1625
 
        self.copy_content_into(result, revision_id=revision_id)
1626
 
        result.set_parent(self.bzrdir.root_transport.base)
1627
 
        return result
 
2593
    def _get_parent_location(self):
 
2594
        medium = self._client._medium
 
2595
        if medium._is_remote_before((1, 13)):
 
2596
            return self._vfs_get_parent_location()
 
2597
        try:
 
2598
            response = self._call('Branch.get_parent', self._remote_path())
 
2599
        except errors.UnknownSmartMethod:
 
2600
            medium._remember_remote_is_before((1, 13))
 
2601
            return self._vfs_get_parent_location()
 
2602
        if len(response) != 1:
 
2603
            raise errors.UnexpectedSmartServerResponse(response)
 
2604
        parent_location = response[0]
 
2605
        if parent_location == '':
 
2606
            return None
 
2607
        return parent_location
 
2608
 
 
2609
    def _vfs_get_parent_location(self):
 
2610
        self._ensure_real()
 
2611
        return self._real_branch._get_parent_location()
 
2612
 
 
2613
    def _set_parent_location(self, url):
 
2614
        medium = self._client._medium
 
2615
        if medium._is_remote_before((1, 15)):
 
2616
            return self._vfs_set_parent_location(url)
 
2617
        try:
 
2618
            call_url = url or ''
 
2619
            if type(call_url) is not str:
 
2620
                raise AssertionError('url must be a str or None (%s)' % url)
 
2621
            response = self._call('Branch.set_parent_location',
 
2622
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2623
                call_url)
 
2624
        except errors.UnknownSmartMethod:
 
2625
            medium._remember_remote_is_before((1, 15))
 
2626
            return self._vfs_set_parent_location(url)
 
2627
        if response != ():
 
2628
            raise errors.UnexpectedSmartServerResponse(response)
 
2629
 
 
2630
    def _vfs_set_parent_location(self, url):
 
2631
        self._ensure_real()
 
2632
        return self._real_branch._set_parent_location(url)
1628
2633
 
1629
2634
    @needs_write_lock
1630
2635
    def pull(self, source, overwrite=False, stop_revision=None,
1652
2657
 
1653
2658
    @needs_write_lock
1654
2659
    def set_last_revision_info(self, revno, revision_id):
 
2660
        # XXX: These should be returned by the set_last_revision_info verb
 
2661
        old_revno, old_revid = self.last_revision_info()
 
2662
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
1655
2663
        revision_id = ensure_null(revision_id)
1656
2664
        try:
1657
 
            response = self._client.call('Branch.set_last_revision_info',
1658
 
                self._remote_path(), self._lock_token, self._repo_lock_token, str(revno), revision_id)
 
2665
            response = self._call('Branch.set_last_revision_info',
 
2666
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2667
                str(revno), revision_id)
1659
2668
        except errors.UnknownSmartMethod:
1660
2669
            self._ensure_real()
1661
2670
            self._clear_cached_state_of_remote_branch_only()
1662
2671
            self._real_branch.set_last_revision_info(revno, revision_id)
1663
2672
            self._last_revision_info_cache = revno, revision_id
1664
2673
            return
1665
 
        except errors.ErrorFromSmartServer, err:
1666
 
            self._translate_error(err)
1667
2674
        if response == ('ok',):
1668
2675
            self._clear_cached_state()
1669
2676
            self._last_revision_info_cache = revno, revision_id
 
2677
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1670
2678
            # Update the _real_branch's cache too.
1671
2679
            if self._real_branch is not None:
1672
2680
                cache = self._last_revision_info_cache
1679
2687
                                  other_branch=None):
1680
2688
        medium = self._client._medium
1681
2689
        if not medium._is_remote_before((1, 6)):
 
2690
            # Use a smart method for 1.6 and above servers
1682
2691
            try:
1683
2692
                self._set_last_revision_descendant(revision_id, other_branch,
1684
2693
                    allow_diverged=True, allow_overwrite_descendant=True)
1686
2695
            except errors.UnknownSmartMethod:
1687
2696
                medium._remember_remote_is_before((1, 6))
1688
2697
        self._clear_cached_state_of_remote_branch_only()
1689
 
        self._ensure_real()
1690
 
        self._real_branch.generate_revision_history(
1691
 
            revision_id, last_rev=last_rev, other_branch=other_branch)
1692
 
 
1693
 
    @property
1694
 
    def tags(self):
1695
 
        self._ensure_real()
1696
 
        return self._real_branch.tags
 
2698
        self.set_revision_history(self._lefthand_history(revision_id,
 
2699
            last_rev=last_rev,other_branch=other_branch))
1697
2700
 
1698
2701
    def set_push_location(self, location):
1699
2702
        self._ensure_real()
1700
2703
        return self._real_branch.set_push_location(location)
1701
2704
 
1702
 
    @needs_write_lock
1703
 
    def update_revisions(self, other, stop_revision=None, overwrite=False,
1704
 
                         graph=None):
1705
 
        """See Branch.update_revisions."""
1706
 
        other.lock_read()
 
2705
 
 
2706
class RemoteConfig(object):
 
2707
    """A Config that reads and writes from smart verbs.
 
2708
 
 
2709
    It is a low-level object that considers config data to be name/value pairs
 
2710
    that may be associated with a section. Assigning meaning to the these
 
2711
    values is done at higher levels like bzrlib.config.TreeConfig.
 
2712
    """
 
2713
 
 
2714
    def get_option(self, name, section=None, default=None):
 
2715
        """Return the value associated with a named option.
 
2716
 
 
2717
        :param name: The name of the value
 
2718
        :param section: The section the option is in (if any)
 
2719
        :param default: The value to return if the value is not set
 
2720
        :return: The value or default value
 
2721
        """
1707
2722
        try:
1708
 
            if stop_revision is None:
1709
 
                stop_revision = other.last_revision()
1710
 
                if revision.is_null(stop_revision):
1711
 
                    # if there are no commits, we're done.
1712
 
                    return
1713
 
            self.fetch(other, stop_revision)
1714
 
 
1715
 
            if overwrite:
1716
 
                # Just unconditionally set the new revision.  We don't care if
1717
 
                # the branches have diverged.
1718
 
                self._set_last_revision(stop_revision)
 
2723
            configobj = self._get_configobj()
 
2724
            if section is None:
 
2725
                section_obj = configobj
1719
2726
            else:
1720
 
                medium = self._client._medium
1721
 
                if not medium._is_remote_before((1, 6)):
1722
 
                    try:
1723
 
                        self._set_last_revision_descendant(stop_revision, other)
1724
 
                        return
1725
 
                    except errors.UnknownSmartMethod:
1726
 
                        medium._remember_remote_is_before((1, 6))
1727
 
                # Fallback for pre-1.6 servers: check for divergence
1728
 
                # client-side, then do _set_last_revision.
1729
 
                last_rev = revision.ensure_null(self.last_revision())
1730
 
                if graph is None:
1731
 
                    graph = self.repository.get_graph()
1732
 
                if self._check_if_descendant_or_diverged(
1733
 
                        stop_revision, last_rev, graph, other):
1734
 
                    # stop_revision is a descendant of last_rev, but we aren't
1735
 
                    # overwriting, so we're done.
1736
 
                    return
1737
 
                self._set_last_revision(stop_revision)
1738
 
        finally:
1739
 
            other.unlock()
 
2727
                try:
 
2728
                    section_obj = configobj[section]
 
2729
                except KeyError:
 
2730
                    return default
 
2731
            return section_obj.get(name, default)
 
2732
        except errors.UnknownSmartMethod:
 
2733
            return self._vfs_get_option(name, section, default)
 
2734
 
 
2735
    def _response_to_configobj(self, response):
 
2736
        if len(response[0]) and response[0][0] != 'ok':
 
2737
            raise errors.UnexpectedSmartServerResponse(response)
 
2738
        lines = response[1].read_body_bytes().splitlines()
 
2739
        return config.ConfigObj(lines, encoding='utf-8')
 
2740
 
 
2741
 
 
2742
class RemoteBranchConfig(RemoteConfig):
 
2743
    """A RemoteConfig for Branches."""
 
2744
 
 
2745
    def __init__(self, branch):
 
2746
        self._branch = branch
 
2747
 
 
2748
    def _get_configobj(self):
 
2749
        path = self._branch._remote_path()
 
2750
        response = self._branch._client.call_expecting_body(
 
2751
            'Branch.get_config_file', path)
 
2752
        return self._response_to_configobj(response)
 
2753
 
 
2754
    def set_option(self, value, name, section=None):
 
2755
        """Set the value associated with a named option.
 
2756
 
 
2757
        :param value: The value to set
 
2758
        :param name: The name of the value to set
 
2759
        :param section: The section the option is in (if any)
 
2760
        """
 
2761
        medium = self._branch._client._medium
 
2762
        if medium._is_remote_before((1, 14)):
 
2763
            return self._vfs_set_option(value, name, section)
 
2764
        try:
 
2765
            path = self._branch._remote_path()
 
2766
            response = self._branch._client.call('Branch.set_config_option',
 
2767
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2768
                value.encode('utf8'), name, section or '')
 
2769
        except errors.UnknownSmartMethod:
 
2770
            medium._remember_remote_is_before((1, 14))
 
2771
            return self._vfs_set_option(value, name, section)
 
2772
        if response != ():
 
2773
            raise errors.UnexpectedSmartServerResponse(response)
 
2774
 
 
2775
    def _real_object(self):
 
2776
        self._branch._ensure_real()
 
2777
        return self._branch._real_branch
 
2778
 
 
2779
    def _vfs_set_option(self, value, name, section=None):
 
2780
        return self._real_object()._get_config().set_option(
 
2781
            value, name, section)
 
2782
 
 
2783
 
 
2784
class RemoteBzrDirConfig(RemoteConfig):
 
2785
    """A RemoteConfig for BzrDirs."""
 
2786
 
 
2787
    def __init__(self, bzrdir):
 
2788
        self._bzrdir = bzrdir
 
2789
 
 
2790
    def _get_configobj(self):
 
2791
        medium = self._bzrdir._client._medium
 
2792
        verb = 'BzrDir.get_config_file'
 
2793
        if medium._is_remote_before((1, 15)):
 
2794
            raise errors.UnknownSmartMethod(verb)
 
2795
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
 
2796
        response = self._bzrdir._call_expecting_body(
 
2797
            verb, path)
 
2798
        return self._response_to_configobj(response)
 
2799
 
 
2800
    def _vfs_get_option(self, name, section, default):
 
2801
        return self._real_object()._get_config().get_option(
 
2802
            name, section, default)
 
2803
 
 
2804
    def set_option(self, value, name, section=None):
 
2805
        """Set the value associated with a named option.
 
2806
 
 
2807
        :param value: The value to set
 
2808
        :param name: The name of the value to set
 
2809
        :param section: The section the option is in (if any)
 
2810
        """
 
2811
        return self._real_object()._get_config().set_option(
 
2812
            value, name, section)
 
2813
 
 
2814
    def _real_object(self):
 
2815
        self._bzrdir._ensure_real()
 
2816
        return self._bzrdir._real_bzrdir
 
2817
 
1740
2818
 
1741
2819
 
1742
2820
def _extract_tar(tar, to_dir):
1757
2835
      - bzrdir
1758
2836
      - token
1759
2837
      - other_branch
 
2838
      - path
1760
2839
 
1761
2840
    If the error from the server doesn't match a known pattern, then
1762
2841
    UnknownErrorFromSmartServer is raised.
1764
2843
    def find(name):
1765
2844
        try:
1766
2845
            return context[name]
1767
 
        except KeyError, keyErr:
1768
 
            mutter('Missing key %r in context %r', keyErr.args[0], context)
 
2846
        except KeyError, key_err:
 
2847
            mutter('Missing key %r in context %r', key_err.args[0], context)
1769
2848
            raise err
1770
 
    if err.error_verb == 'NoSuchRevision':
 
2849
    def get_path():
 
2850
        """Get the path from the context if present, otherwise use first error
 
2851
        arg.
 
2852
        """
 
2853
        try:
 
2854
            return context['path']
 
2855
        except KeyError, key_err:
 
2856
            try:
 
2857
                return err.error_args[0]
 
2858
            except IndexError, idx_err:
 
2859
                mutter(
 
2860
                    'Missing key %r in context %r', key_err.args[0], context)
 
2861
                raise err
 
2862
 
 
2863
    if err.error_verb == 'IncompatibleRepositories':
 
2864
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2865
            err.error_args[1], err.error_args[2])
 
2866
    elif err.error_verb == 'NoSuchRevision':
1771
2867
        raise NoSuchRevision(find('branch'), err.error_args[0])
1772
2868
    elif err.error_verb == 'nosuchrevision':
1773
2869
        raise NoSuchRevision(find('repository'), err.error_args[0])
1774
 
    elif err.error_tuple == ('nobranch',):
1775
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
 
2870
    elif err.error_verb == 'nobranch':
 
2871
        if len(err.error_args) >= 1:
 
2872
            extra = err.error_args[0]
 
2873
        else:
 
2874
            extra = None
 
2875
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2876
            detail=extra)
1776
2877
    elif err.error_verb == 'norepository':
1777
2878
        raise errors.NoRepositoryPresent(find('bzrdir'))
1778
2879
    elif err.error_verb == 'LockContention':
1793
2894
        raise errors.UnstackableRepositoryFormat(*err.error_args)
1794
2895
    elif err.error_verb == 'NotStacked':
1795
2896
        raise errors.NotStacked(branch=find('branch'))
 
2897
    elif err.error_verb == 'PermissionDenied':
 
2898
        path = get_path()
 
2899
        if len(err.error_args) >= 2:
 
2900
            extra = err.error_args[1]
 
2901
        else:
 
2902
            extra = None
 
2903
        raise errors.PermissionDenied(path, extra=extra)
 
2904
    elif err.error_verb == 'ReadError':
 
2905
        path = get_path()
 
2906
        raise errors.ReadError(path)
 
2907
    elif err.error_verb == 'NoSuchFile':
 
2908
        path = get_path()
 
2909
        raise errors.NoSuchFile(path)
 
2910
    elif err.error_verb == 'FileExists':
 
2911
        raise errors.FileExists(err.error_args[0])
 
2912
    elif err.error_verb == 'DirectoryNotEmpty':
 
2913
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2914
    elif err.error_verb == 'ShortReadvError':
 
2915
        args = err.error_args
 
2916
        raise errors.ShortReadvError(
 
2917
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2918
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2919
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2920
        val = err.error_args[1]
 
2921
        start = int(err.error_args[2])
 
2922
        end = int(err.error_args[3])
 
2923
        reason = str(err.error_args[4]) # reason must always be a string
 
2924
        if val.startswith('u:'):
 
2925
            val = val[2:].decode('utf-8')
 
2926
        elif val.startswith('s:'):
 
2927
            val = val[2:].decode('base64')
 
2928
        if err.error_verb == 'UnicodeDecodeError':
 
2929
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2930
        elif err.error_verb == 'UnicodeEncodeError':
 
2931
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
2932
    elif err.error_verb == 'ReadOnlyError':
 
2933
        raise errors.TransportNotPossible('readonly transport')
1796
2934
    raise errors.UnknownErrorFromSmartServer(err)