73 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest()) |
73 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest()) |
74 |
74 |
75 |
75 |
76 # sensitive config sections affecting confighash |
76 # sensitive config sections affecting confighash |
77 _configsections = [ |
77 _configsections = [ |
78 'alias', # affects global state commands.table |
78 b'alias', # affects global state commands.table |
79 'eol', # uses setconfig('eol', ...) |
79 b'eol', # uses setconfig('eol', ...) |
80 'extdiff', # uisetup will register new commands |
80 b'extdiff', # uisetup will register new commands |
81 'extensions', |
81 b'extensions', |
82 ] |
82 ] |
83 |
83 |
84 _configsectionitems = [ |
84 _configsectionitems = [ |
85 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup |
85 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup |
86 ] |
86 ] |
87 |
87 |
88 # sensitive environment variables affecting confighash |
88 # sensitive environment variables affecting confighash |
89 _envre = re.compile( |
89 _envre = re.compile( |
90 br'''\A(?: |
90 br'''\A(?: |
119 sectionitems.append(ui.configitems(section)) |
119 sectionitems.append(ui.configitems(section)) |
120 for section, item in _configsectionitems: |
120 for section, item in _configsectionitems: |
121 sectionitems.append(ui.config(section, item)) |
121 sectionitems.append(ui.config(section, item)) |
122 sectionhash = _hashlist(sectionitems) |
122 sectionhash = _hashlist(sectionitems) |
123 # If $CHGHG is set, the change to $HG should not trigger a new chg server |
123 # If $CHGHG is set, the change to $HG should not trigger a new chg server |
124 if 'CHGHG' in encoding.environ: |
124 if b'CHGHG' in encoding.environ: |
125 ignored = {'HG'} |
125 ignored = {b'HG'} |
126 else: |
126 else: |
127 ignored = set() |
127 ignored = set() |
128 envitems = [ |
128 envitems = [ |
129 (k, v) |
129 (k, v) |
130 for k, v in encoding.environ.iteritems() |
130 for k, v in encoding.environ.iteritems() |
227 # b. or stdout is redirected by protectfinout(), |
227 # b. or stdout is redirected by protectfinout(), |
228 # because the chg client is not aware of these situations and |
228 # because the chg client is not aware of these situations and |
229 # will behave differently (i.e. write to stdout). |
229 # will behave differently (i.e. write to stdout). |
230 if ( |
230 if ( |
231 out is not self.fout |
231 out is not self.fout |
232 or not util.safehasattr(self.fout, 'fileno') |
232 or not util.safehasattr(self.fout, b'fileno') |
233 or self.fout.fileno() != procutil.stdout.fileno() |
233 or self.fout.fileno() != procutil.stdout.fileno() |
234 or self._finoutredirected |
234 or self._finoutredirected |
235 ): |
235 ): |
236 return procutil.system(cmd, environ=environ, cwd=cwd, out=out) |
236 return procutil.system(cmd, environ=environ, cwd=cwd, out=out) |
237 self.flush() |
237 self.flush() |
239 |
239 |
240 def _runpager(self, cmd, env=None): |
240 def _runpager(self, cmd, env=None): |
241 self._csystem( |
241 self._csystem( |
242 cmd, |
242 cmd, |
243 procutil.shellenviron(env), |
243 procutil.shellenviron(env), |
244 type='pager', |
244 type=b'pager', |
245 cmdtable={'attachio': attachio}, |
245 cmdtable={b'attachio': attachio}, |
246 ) |
246 ) |
247 return True |
247 return True |
248 |
248 |
249 return chgui(srcui) |
249 return chgui(srcui) |
250 |
250 |
251 |
251 |
252 def _loadnewui(srcui, args, cdebug): |
252 def _loadnewui(srcui, args, cdebug): |
253 from . import dispatch # avoid cycle |
253 from . import dispatch # avoid cycle |
254 |
254 |
255 newui = srcui.__class__.load() |
255 newui = srcui.__class__.load() |
256 for a in ['fin', 'fout', 'ferr', 'environ']: |
256 for a in [b'fin', b'fout', b'ferr', b'environ']: |
257 setattr(newui, a, getattr(srcui, a)) |
257 setattr(newui, a, getattr(srcui, a)) |
258 if util.safehasattr(srcui, '_csystem'): |
258 if util.safehasattr(srcui, b'_csystem'): |
259 newui._csystem = srcui._csystem |
259 newui._csystem = srcui._csystem |
260 |
260 |
261 # command line args |
261 # command line args |
262 options = dispatch._earlyparseopts(newui, args) |
262 options = dispatch._earlyparseopts(newui, args) |
263 dispatch._parseconfig(newui, options['config']) |
263 dispatch._parseconfig(newui, options[b'config']) |
264 |
264 |
265 # stolen from tortoisehg.util.copydynamicconfig() |
265 # stolen from tortoisehg.util.copydynamicconfig() |
266 for section, name, value in srcui.walkconfig(): |
266 for section, name, value in srcui.walkconfig(): |
267 source = srcui.configsource(section, name) |
267 source = srcui.configsource(section, name) |
268 if ':' in source or source == '--config' or source.startswith('$'): |
268 if b':' in source or source == b'--config' or source.startswith(b'$'): |
269 # path:line or command line, or environ |
269 # path:line or command line, or environ |
270 continue |
270 continue |
271 newui.setconfig(section, name, value, source) |
271 newui.setconfig(section, name, value, source) |
272 |
272 |
273 # load wd and repo config, copied from dispatch.py |
273 # load wd and repo config, copied from dispatch.py |
274 cwd = options['cwd'] |
274 cwd = options[b'cwd'] |
275 cwd = cwd and os.path.realpath(cwd) or None |
275 cwd = cwd and os.path.realpath(cwd) or None |
276 rpath = options['repository'] |
276 rpath = options[b'repository'] |
277 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd) |
277 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd) |
278 |
278 |
279 extensions.populateui(newui) |
279 extensions.populateui(newui) |
280 commandserver.setuplogging(newui, fp=cdebug) |
280 commandserver.setuplogging(newui, fp=cdebug) |
281 if newui is not newlui: |
281 if newui is not newlui: |
309 def __init__(self, in_, out, channel): |
309 def __init__(self, in_, out, channel): |
310 self.in_ = in_ |
310 self.in_ = in_ |
311 self.out = out |
311 self.out = out |
312 self.channel = channel |
312 self.channel = channel |
313 |
313 |
314 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None): |
314 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None): |
315 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or '.')] |
315 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')] |
316 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems()) |
316 args.extend(b'%s=%s' % (k, v) for k, v in environ.iteritems()) |
317 data = '\0'.join(args) |
317 data = b'\0'.join(args) |
318 self.out.write(struct.pack('>cI', self.channel, len(data))) |
318 self.out.write(struct.pack(b'>cI', self.channel, len(data))) |
319 self.out.write(data) |
319 self.out.write(data) |
320 self.out.flush() |
320 self.out.flush() |
321 |
321 |
322 if type == 'system': |
322 if type == b'system': |
323 length = self.in_.read(4) |
323 length = self.in_.read(4) |
324 (length,) = struct.unpack('>I', length) |
324 (length,) = struct.unpack(b'>I', length) |
325 if length != 4: |
325 if length != 4: |
326 raise error.Abort(_('invalid response')) |
326 raise error.Abort(_(b'invalid response')) |
327 (rc,) = struct.unpack('>i', self.in_.read(4)) |
327 (rc,) = struct.unpack(b'>i', self.in_.read(4)) |
328 return rc |
328 return rc |
329 elif type == 'pager': |
329 elif type == b'pager': |
330 while True: |
330 while True: |
331 cmd = self.in_.readline()[:-1] |
331 cmd = self.in_.readline()[:-1] |
332 if not cmd: |
332 if not cmd: |
333 break |
333 break |
334 if cmdtable and cmd in cmdtable: |
334 if cmdtable and cmd in cmdtable: |
335 cmdtable[cmd]() |
335 cmdtable[cmd]() |
336 else: |
336 else: |
337 raise error.Abort(_('unexpected command: %s') % cmd) |
337 raise error.Abort(_(b'unexpected command: %s') % cmd) |
338 else: |
338 else: |
339 raise error.ProgrammingError('invalid S channel type: %s' % type) |
339 raise error.ProgrammingError(b'invalid S channel type: %s' % type) |
340 |
340 |
341 |
341 |
342 _iochannels = [ |
342 _iochannels = [ |
343 # server.ch, ui.fp, mode |
343 # server.ch, ui.fp, mode |
344 ('cin', 'fin', r'rb'), |
344 (b'cin', b'fin', r'rb'), |
345 ('cout', 'fout', r'wb'), |
345 (b'cout', b'fout', r'wb'), |
346 ('cerr', 'ferr', r'wb'), |
346 (b'cerr', b'ferr', r'wb'), |
347 ] |
347 ] |
348 |
348 |
349 |
349 |
350 class chgcmdserver(commandserver.server): |
350 class chgcmdserver(commandserver.server): |
351 def __init__( |
351 def __init__( |
352 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress |
352 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress |
353 ): |
353 ): |
354 super(chgcmdserver, self).__init__( |
354 super(chgcmdserver, self).__init__( |
355 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio), |
355 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio), |
356 repo, |
356 repo, |
357 fin, |
357 fin, |
358 fout, |
358 fout, |
359 prereposetups, |
359 prereposetups, |
360 ) |
360 ) |
363 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" |
363 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" |
364 self.hashstate = hashstate |
364 self.hashstate = hashstate |
365 self.baseaddress = baseaddress |
365 self.baseaddress = baseaddress |
366 if hashstate is not None: |
366 if hashstate is not None: |
367 self.capabilities = self.capabilities.copy() |
367 self.capabilities = self.capabilities.copy() |
368 self.capabilities['validate'] = chgcmdserver.validate |
368 self.capabilities[b'validate'] = chgcmdserver.validate |
369 |
369 |
370 def cleanup(self): |
370 def cleanup(self): |
371 super(chgcmdserver, self).cleanup() |
371 super(chgcmdserver, self).cleanup() |
372 # dispatch._runcatch() does not flush outputs if exception is not |
372 # dispatch._runcatch() does not flush outputs if exception is not |
373 # handled by dispatch._dispatch() |
373 # handled by dispatch._dispatch() |
379 """Attach to client's stdio passed via unix domain socket; all |
379 """Attach to client's stdio passed via unix domain socket; all |
380 channels except cresult will no longer be used |
380 channels except cresult will no longer be used |
381 """ |
381 """ |
382 # tell client to sendmsg() with 1-byte payload, which makes it |
382 # tell client to sendmsg() with 1-byte payload, which makes it |
383 # distinctive from "attachio\n" command consumed by client.read() |
383 # distinctive from "attachio\n" command consumed by client.read() |
384 self.clientsock.sendall(struct.pack('>cI', 'I', 1)) |
384 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1)) |
385 clientfds = util.recvfds(self.clientsock.fileno()) |
385 clientfds = util.recvfds(self.clientsock.fileno()) |
386 self.ui.log('chgserver', 'received fds: %r\n', clientfds) |
386 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds) |
387 |
387 |
388 ui = self.ui |
388 ui = self.ui |
389 ui.flush() |
389 ui.flush() |
390 self._saveio() |
390 self._saveio() |
391 for fd, (cn, fn, mode) in zip(clientfds, _iochannels): |
391 for fd, (cn, fn, mode) in zip(clientfds, _iochannels): |
466 try: |
466 try: |
467 self.ui, lui = _loadnewui(self.ui, args, self.cdebug) |
467 self.ui, lui = _loadnewui(self.ui, args, self.cdebug) |
468 except error.ParseError as inst: |
468 except error.ParseError as inst: |
469 dispatch._formatparse(self.ui.warn, inst) |
469 dispatch._formatparse(self.ui.warn, inst) |
470 self.ui.flush() |
470 self.ui.flush() |
471 self.cresult.write('exit 255') |
471 self.cresult.write(b'exit 255') |
472 return |
472 return |
473 except error.Abort as inst: |
473 except error.Abort as inst: |
474 self.ui.error(_("abort: %s\n") % inst) |
474 self.ui.error(_(b"abort: %s\n") % inst) |
475 if inst.hint: |
475 if inst.hint: |
476 self.ui.error(_("(%s)\n") % inst.hint) |
476 self.ui.error(_(b"(%s)\n") % inst.hint) |
477 self.ui.flush() |
477 self.ui.flush() |
478 self.cresult.write('exit 255') |
478 self.cresult.write(b'exit 255') |
479 return |
479 return |
480 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths) |
480 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths) |
481 insts = [] |
481 insts = [] |
482 if newhash.mtimehash != self.hashstate.mtimehash: |
482 if newhash.mtimehash != self.hashstate.mtimehash: |
483 addr = _hashaddress(self.baseaddress, self.hashstate.confighash) |
483 addr = _hashaddress(self.baseaddress, self.hashstate.confighash) |
484 insts.append('unlink %s' % addr) |
484 insts.append(b'unlink %s' % addr) |
485 # mtimehash is empty if one or more extensions fail to load. |
485 # mtimehash is empty if one or more extensions fail to load. |
486 # to be compatible with hg, still serve the client this time. |
486 # to be compatible with hg, still serve the client this time. |
487 if self.hashstate.mtimehash: |
487 if self.hashstate.mtimehash: |
488 insts.append('reconnect') |
488 insts.append(b'reconnect') |
489 if newhash.confighash != self.hashstate.confighash: |
489 if newhash.confighash != self.hashstate.confighash: |
490 addr = _hashaddress(self.baseaddress, newhash.confighash) |
490 addr = _hashaddress(self.baseaddress, newhash.confighash) |
491 insts.append('redirect %s' % addr) |
491 insts.append(b'redirect %s' % addr) |
492 self.ui.log('chgserver', 'validate: %s\n', stringutil.pprint(insts)) |
492 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts)) |
493 self.cresult.write('\0'.join(insts) or '\0') |
493 self.cresult.write(b'\0'.join(insts) or b'\0') |
494 |
494 |
495 def chdir(self): |
495 def chdir(self): |
496 """Change current directory |
496 """Change current directory |
497 |
497 |
498 Note that the behavior of --cwd option is bit different from this. |
498 Note that the behavior of --cwd option is bit different from this. |
499 It does not affect --config parameter. |
499 It does not affect --config parameter. |
500 """ |
500 """ |
501 path = self._readstr() |
501 path = self._readstr() |
502 if not path: |
502 if not path: |
503 return |
503 return |
504 self.ui.log('chgserver', 'chdir to %r\n', path) |
504 self.ui.log(b'chgserver', b'chdir to %r\n', path) |
505 os.chdir(path) |
505 os.chdir(path) |
506 |
506 |
507 def setumask(self): |
507 def setumask(self): |
508 """Change umask (DEPRECATED)""" |
508 """Change umask (DEPRECATED)""" |
509 # BUG: this does not follow the message frame structure, but kept for |
509 # BUG: this does not follow the message frame structure, but kept for |
512 |
512 |
513 def setumask2(self): |
513 def setumask2(self): |
514 """Change umask""" |
514 """Change umask""" |
515 data = self._readstr() |
515 data = self._readstr() |
516 if len(data) != 4: |
516 if len(data) != 4: |
517 raise ValueError('invalid mask length in setumask2 request') |
517 raise ValueError(b'invalid mask length in setumask2 request') |
518 self._setumask(data) |
518 self._setumask(data) |
519 |
519 |
520 def _setumask(self, data): |
520 def _setumask(self, data): |
521 mask = struct.unpack('>I', data)[0] |
521 mask = struct.unpack(b'>I', data)[0] |
522 self.ui.log('chgserver', 'setumask %r\n', mask) |
522 self.ui.log(b'chgserver', b'setumask %r\n', mask) |
523 os.umask(mask) |
523 os.umask(mask) |
524 |
524 |
525 def runcommand(self): |
525 def runcommand(self): |
526 # pager may be attached within the runcommand session, which should |
526 # pager may be attached within the runcommand session, which should |
527 # be detached at the end of the session. otherwise the pager wouldn't |
527 # be detached at the end of the session. otherwise the pager wouldn't |
539 |
539 |
540 Note that not all variables can make an effect on the running process. |
540 Note that not all variables can make an effect on the running process. |
541 """ |
541 """ |
542 l = self._readlist() |
542 l = self._readlist() |
543 try: |
543 try: |
544 newenv = dict(s.split('=', 1) for s in l) |
544 newenv = dict(s.split(b'=', 1) for s in l) |
545 except ValueError: |
545 except ValueError: |
546 raise ValueError('unexpected value in setenv request') |
546 raise ValueError(b'unexpected value in setenv request') |
547 self.ui.log('chgserver', 'setenv: %r\n', sorted(newenv.keys())) |
547 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys())) |
548 encoding.environ.clear() |
548 encoding.environ.clear() |
549 encoding.environ.update(newenv) |
549 encoding.environ.update(newenv) |
550 |
550 |
551 capabilities = commandserver.server.capabilities.copy() |
551 capabilities = commandserver.server.capabilities.copy() |
552 capabilities.update( |
552 capabilities.update( |
553 { |
553 { |
554 'attachio': attachio, |
554 b'attachio': attachio, |
555 'chdir': chdir, |
555 b'chdir': chdir, |
556 'runcommand': runcommand, |
556 b'runcommand': runcommand, |
557 'setenv': setenv, |
557 b'setenv': setenv, |
558 'setumask': setumask, |
558 b'setumask': setumask, |
559 'setumask2': setumask2, |
559 b'setumask2': setumask2, |
560 } |
560 } |
561 ) |
561 ) |
562 |
562 |
563 if util.safehasattr(procutil, 'setprocname'): |
563 if util.safehasattr(procutil, b'setprocname'): |
564 |
564 |
565 def setprocname(self): |
565 def setprocname(self): |
566 """Change process title""" |
566 """Change process title""" |
567 name = self._readstr() |
567 name = self._readstr() |
568 self.ui.log('chgserver', 'setprocname: %r\n', name) |
568 self.ui.log(b'chgserver', b'setprocname: %r\n', name) |
569 procutil.setprocname(name) |
569 procutil.setprocname(name) |
570 |
570 |
571 capabilities['setprocname'] = setprocname |
571 capabilities[b'setprocname'] = setprocname |
572 |
572 |
573 |
573 |
574 def _tempaddress(address): |
574 def _tempaddress(address): |
575 return '%s.%d.tmp' % (address, os.getpid()) |
575 return b'%s.%d.tmp' % (address, os.getpid()) |
576 |
576 |
577 |
577 |
578 def _hashaddress(address, hashstr): |
578 def _hashaddress(address, hashstr): |
579 # if the basename of address contains '.', use only the left part. this |
579 # if the basename of address contains '.', use only the left part. this |
580 # makes it possible for the client to pass 'server.tmp$PID' and follow by |
580 # makes it possible for the client to pass 'server.tmp$PID' and follow by |
581 # an atomic rename to avoid locking when spawning new servers. |
581 # an atomic rename to avoid locking when spawning new servers. |
582 dirname, basename = os.path.split(address) |
582 dirname, basename = os.path.split(address) |
583 basename = basename.split('.', 1)[0] |
583 basename = basename.split(b'.', 1)[0] |
584 return '%s-%s' % (os.path.join(dirname, basename), hashstr) |
584 return b'%s-%s' % (os.path.join(dirname, basename), hashstr) |
585 |
585 |
586 |
586 |
587 class chgunixservicehandler(object): |
587 class chgunixservicehandler(object): |
588 """Set of operations for chg services""" |
588 """Set of operations for chg services""" |
589 |
589 |
590 pollinterval = 1 # [sec] |
590 pollinterval = 1 # [sec] |
591 |
591 |
592 def __init__(self, ui): |
592 def __init__(self, ui): |
593 self.ui = ui |
593 self.ui = ui |
594 self._idletimeout = ui.configint('chgserver', 'idletimeout') |
594 self._idletimeout = ui.configint(b'chgserver', b'idletimeout') |
595 self._lastactive = time.time() |
595 self._lastactive = time.time() |
596 |
596 |
597 def bindsocket(self, sock, address): |
597 def bindsocket(self, sock, address): |
598 self._inithashstate(address) |
598 self._inithashstate(address) |
599 self._checkextensions() |
599 self._checkextensions() |
601 self._createsymlink() |
601 self._createsymlink() |
602 # no "listening at" message should be printed to simulate hg behavior |
602 # no "listening at" message should be printed to simulate hg behavior |
603 |
603 |
604 def _inithashstate(self, address): |
604 def _inithashstate(self, address): |
605 self._baseaddress = address |
605 self._baseaddress = address |
606 if self.ui.configbool('chgserver', 'skiphash'): |
606 if self.ui.configbool(b'chgserver', b'skiphash'): |
607 self._hashstate = None |
607 self._hashstate = None |
608 self._realaddress = address |
608 self._realaddress = address |
609 return |
609 return |
610 self._hashstate = hashstate.fromui(self.ui) |
610 self._hashstate = hashstate.fromui(self.ui) |
611 self._realaddress = _hashaddress(address, self._hashstate.confighash) |
611 self._realaddress = _hashaddress(address, self._hashstate.confighash) |
615 return |
615 return |
616 if extensions.notloaded(): |
616 if extensions.notloaded(): |
617 # one or more extensions failed to load. mtimehash becomes |
617 # one or more extensions failed to load. mtimehash becomes |
618 # meaningless because we do not know the paths of those extensions. |
618 # meaningless because we do not know the paths of those extensions. |
619 # set mtimehash to an illegal hash value to invalidate the server. |
619 # set mtimehash to an illegal hash value to invalidate the server. |
620 self._hashstate.mtimehash = '' |
620 self._hashstate.mtimehash = b'' |
621 |
621 |
622 def _bind(self, sock): |
622 def _bind(self, sock): |
623 # use a unique temp address so we can stat the file and do ownership |
623 # use a unique temp address so we can stat the file and do ownership |
624 # check later |
624 # check later |
625 tempaddress = _tempaddress(self._realaddress) |
625 tempaddress = _tempaddress(self._realaddress) |
687 # CHGINTERNALMARK is set by chg client. It is an indication of things are |
687 # CHGINTERNALMARK is set by chg client. It is an indication of things are |
688 # started by chg so other code can do things accordingly, like disabling |
688 # started by chg so other code can do things accordingly, like disabling |
689 # demandimport or detecting chg client started by chg client. When executed |
689 # demandimport or detecting chg client started by chg client. When executed |
690 # here, CHGINTERNALMARK is no longer useful and hence dropped to make |
690 # here, CHGINTERNALMARK is no longer useful and hence dropped to make |
691 # environ cleaner. |
691 # environ cleaner. |
692 if 'CHGINTERNALMARK' in encoding.environ: |
692 if b'CHGINTERNALMARK' in encoding.environ: |
693 del encoding.environ['CHGINTERNALMARK'] |
693 del encoding.environ[b'CHGINTERNALMARK'] |
694 |
694 |
695 if repo: |
695 if repo: |
696 # one chgserver can serve multiple repos. drop repo information |
696 # one chgserver can serve multiple repos. drop repo information |
697 ui.setconfig('bundle', 'mainreporoot', '', 'repo') |
697 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo') |
698 h = chgunixservicehandler(ui) |
698 h = chgunixservicehandler(ui) |
699 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h) |
699 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h) |