/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/smart/request.py

  • Committer: John Arbash Meinel
  • Date: 2009-04-21 23:54:16 UTC
  • mto: (4300.1.7 groupcompress_info)
  • mto: This revision was merged to the branch mainline in revision 4301.
  • Revision ID: john@arbash-meinel.com-20090421235416-f0cz6ilf5cufbugi
Fix bug #364900, properly remove the 64kB that was just encoded in the copy.
Also, stop supporting None as a copy length in 'encode_copy_instruction'.
It was only used by the test suite, and it is good to pull that sort of thing out of
production code. (Besides, setting the copy to 64kB has the same effect.)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Infrastructure for server-side request handlers.
 
18
 
 
19
Interesting module attributes:
 
20
    * The request_handlers registry maps verb names to SmartServerRequest
 
21
      classes.
 
22
    * The jail_info threading.local() object is used to prevent accidental
 
23
      opening of BzrDirs outside of the backing transport, or any other
 
24
      transports placed in jail_info.transports.  The jail_info is reset on
 
25
      every call into a request handler (which can happen an arbitrary number
 
26
      of times during a request).
 
27
"""
 
28
 
 
29
# XXX: The class names are a little confusing: the protocol will instantiate a
 
30
# SmartServerRequestHandler, whose dispatch_command method creates an instance
 
31
# of a SmartServerRequest subclass.
 
32
 
 
33
 
 
34
import tempfile
 
35
import threading
 
36
 
 
37
from bzrlib import (
 
38
    bzrdir,
 
39
    errors,
 
40
    registry,
 
41
    revision,
 
42
    trace,
 
43
    urlutils,
 
44
    )
 
45
from bzrlib.lazy_import import lazy_import
 
46
lazy_import(globals(), """
 
47
from bzrlib.bundle import serializer
 
48
""")
 
49
 
 
50
 
 
51
jail_info = threading.local()
 
52
jail_info.transports = None
 
53
 
 
54
 
 
55
def _install_hook():
 
56
    bzrdir.BzrDir.hooks.install_named_hook(
 
57
        'pre_open', _pre_open_hook, 'checking server jail')
 
58
 
 
59
 
 
60
def _pre_open_hook(transport):
 
61
    allowed_transports = getattr(jail_info, 'transports', None)
 
62
    if allowed_transports is None:
 
63
        return
 
64
    abspath = transport.base
 
65
    for allowed_transport in allowed_transports:
 
66
        try:
 
67
            allowed_transport.relpath(abspath)
 
68
        except errors.PathNotChild:
 
69
            continue
 
70
        else:
 
71
            return
 
72
    raise errors.BzrError('jail break: %r' % (abspath,))
 
73
 
 
74
 
 
75
_install_hook()
 
76
 
 
77
 
 
78
class SmartServerRequest(object):
 
79
    """Base class for request handlers.
 
80
 
 
81
    To define a new request, subclass this class and override the `do` method
 
82
    (and if appropriate, `do_body` as well).  Request implementors should take
 
83
    care to call `translate_client_path` and `transport_from_client_path` as
 
84
    appropriate when dealing with paths received from the client.
 
85
    """
 
86
    # XXX: rename this class to BaseSmartServerRequestHandler ?  A request
 
87
    # *handler* is a different concept to the request.
 
88
 
 
89
    def __init__(self, backing_transport, root_client_path='/'):
 
90
        """Constructor.
 
91
 
 
92
        :param backing_transport: the base transport to be used when performing
 
93
            this request.
 
94
        :param root_client_path: the client path that maps to the root of
 
95
            backing_transport.  This is used to interpret relpaths received
 
96
            from the client.  Clients will not be able to refer to paths above
 
97
            this root.  If root_client_path is None, then no translation will
 
98
            be performed on client paths.  Default is '/'.
 
99
        """
 
100
        self._backing_transport = backing_transport
 
101
        if root_client_path is not None:
 
102
            if not root_client_path.startswith('/'):
 
103
                root_client_path = '/' + root_client_path
 
104
            if not root_client_path.endswith('/'):
 
105
                root_client_path += '/'
 
106
        self._root_client_path = root_client_path
 
107
        self._body_chunks = []
 
108
 
 
109
    def _check_enabled(self):
 
110
        """Raises DisabledMethod if this method is disabled."""
 
111
        pass
 
112
 
 
113
    def do(self, *args):
 
114
        """Mandatory extension point for SmartServerRequest subclasses.
 
115
 
 
116
        Subclasses must implement this.
 
117
 
 
118
        This should return a SmartServerResponse if this command expects to
 
119
        receive no body.
 
