~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/request.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""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.
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Basic server-side logic for dealing with requests."""
32
18
 
33
19
 
34
20
import tempfile
35
 
import threading
36
21
 
37
22
from bzrlib import (
38
23
    bzrdir,
39
24
    errors,
40
25
    registry,
41
26
    revision,
42
 
    trace,
43
 
    urlutils,
44
27
    )
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.JailBreak(abspath)
73
 
 
74
 
 
75
 
_install_hook()
 
28
from bzrlib.bundle.serializer import write_bundle
76
29
 
77
30
 
78
31
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='/'):
 
32
    """Base class for request handlers."""
 
33
 
 
34
    def __init__(self, backing_transport):
90
35
        """Constructor.
91
36
 
92
37
        :param backing_transport: the base transport to be used when performing
93
38
            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
39
        """
100
40
        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
41
 
109
42
    def _check_enabled(self):
110
43
        """Raises DisabledMethod if this method is disabled."""
112
45
 
113
46
    def do(self, *args):
114
47
        """Mandatory extension point for SmartServerRequest subclasses.
115
 
 
 
48
        
116
49
        Subclasses must implement this.
117
 
 
 
50
        
118
51
        This should return a SmartServerResponse if this command expects to
119
52
        receive no body.
120
53
        """
133
66
 
134
67
    def do_body(self, body_bytes):
135
68
        """Called if the client sends a body with the request.
136
 
 
137
 
        The do() method is still called, and must have returned None.
138
 
 
 
69
        
139
70
        Must return a SmartServerResponse.
140
71
        """
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 + '/' == self._root_client_path:
180
 
            return '.'
181
 
        if client_path.startswith(self._root_client_path):
182
 
            path = client_path[len(self._root_client_path):]
183
 
            relpath = urlutils.joinpath('/', path)
184
 
            if not relpath.startswith('/'):
185
 
                raise ValueError(relpath)
186
 
            return '.' + relpath
187
 
        else:
188
 
            raise errors.PathNotChild(client_path, self._root_client_path)
189
 
 
190
 
    def transport_from_client_path(self, client_path):
191
 
        """Get a backing transport corresponding to the location referred to by
192
 
        a network client.
193
 
 
194
 
        :seealso: translate_client_path
195
 
        :returns: a transport cloned from self._backing_transport
196
 
        """
197
 
        relpath = self.translate_client_path(client_path)
198
 
        return self._backing_transport.clone(relpath)
 
72
        # TODO: if a client erroneously sends a request that shouldn't have a
 
73
        # body, what to do?  Probably SmartServerRequestHandler should catch
 
74
        # this NotImplementedError and translate it into a 'bad request' error
 
75
        # to send to the client.
 
76
        raise NotImplementedError(self.do_body)
199
77
 
200
78
 
201
79
class SmartServerResponse(object):
202
 
    """A response to a client request.
203
 
 
204
 
    This base class should not be used. Instead use
205
 
    SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate.
206
 
    """
207
 
 
208
 
    def __init__(self, args, body=None, body_stream=None):
209
 
        """Constructor.
210
 
 
211
 
        :param args: tuple of response arguments.
212
 
        :param body: string of a response body.
213
 
        :param body_stream: iterable of bytestrings to be streamed to the
214
 
            client.
215
 
        """
 
80
    """Response generated by SmartServerRequestHandler."""
 
81
 
 
82
    def __init__(self, args, body=None):
216
83
        self.args = args
217
 
        if body is not None and body_stream is not None:
218
 
            raise errors.BzrError(
219
 
                "'body' and 'body_stream' are mutually exclusive.")
220
84
        self.body = body
221
 
        self.body_stream = body_stream
222
85
 
223
86
    def __eq__(self, other):
224
87
        if other is None:
225
88
            return False
226
 
        return (other.args == self.args and
227
 
                other.body == self.body and
228
 
                other.body_stream is self.body_stream)
 
89
        return other.args == self.args and other.body == self.body
229
90
 
230
91
    def __repr__(self):
231
 
        return "<%s args=%r body=%r>" % (self.__class__.__name__,
232
 
            self.args, self.body)
233
 
 
234
 
 
235
 
class FailedSmartServerResponse(SmartServerResponse):
236
 
    """A SmartServerResponse for a request which failed."""
237
 
 
238
 
    def is_successful(self):
239
 
        """FailedSmartServerResponse are not successful."""
240
 
        return False
241
 
 
242
 
 
243
 
class SuccessfulSmartServerResponse(SmartServerResponse):
244
 
    """A SmartServerResponse for a successfully completed request."""
245
 
 
246
 
    def is_successful(self):
247
 
        """SuccessfulSmartServerResponse are successful."""
248
 
        return True
 
92
        return "<SmartServerResponse args=%r body=%r>" % (self.args, self.body)
249
93
 
250
94
 
251
95
class SmartServerRequestHandler(object):
252
96
    """Protocol logic for smart server.
