1# Copyright (C) 2006-2011 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 19Interesting 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 34import threading 35from _thread import get_ident 36 37from ... import ( 38 branch as _mod_branch, 39 debug, 40 errors, 41 osutils, 42 registry, 43 revision, 44 trace, 45 urlutils, 46 ) 47from ...lazy_import import lazy_import 48lazy_import(globals(), """ 49from breezy.bzr import bzrdir 50from breezy.bzr.bundle import serializer 51 52import tempfile 53""") 54 55 56jail_info = threading.local() 57jail_info.transports = None 58 59 60class DisabledMethod(errors.InternalBzrError): 61 62 _fmt = "The smart server method '%(class_name)s' is disabled." 63 64 def __init__(self, class_name): 65 errors.BzrError.__init__(self) 66 self.class_name = class_name 67 68 69def _install_hook(): 70 bzrdir.BzrDir.hooks.install_named_hook( 71 'pre_open', _pre_open_hook, 'checking server jail') 72 73 74def _pre_open_hook(transport): 75 allowed_transports = getattr(jail_info, 'transports', None) 76 if allowed_transports is None: 77 return 78 abspath = transport.base 79 for allowed_transport in allowed_transports: 80 try: 81 allowed_transport.relpath(abspath) 82 except errors.PathNotChild: 83 continue 84 else: 85 return 86 raise errors.JailBreak(abspath) 87 88 89_install_hook() 90 91 92class SmartServerRequest(object): 93 """Base class for request handlers. 94 95 To define a new request, subclass this class and override the `do` method 96 (and if appropriate, `do_body` as well). Request implementors should take 97 care to call `translate_client_path` and `transport_from_client_path` as 98 appropriate when dealing with paths received from the client. 99 """ 100 # XXX: rename this class to BaseSmartServerRequestHandler ? A request 101 # *handler* is a different concept to the request. 102 103 def __init__(self, backing_transport, root_client_path='/', jail_root=None): 104 """Constructor. 105 106 :param backing_transport: the base transport to be used when performing 107 this request. 108 :param root_client_path: the client path that maps to the root of 109 backing_transport. This is used to interpret relpaths received 110 from the client. Clients will not be able to refer to paths above 111 this root. If root_client_path is None, then no translation will 112 be performed on client paths. Default is '/'. 113 :param jail_root: if specified, the root of the BzrDir.open jail to use 114 instead of backing_transport. 115 """ 116 self._backing_transport = backing_transport 117 if jail_root is None: 118 jail_root = backing_transport 119 self._jail_root = jail_root 120 if root_client_path is not None: 121 if not root_client_path.startswith('/'): 122 root_client_path = '/' + root_client_path 123 if not root_client_path.endswith('/'): 124 root_client_path += '/' 125 self._root_client_path = root_client_path 126 self._body_chunks = [] 127 128 def _check_enabled(self): 129 """Raises DisabledMethod if this method is disabled.""" 130 pass 131 132 def do(self, *args): 133 """Mandatory extension point for SmartServerRequest subclasses. 134 135 Subclasses must implement this. 136 137 This should return a SmartServerResponse if this command expects to 138 receive no body. 139 """ 140 raise NotImplementedError(self.do) 141 142 def execute(self, *args): 143 """Public entry point to execute this request. 144 145 It will return a SmartServerResponse if the command does not expect a 146 body. 147 148 :param args: the arguments of the request. 149 """ 150 self._check_enabled() 151 return self.do(*args) 152 153 def do_body(self, body_bytes): 154 """Called if the client sends a body with the request. 155 156 The do() method is still called, and must have returned None. 157 158 Must return a SmartServerResponse. 159 """ 160 if body_bytes != b'': 161 raise errors.SmartProtocolError('Request does not expect a body') 162 163 def do_chunk(self, chunk_bytes): 164 """Called with each body chunk if the request has a streamed body. 165 166 The do() method is still called, and must have returned None. 167 """ 168 self._body_chunks.append(chunk_bytes) 169 170 def do_end(self): 171 """Called when the end of the request has been received.""" 172 body_bytes = b''.join(self._body_chunks) 173 self._body_chunks = None 174 return self.do_body(body_bytes) 175 176 def setup_jail(self): 177 jail_info.transports = [self._jail_root] 178 179 def teardown_jail(self): 180 jail_info.transports = None 181 182 def translate_client_path(self, client_path): 183 """Translate a path received from a network client into a local 184 relpath. 185 186 All paths received from the client *must* be translated. 187 188 :param client_path: the path from the client. 189 :returns: a relpath that may be used with self._backing_transport 190 (unlike the untranslated client_path, which must not be used with 191 the backing transport). 192 """ 193 client_path = client_path.decode('utf-8') 194 if self._root_client_path is None: 195 # no translation necessary! 196 return client_path 197 if not client_path.startswith('/'): 198 client_path = '/' + client_path 199 if client_path + '/' == self._root_client_path: 200 return '.' 201 if client_path.startswith(self._root_client_path): 202 path = client_path[len(self._root_client_path):] 203 relpath = urlutils.joinpath('/', path) 204 if not relpath.startswith('/'): 205 raise ValueError(relpath) 206 return urlutils.escape('.' + relpath) 207 else: 208 raise errors.PathNotChild(client_path, self._root_client_path) 209 210 def transport_from_client_path(self, client_path): 211 """Get a backing transport corresponding to the location referred to by 212 a network client. 213 214 :seealso: translate_client_path 215 :returns: a transport cloned from self._backing_transport 216 """ 217 relpath = self.translate_client_path(client_path) 218 return self._backing_transport.clone(relpath) 219 220 221class SmartServerResponse(object): 222 """A response to a client request. 223 224 This base class should not be used. Instead use 225 SuccessfulSmartServerResponse and FailedSmartServerResponse as appropriate. 226 """ 227 228 def __init__(self, args, body=None, body_stream=None): 229 """Constructor. 230 231 :param args: tuple of response arguments. 232 :param body: string of a response body. 233 :param body_stream: iterable of bytestrings to be streamed to the 234 client. 235 """ 236 self.args = args 237 if body is not None and body_stream is not None: 238 raise errors.BzrError( 239 "'body' and 'body_stream' are mutually exclusive.") 240 self.body = body 241 self.body_stream = body_stream 242 243 def __eq__(self, other): 244 if other is None: 245 return False 246 return (other.args == self.args 247 and other.body == self.body 248 and other.body_stream is self.body_stream) 249 250 def __repr__(self): 251 return "<%s args=%r body=%r>" % (self.__class__.__name__, 252 self.args, self.body) 253 254 255class FailedSmartServerResponse(SmartServerResponse): 256 """A SmartServerResponse for a request which failed.""" 257 258 def is_successful(self): 259 """FailedSmartServerResponse are not successful.""" 260 return False 261 262 263class SuccessfulSmartServerResponse(SmartServerResponse): 264 """A SmartServerResponse for a successfully completed request.""" 265 266 def is_successful(self): 267 """SuccessfulSmartServerResponse are successful.""" 268 return True 269 270 271class SmartServerRequestHandler(object): 272 """Protocol logic for smart server. 273 274 This doesn't handle serialization at all, it just processes requests and 275 creates responses. 276 """ 277 278 # IMPORTANT FOR IMPLEMENTORS: It is important that SmartServerRequestHandler 279 # not contain encoding or decoding logic to allow the wire protocol to vary 280 # from the object protocol: we will want to tweak the wire protocol separate 281 # from the object model, and ideally we will be able to do that without 282 # having a SmartServerRequestHandler subclass for each wire protocol, rather 283 # just a Protocol subclass. 284 285 # TODO: Better way of representing the body for commands that take it, 286 # and allow it to be streamed into the server. 287 288 def __init__(self, backing_transport, commands, root_client_path, 289 jail_root=None): 290 """Constructor. 291 292 :param backing_transport: a Transport to handle requests for. 293 :param commands: a registry mapping command names to SmartServerRequest 294 subclasses. e.g. breezy.transport.smart.vfs.vfs_commands. 295 """ 296 self._backing_transport = backing_transport 297 self._root_client_path = root_client_path 298 self._commands = commands 299 if jail_root is None: 300 jail_root = backing_transport 301 self._jail_root = jail_root 302 self.response = None 303 self.finished_reading = False 304 self._command = None 305 if 'hpss' in debug.debug_flags: 306 self._request_start_time = osutils.perf_counter() 307 self._thread_id = get_ident() 308 309 def _trace(self, action, message, extra_bytes=None, include_time=False): 310 # It is a bit of a shame that this functionality overlaps with that of 311 # ProtocolThreeRequester._trace. However, there is enough difference 312 # that just putting it in a helper doesn't help a lot. And some state 313 # is taken from the instance. 314 if include_time: 315 t = '%5.3fs ' % (osutils.perf_counter() - self._request_start_time) 316 else: 317 t = '' 318 if extra_bytes is None: 319 extra = '' 320 else: 321 extra = ' ' + repr(extra_bytes[:40]) 322 if len(extra) > 33: 323 extra = extra[:29] + extra[-1] + '...' 324 trace.mutter('%12s: [%s] %s%s%s' 325 % (action, self._thread_id, t, message, extra)) 326 327 def accept_body(self, bytes): 328 """Accept body data.""" 329 if self._command is None: 330 # no active command object, so ignore the event. 331 return 332 self._run_handler_code(self._command.do_chunk, (bytes,), {}) 333 if 'hpss' in debug.debug_flags: 334 self._trace('accept body', 335 '%d bytes' % (len(bytes),), bytes) 336 337 def end_of_body(self): 338 """No more body data will be received.""" 339 self._run_handler_code(self._command.do_end, (), {}) 340 # cannot read after this. 341 self.finished_reading = True 342 if 'hpss' in debug.debug_flags: 343 self._trace('end of body', '', include_time=True) 344 345 def _run_handler_code(self, callable, args, kwargs): 346 """Run some handler specific code 'callable'. 347 348 If a result is returned, it is considered to be the commands response, 349 and finished_reading is set true, and its assigned to self.response. 350 351 Any exceptions caught are translated and a response object created 352 from them. 353 """ 354 result = self._call_converting_errors(callable, args, kwargs) 355 356 if result is not None: 357 self.response = result 358 self.finished_reading = True 359 360 def _call_converting_errors(self, callable, args, kwargs): 361 """Call callable converting errors to Response objects.""" 362 # XXX: most of this error conversion is VFS-related, and thus ought to 363 # be in SmartServerVFSRequestHandler somewhere. 364 try: 365 self._command.setup_jail() 366 try: 367 return callable(*args, **kwargs) 368 finally: 369 self._command.teardown_jail() 370 except (KeyboardInterrupt, SystemExit): 371 raise 372 except Exception as err: 373 err_struct = _translate_error(err) 374 return FailedSmartServerResponse(err_struct) 375 376 def headers_received(self, headers): 377 # Just a no-op at the moment. 378 if 'hpss' in debug.debug_flags: 379 self._trace('headers', repr(headers)) 380 381 def args_received(self, args): 382 cmd = args[0] 383 args = args[1:] 384 try: 385 command = self._commands.get(cmd) 386 except LookupError: 387 if 'hpss' in debug.debug_flags: 388 self._trace('hpss unknown request', 389 cmd, repr(args)[1:-1]) 390 raise errors.UnknownSmartMethod(cmd) 391 if 'hpss' in debug.debug_flags: 392 from . import vfs 393 if issubclass(command, vfs.VfsRequest): 394 action = 'hpss vfs req' 395 else: 396 action = 'hpss request' 397 self._trace(action, '%s %s' % (cmd, repr(args)[1:-1])) 398 self._command = command( 399 self._backing_transport, self._root_client_path, self._jail_root) 400 self._run_handler_code(self._command.execute, args, {}) 401 402 def end_received(self): 403 if self._command is None: 404 # no active command object, so ignore the event. 405 return 406 self._run_handler_code(self._command.do_end, (), {}) 407 if 'hpss' in debug.debug_flags: 408 self._trace('end', '', include_time=True) 409 410 def post_body_error_received(self, error_args): 411 # Just a no-op at the moment. 412 pass 413 414 415def _translate_error(err): 416 if isinstance(err, errors.NoSuchFile): 417 return (b'NoSuchFile', err.path.encode('utf-8')) 418 elif isinstance(err, errors.FileExists): 419 return (b'FileExists', err.path.encode('utf-8')) 420 elif isinstance(err, errors.DirectoryNotEmpty): 421 return (b'DirectoryNotEmpty', err.path.encode('utf-8')) 422 elif isinstance(err, errors.IncompatibleRepositories): 423 return (b'IncompatibleRepositories', str(err.source), str(err.target), 424 str(err.details)) 425 elif isinstance(err, errors.ShortReadvError): 426 return (b'ShortReadvError', err.path.encode('utf-8'), 427 str(err.offset).encode('ascii'), 428 str(err.length).encode('ascii'), 429 str(err.actual).encode('ascii')) 430 elif isinstance(err, errors.RevisionNotPresent): 431 return (b'RevisionNotPresent', err.revision_id, err.file_id) 432 elif isinstance(err, errors.UnstackableRepositoryFormat): 433 return ((b'UnstackableRepositoryFormat', 434 str(err.format).encode('utf-8'), err.url.encode('utf-8'))) 435 elif isinstance(err, _mod_branch.UnstackableBranchFormat): 436 return (b'UnstackableBranchFormat', str(err.format).encode('utf-8'), 437 err.url.encode('utf-8')) 438 elif isinstance(err, errors.NotStacked): 439 return (b'NotStacked',) 440 elif isinstance(err, errors.BzrCheckError): 441 return (b'BzrCheckError', err.msg.encode('utf-8')) 442 elif isinstance(err, UnicodeError): 443 # If it is a DecodeError, than most likely we are starting 444 # with a plain string 445 str_or_unicode = err.object 446 if isinstance(str_or_unicode, str): 447 # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator 448 # byte) in it, so this encoding could cause broken responses. 449 # Newer clients use protocol v3, so will be fine. 450 val = 'u:' + str_or_unicode.encode('utf-8') 451 else: 452 val = 's:' + str_or_unicode.encode('base64') 453 # This handles UnicodeEncodeError or UnicodeDecodeError 454 return (err.__class__.__name__, err.encoding, val, str(err.start), 455 str(err.end), err.reason) 456 elif isinstance(err, errors.TransportNotPossible): 457 if err.msg == "readonly transport": 458 return (b'ReadOnlyError', ) 459 elif isinstance(err, errors.ReadError): 460 # cannot read the file 461 return (b'ReadError', err.path) 462 elif isinstance(err, errors.PermissionDenied): 463 return (b'PermissionDenied', err.path.encode('utf-8'), err.extra.encode('utf-8')) 464 elif isinstance(err, errors.TokenMismatch): 465 return (b'TokenMismatch', err.given_token, err.lock_token) 466 elif isinstance(err, errors.LockContention): 467 return (b'LockContention',) 468 elif isinstance(err, errors.GhostRevisionsHaveNoRevno): 469 return (b'GhostRevisionsHaveNoRevno', err.revision_id, err.ghost_revision_id) 470 elif isinstance(err, urlutils.InvalidURL): 471 return (b'InvalidURL', err.path.encode('utf-8'), err.extra.encode('ascii')) 472 elif isinstance(err, MemoryError): 473 # GZ 2011-02-24: Copy breezy.trace -Dmem_dump functionality here? 474 return (b'MemoryError',) 475 elif isinstance(err, errors.AlreadyControlDirError): 476 return (b'AlreadyControlDir', err.path) 477 # Unserialisable error. Log it, and return a generic error 478 trace.log_exception_quietly() 479 return (b'error', 480 trace._qualified_exception_name( 481 err.__class__, True).encode('utf-8'), 482 str(err).encode('utf-8')) 483 484 485class HelloRequest(SmartServerRequest): 486 """Answer a version request with the highest protocol version this server 487 supports. 488 """ 489 490 def do(self): 491 return SuccessfulSmartServerResponse((b'ok', b'2')) 492 493 494class GetBundleRequest(SmartServerRequest): 495 """Get a bundle of from the null revision to the specified revision.""" 496 497 def do(self, path, revision_id): 498 # open transport relative to our base 499 t = self.transport_from_client_path(path) 500 control, extra_path = bzrdir.BzrDir.open_containing_from_transport(t) 501 repo = control.open_repository() 502 tmpf = tempfile.TemporaryFile() 503 base_revision = revision.NULL_REVISION 504 serializer.write_bundle(repo, revision_id, base_revision, tmpf) 505 tmpf.seek(0) 506 return SuccessfulSmartServerResponse((), tmpf.read()) 507 508 509class SmartServerIsReadonly(SmartServerRequest): 510 # XXX: this request method belongs somewhere else. 511 512 def do(self): 513 if self._backing_transport.is_readonly(): 514 answer = b'yes' 515 else: 516 answer = b'no' 517 return SuccessfulSmartServerResponse((answer,)) 518 519 520# In the 'info' attribute, we store whether this request is 'safe' to retry if 521# we get a disconnect while reading the response. It can have the values: 522# read This is purely a read request, so retrying it is perfectly ok. 523# idem An idempotent write request. Something like 'put' where if you put 524# the same bytes twice you end up with the same final bytes. 525# semi This is a request that isn't strictly idempotent, but doesn't 526# result in corruption if it is retried. This is for things like 527# 'lock' and 'unlock'. If you call lock, it updates the disk 528# structure. If you fail to read the response, you won't be able to 529# use the lock, because you don't have the lock token. Calling lock 530# again will fail, because the lock is already taken. However, we 531# can't tell if the server received our request or not. If it didn't, 532# then retrying the request is fine, as it will actually do what we 533# want. If it did, we will interrupt the current operation, but we 534# are no worse off than interrupting the current operation because of 535# a ConnectionReset. 536# semivfs Similar to semi, but specific to a Virtual FileSystem request. 537# stream This is a request that takes a stream that cannot be restarted if 538# consumed. This request is 'safe' in that if we determine the 539# connection is closed before we consume the stream, we can try 540# again. 541# mutate State is updated in a way that replaying that request results in a 542# different state. For example 'append' writes more bytes to a given 543# file. If append succeeds, it moves the file pointer. 544request_handlers = registry.Registry() 545request_handlers.register_lazy( 546 b'append', 'breezy.bzr.smart.vfs', 'AppendRequest', info='mutate') 547request_handlers.register_lazy( 548 b'Branch.break_lock', 'breezy.bzr.smart.branch', 549 'SmartServerBranchBreakLock', info='idem') 550request_handlers.register_lazy( 551 b'Branch.get_config_file', 'breezy.bzr.smart.branch', 552 'SmartServerBranchGetConfigFile', info='read') 553request_handlers.register_lazy( 554 b'Branch.get_parent', 'breezy.bzr.smart.branch', 'SmartServerBranchGetParent', 555 info='read') 556request_handlers.register_lazy( 557 b'Branch.put_config_file', 'breezy.bzr.smart.branch', 558 'SmartServerBranchPutConfigFile', info='idem') 559request_handlers.register_lazy( 560 b'Branch.get_tags_bytes', 'breezy.bzr.smart.branch', 561 'SmartServerBranchGetTagsBytes', info='read') 562request_handlers.register_lazy( 563 b'Branch.set_tags_bytes', 'breezy.bzr.smart.branch', 564 'SmartServerBranchSetTagsBytes', info='idem') 565request_handlers.register_lazy( 566 b'Branch.heads_to_fetch', 'breezy.bzr.smart.branch', 567 'SmartServerBranchHeadsToFetch', info='read') 568request_handlers.register_lazy( 569 b'Branch.get_stacked_on_url', 'breezy.bzr.smart.branch', 570 'SmartServerBranchRequestGetStackedOnURL', info='read') 571request_handlers.register_lazy( 572 b'Branch.get_physical_lock_status', 'breezy.bzr.smart.branch', 573 'SmartServerBranchRequestGetPhysicalLockStatus', info='read') 574request_handlers.register_lazy( 575 b'Branch.last_revision_info', 'breezy.bzr.smart.branch', 576 'SmartServerBranchRequestLastRevisionInfo', info='read') 577request_handlers.register_lazy( 578 b'Branch.lock_write', 'breezy.bzr.smart.branch', 579 'SmartServerBranchRequestLockWrite', info='semi') 580request_handlers.register_lazy( 581 b'Branch.revision_history', 'breezy.bzr.smart.branch', 582 'SmartServerRequestRevisionHistory', info='read') 583request_handlers.register_lazy( 584 b'Branch.set_config_option', 'breezy.bzr.smart.branch', 585 'SmartServerBranchRequestSetConfigOption', info='idem') 586request_handlers.register_lazy( 587 b'Branch.set_config_option_dict', 'breezy.bzr.smart.branch', 588 'SmartServerBranchRequestSetConfigOptionDict', info='idem') 589request_handlers.register_lazy( 590 b'Branch.set_last_revision', 'breezy.bzr.smart.branch', 591 'SmartServerBranchRequestSetLastRevision', info='idem') 592request_handlers.register_lazy( 593 b'Branch.set_last_revision_info', 'breezy.bzr.smart.branch', 594 'SmartServerBranchRequestSetLastRevisionInfo', info='idem') 595request_handlers.register_lazy( 596 b'Branch.set_last_revision_ex', 'breezy.bzr.smart.branch', 597 'SmartServerBranchRequestSetLastRevisionEx', info='idem') 598request_handlers.register_lazy( 599 b'Branch.set_parent_location', 'breezy.bzr.smart.branch', 600 'SmartServerBranchRequestSetParentLocation', info='idem') 601request_handlers.register_lazy( 602 b'Branch.unlock', 'breezy.bzr.smart.branch', 603 'SmartServerBranchRequestUnlock', info='semi') 604request_handlers.register_lazy( 605 b'Branch.revision_id_to_revno', 'breezy.bzr.smart.branch', 606 'SmartServerBranchRequestRevisionIdToRevno', info='read') 607request_handlers.register_lazy( 608 b'Branch.get_all_reference_info', 'breezy.bzr.smart.branch', 609 'SmartServerBranchRequestGetAllReferenceInfo', info='read') 610request_handlers.register_lazy( 611 b'BzrDir.checkout_metadir', 'breezy.bzr.smart.bzrdir', 612 'SmartServerBzrDirRequestCheckoutMetaDir', info='read') 613request_handlers.register_lazy( 614 b'BzrDir.cloning_metadir', 'breezy.bzr.smart.bzrdir', 615 'SmartServerBzrDirRequestCloningMetaDir', info='read') 616request_handlers.register_lazy( 617 b'BzrDir.create_branch', 'breezy.bzr.smart.bzrdir', 618 'SmartServerRequestCreateBranch', info='semi') 619request_handlers.register_lazy( 620 b'BzrDir.create_repository', 'breezy.bzr.smart.bzrdir', 621 'SmartServerRequestCreateRepository', info='semi') 622request_handlers.register_lazy( 623 b'BzrDir.find_repository', 'breezy.bzr.smart.bzrdir', 624 'SmartServerRequestFindRepositoryV1', info='read') 625request_handlers.register_lazy( 626 b'BzrDir.find_repositoryV2', 'breezy.bzr.smart.bzrdir', 627 'SmartServerRequestFindRepositoryV2', info='read') 628request_handlers.register_lazy( 629 b'BzrDir.find_repositoryV3', 'breezy.bzr.smart.bzrdir', 630 'SmartServerRequestFindRepositoryV3', info='read') 631request_handlers.register_lazy( 632 b'BzrDir.get_branches', 'breezy.bzr.smart.bzrdir', 633 'SmartServerBzrDirRequestGetBranches', info='read') 634request_handlers.register_lazy( 635 b'BzrDir.get_config_file', 'breezy.bzr.smart.bzrdir', 636 'SmartServerBzrDirRequestConfigFile', info='read') 637request_handlers.register_lazy( 638 b'BzrDir.destroy_branch', 'breezy.bzr.smart.bzrdir', 639 'SmartServerBzrDirRequestDestroyBranch', info='semi') 640request_handlers.register_lazy( 641 b'BzrDir.destroy_repository', 'breezy.bzr.smart.bzrdir', 642 'SmartServerBzrDirRequestDestroyRepository', info='semi') 643request_handlers.register_lazy( 644 b'BzrDir.has_workingtree', 'breezy.bzr.smart.bzrdir', 645 'SmartServerBzrDirRequestHasWorkingTree', info='read') 646request_handlers.register_lazy( 647 b'BzrDirFormat.initialize', 'breezy.bzr.smart.bzrdir', 648 'SmartServerRequestInitializeBzrDir', info='semi') 649request_handlers.register_lazy( 650 b'BzrDirFormat.initialize_ex_1.16', 'breezy.bzr.smart.bzrdir', 651 'SmartServerRequestBzrDirInitializeEx', info='semi') 652request_handlers.register_lazy( 653 b'BzrDir.open', 'breezy.bzr.smart.bzrdir', 'SmartServerRequestOpenBzrDir', 654 info='read') 655request_handlers.register_lazy( 656 b'BzrDir.open_2.1', 'breezy.bzr.smart.bzrdir', 657 'SmartServerRequestOpenBzrDir_2_1', info='read') 658request_handlers.register_lazy( 659 b'BzrDir.open_branch', 'breezy.bzr.smart.bzrdir', 660 'SmartServerRequestOpenBranch', info='read') 661request_handlers.register_lazy( 662 b'BzrDir.open_branchV2', 'breezy.bzr.smart.bzrdir', 663 'SmartServerRequestOpenBranchV2', info='read') 664request_handlers.register_lazy( 665 b'BzrDir.open_branchV3', 'breezy.bzr.smart.bzrdir', 666 'SmartServerRequestOpenBranchV3', info='read') 667request_handlers.register_lazy( 668 b'delete', 'breezy.bzr.smart.vfs', 'DeleteRequest', info='semivfs') 669request_handlers.register_lazy( 670 b'get', 'breezy.bzr.smart.vfs', 'GetRequest', info='read') 671request_handlers.register_lazy( 672 b'get_bundle', 'breezy.bzr.smart.request', 'GetBundleRequest', info='read') 673request_handlers.register_lazy( 674 b'has', 'breezy.bzr.smart.vfs', 'HasRequest', info='read') 675request_handlers.register_lazy( 676 b'hello', 'breezy.bzr.smart.request', 'HelloRequest', info='read') 677request_handlers.register_lazy( 678 b'iter_files_recursive', 'breezy.bzr.smart.vfs', 'IterFilesRecursiveRequest', 679 info='read') 680request_handlers.register_lazy( 681 b'list_dir', 'breezy.bzr.smart.vfs', 'ListDirRequest', info='read') 682request_handlers.register_lazy( 683 b'mkdir', 'breezy.bzr.smart.vfs', 'MkdirRequest', info='semivfs') 684request_handlers.register_lazy( 685 b'move', 'breezy.bzr.smart.vfs', 'MoveRequest', info='semivfs') 686request_handlers.register_lazy( 687 b'put', 'breezy.bzr.smart.vfs', 'PutRequest', info='idem') 688request_handlers.register_lazy( 689 b'put_non_atomic', 'breezy.bzr.smart.vfs', 'PutNonAtomicRequest', info='idem') 690request_handlers.register_lazy( 691 b'readv', 'breezy.bzr.smart.vfs', 'ReadvRequest', info='read') 692request_handlers.register_lazy( 693 b'rename', 'breezy.bzr.smart.vfs', 'RenameRequest', info='semivfs') 694request_handlers.register_lazy( 695 b'Repository.add_signature_text', 'breezy.bzr.smart.repository', 696 'SmartServerRepositoryAddSignatureText', info='idem') 697request_handlers.register_lazy( 698 b'Repository.annotate_file_revision', 'breezy.bzr.smart.repository', 699 'SmartServerRepositoryAnnotateFileRevision', info='read') 700request_handlers.register_lazy( 701 b'Repository.all_revision_ids', 'breezy.bzr.smart.repository', 702 'SmartServerRepositoryAllRevisionIds', info='read') 703request_handlers.register_lazy( 704 b'PackRepository.autopack', 'breezy.bzr.smart.packrepository', 705 'SmartServerPackRepositoryAutopack', info='idem') 706request_handlers.register_lazy( 707 b'Repository.break_lock', 'breezy.bzr.smart.repository', 708 'SmartServerRepositoryBreakLock', info='idem') 709request_handlers.register_lazy( 710 b'Repository.gather_stats', 'breezy.bzr.smart.repository', 711 'SmartServerRepositoryGatherStats', info='read') 712request_handlers.register_lazy( 713 b'Repository.get_parent_map', 'breezy.bzr.smart.repository', 714 'SmartServerRepositoryGetParentMap', info='read') 715request_handlers.register_lazy( 716 b'Repository.get_revision_graph', 'breezy.bzr.smart.repository', 717 'SmartServerRepositoryGetRevisionGraph', info='read') 718request_handlers.register_lazy( 719 b'Repository.get_revision_signature_text', 'breezy.bzr.smart.repository', 720 'SmartServerRepositoryGetRevisionSignatureText', info='read') 721request_handlers.register_lazy( 722 b'Repository.has_revision', 'breezy.bzr.smart.repository', 723 'SmartServerRequestHasRevision', info='read') 724request_handlers.register_lazy( 725 b'Repository.has_signature_for_revision_id', 'breezy.bzr.smart.repository', 726 'SmartServerRequestHasSignatureForRevisionId', info='read') 727request_handlers.register_lazy( 728 b'Repository.insert_stream', 'breezy.bzr.smart.repository', 729 'SmartServerRepositoryInsertStream', info='stream') 730request_handlers.register_lazy( 731 b'Repository.insert_stream_1.19', 'breezy.bzr.smart.repository', 732 'SmartServerRepositoryInsertStream_1_19', info='stream') 733request_handlers.register_lazy( 734 b'Repository.insert_stream_locked', 'breezy.bzr.smart.repository', 735 'SmartServerRepositoryInsertStreamLocked', info='stream') 736request_handlers.register_lazy( 737 b'Repository.is_shared', 'breezy.bzr.smart.repository', 738 'SmartServerRepositoryIsShared', info='read') 739request_handlers.register_lazy( 740 b'Repository.iter_files_bytes', 'breezy.bzr.smart.repository', 741 'SmartServerRepositoryIterFilesBytes', info='read') 742request_handlers.register_lazy( 743 b'Repository.lock_write', 'breezy.bzr.smart.repository', 744 'SmartServerRepositoryLockWrite', info='semi') 745request_handlers.register_lazy( 746 b'Repository.make_working_trees', 'breezy.bzr.smart.repository', 747 'SmartServerRepositoryMakeWorkingTrees', info='read') 748request_handlers.register_lazy( 749 b'Repository.set_make_working_trees', 'breezy.bzr.smart.repository', 750 'SmartServerRepositorySetMakeWorkingTrees', info='idem') 751request_handlers.register_lazy( 752 b'Repository.unlock', 'breezy.bzr.smart.repository', 753 'SmartServerRepositoryUnlock', info='semi') 754request_handlers.register_lazy( 755 b'Repository.get_physical_lock_status', 'breezy.bzr.smart.repository', 756 'SmartServerRepositoryGetPhysicalLockStatus', info='read') 757request_handlers.register_lazy( 758 b'Repository.get_rev_id_for_revno', 'breezy.bzr.smart.repository', 759 'SmartServerRepositoryGetRevIdForRevno', info='read') 760request_handlers.register_lazy( 761 b'Repository.get_stream', 'breezy.bzr.smart.repository', 762 'SmartServerRepositoryGetStream', info='read') 763request_handlers.register_lazy( 764 b'Repository.get_stream_1.19', 'breezy.bzr.smart.repository', 765 'SmartServerRepositoryGetStream_1_19', info='read') 766request_handlers.register_lazy( 767 b'Repository.get_stream_for_missing_keys', 'breezy.bzr.smart.repository', 768 'SmartServerRepositoryGetStreamForMissingKeys', info='read') 769request_handlers.register_lazy( 770 b'Repository.iter_revisions', 'breezy.bzr.smart.repository', 771 'SmartServerRepositoryIterRevisions', info='read') 772request_handlers.register_lazy( 773 b'Repository.pack', 'breezy.bzr.smart.repository', 774 'SmartServerRepositoryPack', info='idem') 775request_handlers.register_lazy( 776 b'Repository.start_write_group', 'breezy.bzr.smart.repository', 777 'SmartServerRepositoryStartWriteGroup', info='semi') 778request_handlers.register_lazy( 779 b'Repository.commit_write_group', 'breezy.bzr.smart.repository', 780 'SmartServerRepositoryCommitWriteGroup', info='semi') 781request_handlers.register_lazy( 782 b'Repository.abort_write_group', 'breezy.bzr.smart.repository', 783 'SmartServerRepositoryAbortWriteGroup', info='semi') 784request_handlers.register_lazy( 785 b'Repository.check_write_group', 'breezy.bzr.smart.repository', 786 'SmartServerRepositoryCheckWriteGroup', info='read') 787request_handlers.register_lazy( 788 b'Repository.reconcile', 'breezy.bzr.smart.repository', 789 'SmartServerRepositoryReconcile', info='idem') 790request_handlers.register_lazy( 791 b'Repository.revision_archive', 'breezy.bzr.smart.repository', 792 'SmartServerRepositoryRevisionArchive', info='read') 793request_handlers.register_lazy( 794 b'Repository.tarball', 'breezy.bzr.smart.repository', 795 'SmartServerRepositoryTarball', info='read') 796request_handlers.register_lazy( 797 b'VersionedFileRepository.get_serializer_format', 'breezy.bzr.smart.repository', 798 'SmartServerRepositoryGetSerializerFormat', info='read') 799request_handlers.register_lazy( 800 b'VersionedFileRepository.get_inventories', 'breezy.bzr.smart.repository', 801 'SmartServerRepositoryGetInventories', info='read') 802request_handlers.register_lazy( 803 b'rmdir', 'breezy.bzr.smart.vfs', 'RmdirRequest', info='semivfs') 804request_handlers.register_lazy( 805 b'stat', 'breezy.bzr.smart.vfs', 'StatRequest', info='read') 806request_handlers.register_lazy( 807 b'Transport.is_readonly', 'breezy.bzr.smart.request', 808 'SmartServerIsReadonly', info='read') 809