120
        """
 
121
        raise NotImplementedError(self.do)
 
122
 
 
123
    def execute(self, *args):
 
124
        """Public entry point to execute this request.
 
125
 
 
126
        It will return a SmartServerResponse if the command does not expect a
 
127
        body.
 
128
 
 
129
        :param *args: the arguments of the request.
 
130
        """
 
131
        self._check_enabled()
 
132
        return self.do(*args)
 
133
 
 
134
    def do_body(self, body_bytes):
 
135
        """Called if the client sends a body with the request.
 
136
 
 
137
        The do() method is still called, and must have returned None.
 
138
 
 
139
        Must return a SmartServerResponse.
 
140
        """
 
141
        if body_bytes != '':
 
142
            raise errors.SmartProtocolError('Request does not expect a body')
 
143
 
 
144
    def do_chunk(self, chunk_bytes):
 
145
        """Called with each body chunk if the request has a streamed body.
 
146
 
 
147
        The do() method is still called, and must have returned None.
 
148
        """
 
149
        self._body_chunks.append(chunk_bytes)
 
150
 
 
151
    def do_end(self):
 
152
        """Called when the end of the request has been received."""
 
153
        body_bytes = ''.join(self._body_chunks)
 
154
        self._body_chunks = None
 
155
        return self.do_body(body_bytes)
 
156
 
 
157
    def setup_jail(self):
 
158
        jail_info.transports = [self._backing_transport]
 
159
 
 
160
    def teardown_jail(self):
 
161
        jail_info.transports = None
 
162
 
 
163
    def translate_client_path(self, client_path):
 
164
        """Translate a path received from a network client into a local
 
165
        relpath.
 
166
 
 
167
        All paths received from the client *must* be translated.
 
168
 
 
169
        :param client_path: the path from the client.
 
170
        :returns: a relpath that may be used with self._backing_transport
 
171
            (unlike the untranslated client_path, which must not be used with
 
172
            the backing transport).
 
173
        """
 
174
        if self._root_client_path is None:
 
175
            # no translation necessary!
 
176
            return client_path
 
177
        if not client_path.startswith('/'):
 
178
            client_path = '/' + client_path
 
179
        if client_path.startswith(self._root_client_path):
 
180
            path = client_path[len(self._root_client_path):]
 
181
            relpath = urlutils.joinpath('/', path)
 
182
            if not relpath.startswith('/'):
 
183
                raise ValueError(relpath)
 
184
            return '.' + relpath
 
185
        else:
 
186
            raise errors.PathNotChild(client_path, self._root_client_path)
 
187
 
 
188
    def transport_from_client_path(self, client_path):
 
189
        """Get a backing transport corresponding to the location referred to by
 
190
        a network client.
 
191
 
 
192
        :seealso: translate_client_path
 
193
        :returns: a transport cloned from self._backing_transport
 
194
        """
 
195
        relpath = self.translate_client_path(client_path)
 
196
        return self._backing_transport.clone(relpath)
 
197
 
 
198
 
 
199
class SmartServerResponse(object):
 
200
    """A response to a client request.
 
201
 
 
202
    This base class should not be used. Instead use
 
203
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
 
204
    """
 
205
 
 
206
    def __init__(self, args, body=None, body_stream=None):
 
207
        """Constructor.
 
208
 
 
209
        :param args: tuple of response arguments.
 
210
        :param body: string of a response body.
 
211
        :param body_stream: iterable of bytestrings to be streamed to the
 
212
            client.
 
213
        """
 
214
        self.args = args
 
215
        if body is not None and body_stream is not None:
 
216
            raise errors.BzrError(
 
217
                "'body' and 'body_stream' are mutually exclusive.")
 
218
        self.body = body
 
219
        self.body_stream = body_stream
 
220
 
 
221
    def __eq__(self, other):
 
222
        if other is None:
 
223
            return False
 
224
        return (other.args == self.args and
 
225
                other.body == self.body and
 
226
                other.body_stream is self.body_stream)
 
227
 
 
228
    def __repr__(self):
 
229
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
 
230
            self.args, self.body)
 
231
 
 
232
 
 
233
class FailedSmartServerResponse(SmartServerResponse):
 
234
    """A SmartServerResponse for a request which failed."""
 
235
 
 
236
    def is_successful(self):
 
237
        """FailedSmartServerResponse are not successful."""
 
238
        return False
 
239
 
 
240
 
 
241
class SuccessfulSmartServerResponse(SmartServerResponse):
 
242
    """A SmartServerResponse for a successfully completed request."""
 
243
 
 
244
    def is_successful(self):
 
245
        """SuccessfulSmartServerResponse are successful."""
 
246
        return True
 
247
 
 
248
 
 
249
class SmartServerRequestHandler(object):
 
250
    """Protocol logic for smart server.
 