253
 
 
 
97
    
254
98
    This doesn't handle serialization at all, it just processes requests and
255
99
    creates responses.
256
100
    """
265
109
    # TODO: Better way of representing the body for commands that take it,
266
110
    # and allow it to be streamed into the server.
267
111
 
268
 
    def __init__(self, backing_transport, commands, root_client_path):
 
112
    def __init__(self, backing_transport, commands):
269
113
        """Constructor.
270
114
 
271
115
        :param backing_transport: a Transport to handle requests for.
273
117
            subclasses. e.g. bzrlib.transport.smart.vfs.vfs_commands.
274
118
        """
275
119
        self._backing_transport = backing_transport
276
 
        self._root_client_path = root_client_path
277
120
        self._commands = commands
 
121
        self._body_bytes = ''
278
122
        self.response = None
279
123
        self.finished_reading = False
280
124
        self._command = None
281
125
 
282
126
    def accept_body(self, bytes):
283
127
        """Accept body data."""
284
 
        if self._command is None:
285
 
            # no active command object, so ignore the event.
286
 
            return
287
 
        self._run_handler_code(self._command.do_chunk, (bytes,), {})
288
 
 
 
128
 
 
129
        # TODO: This should be overriden for each command that desired body data
 
130
        # to handle the right format of that data, i.e. plain bytes, a bundle,
 
131
        # etc.  The deserialisation into that format should be done in the
 
132
        # Protocol object.
 
133
 
 
134
        # default fallback is to accumulate bytes.
 
135
        self._body_bytes += bytes
 
136
        
289
137
    def end_of_body(self):
290
138
        """No more body data will be received."""
291
 
        self._run_handler_code(self._command.do_end, (), {})
 
139
        self._run_handler_code(self._command.do_body, (self._body_bytes,), {})
292
140
        # cannot read after this.
293
141
        self.finished_reading = True
294
142
 
297
145
        try:
298
146
            command = self._commands.get(cmd)
299
147
        except LookupError:
300
 
            raise errors.UnknownSmartMethod(cmd)
301
 
        self._command = command(self._backing_transport, self._root_client_path)
 
148
            raise errors.SmartProtocolError("bad request %r" % (cmd,))
 
149
        self._command = command(self._backing_transport)
302
150
        self._run_handler_code(self._command.execute, args, {})
303
151
 
304
152
    def _run_handler_code(self, callable, args, kwargs):
321
169
        # XXX: most of this error conversion is VFS-related, and thus ought to
322
170
        # be in SmartServerVFSRequestHandler somewhere.
323
171
        try:
324
 
            self._command.setup_jail()
325
 
            try:
326
 
                return callable(*args, **kwargs)
327
 
            finally:
328
 
                self._command.teardown_jail()
329
 
        except (KeyboardInterrupt, SystemExit):
330
 
            raise
331
 
        except Exception, err:
332
 
            err_struct = _translate_error(err)
333
 
            return FailedSmartServerResponse(err_struct)
334
 
 
335
 
    def headers_received(self, headers):
336
 
        # Just a no-op at the moment.
337
 
        pass
338
 
 
339
 
    def args_received(self, args):
340
 
        cmd = args[0]
341
 
        args = args[1:]
342
 
        try:
343
 
            command = self._commands.get(cmd)
344
 
        except LookupError:
345
 
            raise errors.UnknownSmartMethod(cmd)
346
 
        self._command = command(self._backing_transport)
347
 
        self._run_handler_code(self._command.execute, args, {})
348
 
 
349
 
    def end_received(self):
350
 
        if self._command is None:
351
 
            # no active command object, so ignore the event.
352
 
            return
353
 
        self._run_handler_code(self._command.do_end, (), {})
354
 
 
355
 
    def post_body_error_received(self, error_args):
356
 
        # Just a no-op at the moment.
357
 
        pass
358
 
 
359
 
 
360
 
def _translate_error(err):
361
 
    if isinstance(err, errors.NoSuchFile):
362
 
        return ('NoSuchFile', err.path)
363
 
    elif isinstance(err, errors.FileExists):
364
 
        return ('FileExists', err.path)
365
 
    elif isinstance(err, errors.DirectoryNotEmpty):
366
 
        return ('DirectoryNotEmpty', err.path)
367
 
    elif isinstance(err, errors.ShortReadvError):
368
 
        return ('ShortReadvError', err.path, str(err.offset), str(err.length),
369
 
                str(err.actual))
370
 
    elif isinstance(err, errors.UnstackableRepositoryFormat):
371
 
        return (('UnstackableRepositoryFormat', str(err.format), err.url))
372
 
    elif isinstance(err, errors.UnstackableBranchFormat):
373
 
        return ('UnstackableBranchFormat', str(err.format), err.url)
374
 
    elif isinstance(err, errors.NotStacked):
375
 
        return ('NotStacked',)
376
 
    elif isinstance(err, UnicodeError):
377
 
        # If it is a DecodeError, than most likely we are starting
378
 
        # with a plain string
379
 
        str_or_unicode = err.object
380
 
        if isinstance(str_or_unicode, unicode):
381
 
            # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator
382
 
            # byte) in it, so this encoding could cause broken responses.
383
 
            # Newer clients use protocol v3, so will be fine.
384
 
            val = 'u:' + str_or_unicode.encode('utf-8')
385
 
        else:
386
 
            val = 's:' + str_or_unicode.encode('base64')
387
 
        # This handles UnicodeEncodeError or UnicodeDecodeError
388
 
        return (err.__class__.__name__, err.encoding, val, str(err.start),
389
 
                str(err.end), err.reason)
390
 
    elif isinstance(err, errors.TransportNotPossible):
391
 
        if err.msg == "readonly transport":
392
 
            return ('ReadOnlyError', )
393
 
    elif isinstance(err, errors.ReadError):
394
 
        # cannot read the file
395
 
        return ('ReadError', err.path)
396
 
    elif isinstance(err, errors.PermissionDenied):
397
 
        return ('PermissionDenied', err.path, err.extra)
398
 
    elif isinstance(err, errors.TokenMismatch):
399
 
        return ('TokenMismatch', err.given_token, err.lock_token)
400
 
    elif isinstance(err, errors.LockContention):
401
 
        return ('LockContention', err.lock, err.msg)
402
 
    # Unserialisable error.  Log it, and return a generic error
403
 
    trace.log_exception_quietly()
404
 
    return ('error', str(err))
 
172
            return callable(*args, **kwargs)
 
173
        except errors.NoSuchFile, e:
 
174
            return SmartServerResponse(('NoSuchFile', e.path))
 
175
        except errors.FileExists, e:
 
176
            return SmartServerResponse(('FileExists', e.path))
 
177
        except errors.DirectoryNotEmpty, e:
 
178
            return SmartServerResponse(('DirectoryNotEmpty', e.path))
 
179
        except errors.ShortReadvError, e:
 
180
            return SmartServerResponse(('ShortReadvError',
 
181
                e.path, str(e.offset), str(e.length), str(e.actual)))
 
182
        except UnicodeError, e:
 
183
            # If it is a DecodeError, than most likely we are starting
 
184
            # with a plain string
 
185
            str_or_unicode = e.object
 
186
            if isinstance(str_or_unicode, unicode):
 
187
                # XXX: UTF-8 might have \x01 (our seperator byte) in it.  We
 
188
                # should escape it somehow.
 
189
                val = 'u:' + str_or_unicode.encode('utf-8')
 
190
            else:
 
191
                val = 's:' + str_or_unicode.encode('base64')
 
192
            # This handles UnicodeEncodeError or UnicodeDecodeError
 
193
            return SmartServerResponse((e.__class__.__name__,
 
194
                    e.encoding, val, str(e.start), str(e.end), e.reason))
 
195
        except errors.TransportNotPossible, e:
 
196
            if e.msg == "readonly transport":
 
197
                return SmartServerResponse(('ReadOnlyError', ))
 
198
            else:
 
199
                raise
405
200
 
406
201
 
407
202
class HelloRequest(SmartServerRequest):
408
 
    """Answer a version request with the highest protocol version this server