251
 
 
252
    This doesn't handle serialization at all, it just processes requests and
 
253
    creates responses.
 
254
    """
 
255
 
 
256
    # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler
 
257
    # not contain encoding or decoding logic to allow the wire protocol to vary
 
258
    # from the object protocol: we will want to tweak the wire protocol separate
 
259
    # from the object model, and ideally we will be able to do that without
 
260
    # having a SmartServerRequestHandler subclass for each wire protocol, rather
 
261
    # just a Protocol subclass.
 
262
 
 
263
    # TODO: Better way of representing the body for commands that take it,
 
264
    # and allow it to be streamed into the server.
 
265
 
 
266
    def __init__(self, backing_transport, commands, root_client_path):
 
267
        """Constructor.
 
268
 
 
269
        :param backing_transport: a Transport to handle requests for.
 
270
        :param commands: a registry mapping command names to SmartServerRequest
 
271
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
 
272
        """
 
273
        self._backing_transport = backing_transport
 
274
        self._root_client_path = root_client_path
 
275
        self._commands = commands
 
276
        self.response = None
 
277
        self.finished_reading = False
 
278
        self._command = None
 
279
 
 
280
    def accept_body(self, bytes):
 
281
        """Accept body data."""
 
282
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
 
283
 
 
284
    def end_of_body(self):
 
285
        """No more body data will be received."""
 
286
        self._run_handler_code(self._command.do_end, (), {})
 
287
        # cannot read after this.
 
288
        self.finished_reading = True
 
289
 
 
290
    def dispatch_command(self, cmd, args):
 
291
        """Deprecated compatibility method.""" # XXX XXX
 
292
        try:
 
293
            command = self._commands.get(cmd)
 
294
        except LookupError:
 
295
            raise errors.UnknownSmartMethod(cmd)
 
296
        self._command = command(self._backing_transport, self._root_client_path)
 
297
        self._run_handler_code(self._command.execute, args, {})
 
298
 
 
299
    def _run_handler_code(self, callable, args, kwargs):
 
300
        """Run some handler specific code 'callable'.
 
301
 
 
302
        If a result is returned, it is considered to be the commands response,
 
303
        and finished_reading is set true, and its assigned to self.response.
 
304
 
 
305
        Any exceptions caught are translated and a response object created
 
306
        from them.
 
307
        """
 
308
        result = self._call_converting_errors(callable, args, kwargs)
 
309
 
 
310
        if result is not None:
 
311
            self.response = result
 
312
            self.finished_reading = True
 
313
 
 
314
    def _call_converting_errors(self, callable, args, kwargs):
 
315
        """Call callable converting errors to Response objects."""
 
316
        # XXX: most of this error conversion is VFS-related, and thus ought to
 
317
        # be in SmartServerVFSRequestHandler somewhere.
 
318
        try:
 
319
            self._command.setup_jail()
 
320
            try:
 
321
                return callable(*args, **kwargs)
 
322
            finally:
 
323
                self._command.teardown_jail()
 
324
        except (KeyboardInterrupt, SystemExit):
 
325
            raise
 
326
        except Exception, err:
 
327
            err_struct = _translate_error(err)
 
328
            return FailedSmartServerResponse(err_struct)
 
329
 
 
330
    def headers_received(self, headers):
 
331
        # Just a no-op at the moment.
 
332
        pass
 
333
 
 
334
    def args_received(self, args):
 
335
        cmd = args[0]
 
336
        args = args[1:]
 
337
        try:
 
338
            command = self._commands.get(cmd)
 
339
        except LookupError:
 
340
            raise errors.UnknownSmartMethod(cmd)
 
341
        self._command = command(self._backing_transport)
 
342
        self._run_handler_code(self._command.execute, args, {})
 
343
 
 
344
    def end_received(self):
 
345
        self._run_handler_code(self._command.do_end, (), {})
 
346
 
 
347
    def post_body_error_received(self, error_args):
 
348
        # Just a no-op at the moment.
 
349
        pass
 
350
 
 
351
 
 
352
def _translate_error(err):
 
353
    if isinstance(err, errors.NoSuchFile):
 
354
        return ('NoSuchFile', err.path)
 
355
    elif isinstance(err, errors.FileExists):
 
356
        return ('FileExists', err.path)
 
357
    elif isinstance(err, errors.DirectoryNotEmpty):
 
358
        return ('DirectoryNotEmpty', err.path)
 
359
    elif isinstance(err, errors.ShortReadvError):
 
360
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
 
361
                str(err.actual))
 
362
    elif isinstance(err, errors.UnstackableRepositoryFormat):
 
363
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
 
364
    elif isinstance(err, errors.UnstackableBranchFormat):
 
365
        return ('UnstackableBranchFormat', str(err.format), err.url)
 
366
    elif isinstance(err, errors.NotStacked):
 
367
        return ('NotStacked',)
 
368
    elif isinstance(err, UnicodeError):
 
369
        # If it is a DecodeError, than most likely we are starting
 
370
        # with a plain string
 
371
        str_or_unicode = err.object
 
372
        if isinstance(str_or_unicode, unicode):
 
373
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
 
374
            # byte) in it, so this encoding could cause broken responses.
 
375
            # Newer clients use protocol v3, so will be fine.
 
376
            val = 'u:' + str_or_unicode.encode('utf-8')
 
377
        else:
 
378
            val = 's:' + str_or_unicode.encode('base64')
 
379
        # This handles UnicodeEncodeError or UnicodeDecodeError
 
380
        return (err.__class__.__name__, err.encoding, val, str(err.start),
 
381
                str(err.end), err.reason)
 
382
    elif isinstance(err, errors.TransportNotPossible):
 
383
        if err.msg == "readonly transport":
 
384
            return ('ReadOnlyError', )
 
385
    elif isinstance(err, errors.ReadError):
 
386
        # cannot read the file
 
387
        return ('ReadError', err.path)
 
388
    elif isinstance(err, errors.PermissionDenied):
 
389
        return ('PermissionDenied', err.path, err.extra)
 
390
    elif isinstance(err, errors.TokenMismatch):
 
391
        return ('TokenMismatch', err.given_token, err.lock_token)
 
392
    elif isinstance(err, errors.LockContention):
 
393
        return ('LockContention', err.lock, err.msg)
 
394
    # Unserialisable error.  Log it, and return a generic error
 
395
    trace.log_exception_quietly()
 
396
    return ('error', str(err))
 
397
 
 
398
 
 
399
class HelloRequest(SmartServerRequest):
 
400
    """Answer a version request with the highest protocol version this server
 
401
    supports.
 