409
 
    supports.
410
 
    """
 
203
    """Answer a version request with my version."""
411
204
 
412
205
    def do(self):
413
 
        return SuccessfulSmartServerResponse(('ok', '2'))
 
206
        return SmartServerResponse(('ok', '1'))
414
207
 
415
208
 
416
209
class GetBundleRequest(SmartServerRequest):
418
211
 
419
212
    def do(self, path, revision_id):
420
213
        # open transport relative to our base
421
 
        t = self.transport_from_client_path(path)
 
214
        t = self._backing_transport.clone(path)
422
215
        control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t)
423
216
        repo = control.open_repository()
424
217
        tmpf = tempfile.TemporaryFile()
425
218
        base_revision = revision.NULL_REVISION
426
 
        serializer.write_bundle(repo, revision_id, base_revision, tmpf)
 
219
        write_bundle(repo, revision_id, base_revision, tmpf)
427
220
        tmpf.seek(0)
428
 
        return SuccessfulSmartServerResponse((), tmpf.read())
 
221
        return SmartServerResponse((), tmpf.read())
429
222
 
430
223
 
431
224
class SmartServerIsReadonly(SmartServerRequest):
436
229
            answer = 'yes'
437
230
        else:
438
231
            answer = 'no'
439
 
        return SuccessfulSmartServerResponse((answer,))
 
232
        return SmartServerResponse((answer,))
440
233
 
441
234
 
442
235
request_handlers = registry.Registry()
443
236
request_handlers.register_lazy(
444
237
    'append', 'bzrlib.smart.vfs', 'AppendRequest')
445
238
request_handlers.register_lazy(
446
 
    'Branch.get_config_file', 'bzrlib.smart.branch',
447
 
    'SmartServerBranchGetConfigFile')
448
 
request_handlers.register_lazy(
449
 
    'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
450
 
request_handlers.register_lazy(
451
 
    'Branch.get_tags_bytes', 'bzrlib.smart.branch',
452
 
    'SmartServerBranchGetTagsBytes')
453
 
request_handlers.register_lazy(
454
 
    'Branch.get_stacked_on_url', 'bzrlib.smart.branch', 'SmartServerBranchRequestGetStackedOnURL')
455
 
request_handlers.register_lazy(
456
 
    'Branch.last_revision_info', 'bzrlib.smart.branch', 'SmartServerBranchRequestLastRevisionInfo')
457
 
request_handlers.register_lazy(
458
 
    'Branch.lock_write', 'bzrlib.smart.branch', 'SmartServerBranchRequestLockWrite')
459
 
request_handlers.register_lazy( 'Branch.revision_history',
460
 
    'bzrlib.smart.branch', 'SmartServerRequestRevisionHistory')
461
 
request_handlers.register_lazy( 'Branch.set_config_option',
462
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetConfigOption')
463
 
request_handlers.register_lazy( 'Branch.set_last_revision',
464
 
    'bzrlib.smart.branch', 'SmartServerBranchRequestSetLastRevision')
465
 
request_handlers.register_lazy(
466
 
    'Branch.set_last_revision_info', 'bzrlib.smart.branch',
467
 
    'SmartServerBranchRequestSetLastRevisionInfo')
468
 
request_handlers.register_lazy(
469
 
    'Branch.set_last_revision_ex', 'bzrlib.smart.branch',
470
 
    'SmartServerBranchRequestSetLastRevisionEx')
471
 
request_handlers.register_lazy(
472
 
    'Branch.set_parent_location', 'bzrlib.smart.branch',
473
 
    'SmartServerBranchRequestSetParentLocation')
474
 
request_handlers.register_lazy(
475
 
    'Branch.unlock', 'bzrlib.smart.branch', 'SmartServerBranchRequestUnlock')
476
 
request_handlers.register_lazy(
477
 
    'BzrDir.cloning_metadir', 'bzrlib.smart.bzrdir',
478
 
    'SmartServerBzrDirRequestCloningMetaDir')
479
 
request_handlers.register_lazy(
480
 
    'BzrDir.create_branch', 'bzrlib.smart.bzrdir',
481
 
    'SmartServerRequestCreateBranch')
482
 
request_handlers.register_lazy(
483
 
    'BzrDir.create_repository', 'bzrlib.smart.bzrdir',
484
 
    'SmartServerRequestCreateRepository')
485
 
request_handlers.register_lazy(
486
 
    'BzrDir.find_repository', 'bzrlib.smart.bzrdir',
487
 
    'SmartServerRequestFindRepositoryV1')
488
 
request_handlers.register_lazy(
489
 
    'BzrDir.find_repositoryV2', 'bzrlib.smart.bzrdir',
490
 
    'SmartServerRequestFindRepositoryV2')
491
 
request_handlers.register_lazy(
492
 
    'BzrDir.find_repositoryV3', 'bzrlib.smart.bzrdir',
493
 
    'SmartServerRequestFindRepositoryV3')
494
 
request_handlers.register_lazy(
495
 
    'BzrDir.get_config_file', 'bzrlib.smart.bzrdir',
496
 
    'SmartServerBzrDirRequestConfigFile')
497
 
request_handlers.register_lazy(
498
 
    'BzrDirFormat.initialize', 'bzrlib.smart.bzrdir',
499
 
    'SmartServerRequestInitializeBzrDir')
500
 
request_handlers.register_lazy(
501
 
    'BzrDirFormat.initialize_ex_1.16', 'bzrlib.smart.bzrdir',
502
 
    'SmartServerRequestBzrDirInitializeEx')
503
 
request_handlers.register_lazy(
504
 
    'BzrDir.open', 'bzrlib.smart.bzrdir', 'SmartServerRequestOpenBzrDir')
505
 
request_handlers.register_lazy(
506
 
    'BzrDir.open_branch', 'bzrlib.smart.bzrdir',
507
 
    'SmartServerRequestOpenBranch')
508
 
request_handlers.register_lazy(
509
 
    'BzrDir.open_branchV2', 'bzrlib.smart.bzrdir',
510
 
    'SmartServerRequestOpenBranchV2')
511
 
request_handlers.register_lazy(
512
239
    'delete', 'bzrlib.smart.vfs', 'DeleteRequest')
513
240
request_handlers.register_lazy(
514
241
    'get', 'bzrlib.smart.vfs', 'GetRequest')
535
262
request_handlers.register_lazy(
536
263
    'rename', 'bzrlib.smart.vfs', 'RenameRequest')
537
264
request_handlers.register_lazy(
538
 
    'PackRepository.autopack', 'bzrlib.smart.packrepository',
539
 
    'SmartServerPackRepositoryAutopack')
540
 
request_handlers.register_lazy('Repository.gather_stats',
541
 
                               'bzrlib.smart.repository',
542
 
                               'SmartServerRepositoryGatherStats')
543
 
request_handlers.register_lazy('Repository.get_parent_map',
544
 
                               'bzrlib.smart.repository',
545
 
                               'SmartServerRepositoryGetParentMap')
546
 
request_handlers.register_lazy(
547
 
    'Repository.get_revision_graph', 'bzrlib.smart.repository', 'SmartServerRepositoryGetRevisionGraph')
548
 
request_handlers.register_lazy(
549
 
    'Repository.has_revision', 'bzrlib.smart.repository', 'SmartServerRequestHasRevision')
550
 
request_handlers.register_lazy(
551
 
    'Repository.insert_stream', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStream')
552
 
request_handlers.register_lazy(
553
 
    'Repository.insert_stream_locked', 'bzrlib.smart.repository', 'SmartServerRepositoryInsertStreamLocked')
554
 
request_handlers.register_lazy(
555
 
    'Repository.is_shared', 'bzrlib.smart.repository', 'SmartServerRepositoryIsShared')
556
 
request_handlers.register_lazy(
557
 
    'Repository.lock_write', 'bzrlib.smart.repository', 'SmartServerRepositoryLockWrite')
558
 
request_handlers.register_lazy(
559
 
    'Repository.set_make_working_trees', 'bzrlib.smart.repository',
560
 
    'SmartServerRepositorySetMakeWorkingTrees')
561
 
request_handlers.register_lazy(
562
 
    'Repository.unlock', 'bzrlib.smart.repository', 'SmartServerRepositoryUnlock')
563
 
request_handlers.register_lazy(
564
 
    'Repository.get_rev_id_for_revno', 'bzrlib.smart.repository',
565
 
    'SmartServerRepositoryGetRevIdForRevno')
566
 
request_handlers.register_lazy(
567
 
    'Repository.get_stream', 'bzrlib.smart.repository',
568
 
    'SmartServerRepositoryGetStream')
569
 
request_handlers.register_lazy(
570
 
    'Repository.tarball', 'bzrlib.smart.repository',
571
 
    'SmartServerRepositoryTarball')
572
 
request_handlers.register_lazy(
573
265
    'rmdir', 'bzrlib.smart.vfs', 'RmdirRequest')
574
266
request_handlers.register_lazy(
575
267
    'stat', 'bzrlib.smart.vfs', 'StatRequest')
576
 
request_handlers.register_lazy(
577
 
    'Transport.is_readonly', 'bzrlib.smart.request', 'SmartServerIsReadonly')