402
    """
 
403
 
 
404
    def do(self):
 
405
        return SuccessfulSmartServerResponse(('ok', '2'))
 
406
 
 
407
 
 
408
class GetBundleRequest(SmartServerRequest):
 
409
    """Get a bundle of from the null revision to the specified revision."""
 
410
 
 
411
    def do(self, path, revision_id):
 
412
        # open transport relative to our base
 
413
        t = self.transport_from_client_path(path)
 
414
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
 
415
        repo = control.open_repository()
 
416
        tmpf = tempfile.TemporaryFile()
 
417
        base_revision = revision.NULL_REVISION
 
418
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
 
419
        tmpf.seek(0)
 
420
        return SuccessfulSmartServerResponse((), tmpf.read())
 
421
 
 
422
 
 
423
class SmartServerIsReadonly(SmartServerRequest):
 
424
    # XXX: this request method belongs somewhere else.
 
425
 
 
426
    def do(self):
 
427
        if self._backing_transport.is_readonly():
 
428
            answer = 'yes'
 
429
        else:
 
430
            answer = 'no'
 
431
        return SuccessfulSmartServerResponse((answer,))
 
432
 
 
433
 
 
434
request_handlers = registry.Registry()
 
435
request_handlers.register_lazy(
 
436
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
 
437
request_handlers.register_lazy(
 
438
    'Branch.get_config_file', 'bzrlib.smart.branch',
 
439
    'SmartServerBranchGetConfigFile')
 
440
request_handlers.register_lazy(
 
441
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
 
442
request_handlers.register_lazy(
 
443
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
 
444
    'SmartServerBranchGetTagsBytes')
 
445
request_handlers.register_lazy(
 
446
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
 
447
request_handlers.register_lazy(
 
448
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
 
449
request_handlers.register_lazy(
 
450
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
 
451
request_handlers.register_lazy( 'Branch.revision_history',
 
452
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
 
453
request_handlers.register_lazy( 'Branch.set_config_option',
 
454
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
 
455
request_handlers.register_lazy( 'Branch.set_last_revision',
 
456
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
 
457
request_handlers.register_lazy(
 
458
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
 
459
    'SmartServerBranchRequestSetLastRevisionInfo')
 
460
request_handlers.register_lazy(
 
461
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
 
462
    'SmartServerBranchRequestSetLastRevisionEx')
 
463
request_handlers.register_lazy(
 
464
    'Branch.set_parent_location', 'bzrlib.smart.branch',
 
465
    'SmartServerBranchRequestSetParentLocation')
 
466
request_handlers.register_lazy(
 
467
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
 
468
request_handlers.register_lazy(
 
469
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
 
470
    'SmartServerBzrDirRequestCloningMetaDir')
 
471
request_handlers.register_lazy(
 
472
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
 
473
    'SmartServerRequestCreateBranch')
 
474
request_handlers.register_lazy(
 
475
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
 
476
    'SmartServerRequestCreateRepository')
 
477
request_handlers.register_lazy(
 
478
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
 
479
    'SmartServerRequestFindRepositoryV1')
 
480
request_handlers.register_lazy(
 
481
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
 
482
    'SmartServerRequestFindRepositoryV2')
 
483
request_handlers.register_lazy(
 
484
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
 
485
    'SmartServerRequestFindRepositoryV3')
 
486
request_handlers.register_lazy(
 
487
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
 
488
    'SmartServerBzrDirRequestConfigFile')
 
489
request_handlers.register_lazy(
 
490
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
 
491
    'SmartServerRequestInitializeBzrDir')
 
492
request_handlers.register_lazy(
 
493
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
 
494
request_handlers.register_lazy(
 
495
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
 
496
    'SmartServerRequestOpenBranch')
 
497
request_handlers.register_lazy(
 
498
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
 
499
    'SmartServerRequestOpenBranchV2')
 
500
request_handlers.register_lazy(
 
501
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
 
502
request_handlers.register_lazy(
 
503
    'get', 'bzrlib.smart.vfs', 'GetRequest')
 
504
request_handlers.register_lazy(
 
505
    'get_bundle', 'bzrlib.smart.request', 'GetBundleRequest')
 
506
request_handlers.register_lazy(
 
507
    'has', 'bzrlib.smart.vfs', 'HasRequest')
 
508
request_handlers.register_lazy(
 
509
    'hello', 'bzrlib.smart.request', 'HelloRequest')
 
510
request_handlers.register_lazy(
 
511
    'iter_files_recursive', 'bzrlib.smart.vfs', 'IterFilesRecursiveRequest')
 
512
request_handlers.register_lazy(
 
513
    'list_dir', 'bzrlib.smart.vfs', 'ListDirRequest')
 
514
request_handlers.register_lazy(
 
515
    'mkdir', 'bzrlib.smart.vfs', 'MkdirRequest')
 
516
request_handlers.register_lazy(
 
517
    'move', 'bzrlib.smart.vfs', 'MoveRequest')
 
518
request_handlers.register_lazy(
 
519
    'put', 'bzrlib.smart.vfs', 'PutRequest')
 
520
request_handlers.register_lazy(
 
521
    'put_non_atomic', 'bzrlib.smart.vfs', 'PutNonAtomicRequest')
 
522
request_handlers.register_lazy(
 
523
    'readv', 'bzrlib.smart.vfs', 'ReadvRequest')
 
524
request_handlers.register_lazy(
 
525
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
 
526
request_handlers.register_lazy(
 
527
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
 
528
    'SmartServerPackRepositoryAutopack')
 
529
request_handlers.register_lazy('Repository.gather_stats',
 
530
                               'bzrlib.smart.repository',
 
531
                               'SmartServerRepositoryGatherStats')
 
532
request_handlers.register_lazy('Repository.get_parent_map',
 
533
                               'bzrlib.smart.repository',
 
534
                               'SmartServerRepositoryGetParentMap')
 
535
request_handlers.register_lazy(
 
536
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
 
537
request_handlers.register_lazy(
 
538
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
 
539
request_handlers.register_lazy(
 
540
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
 
541
request_handlers.register_lazy(
 
542
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
 
543
request_handlers.register_lazy(
 
544
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
 
545
request_handlers.register_lazy(
 
546
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
 
547
request_handlers.register_lazy(
 
548
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
 
549
    'SmartServerRepositorySetMakeWorkingTrees')
 
550
request_handlers.register_lazy(
 
551
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
 
552
request_handlers.register_lazy(
 
553
    'Repository.get_stream', 'bzrlib.smart.repository',
 
554
    'SmartServerRepositoryGetStream')
 
555
request_handlers.register_lazy(
 
556
    'Repository.tarball', 'bzrlib.smart.repository',
 
557
    'SmartServerRepositoryTarball')
 
558
request_handlers.register_lazy(
 
559
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
 
560
request_handlers.register_lazy(
 
561
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
 
562
request_handlers.register_lazy(
 
563
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')