##// END OF EJS Templates
chgserver: implement chgui._runpager...
Jun Wu -
r30740:493935e0 default
parent child Browse files
Show More
@@ -1,651 +1,656
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """command server extension for cHg
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'getpager' command
20 20 checks if pager is enabled and which pager should be executed
21 21
22 22 'setenv' command
23 23 replace os.environ completely
24 24
25 25 'setumask' command
26 26 set umask
27 27
28 28 'validate' command
29 29 reload the config and check if the server is up to date
30 30
31 31 Config
32 32 ------
33 33
34 34 ::
35 35
36 36 [chgserver]
37 37 idletimeout = 3600 # seconds, after which an idle server will exit
38 38 skiphash = False # whether to skip config or env change checks
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import errno
44 44 import hashlib
45 45 import inspect
46 46 import os
47 47 import re
48 48 import signal
49 49 import struct
50 50 import time
51 51
52 52 from .i18n import _
53 53
54 54 from . import (
55 55 cmdutil,
56 56 commandserver,
57 57 encoding,
58 58 error,
59 59 extensions,
60 60 osutil,
61 61 pycompat,
62 62 util,
63 63 )
64 64
65 65 _log = commandserver.log
66 66
67 67 def _hashlist(items):
68 68 """return sha1 hexdigest for a list"""
69 69 return hashlib.sha1(str(items)).hexdigest()
70 70
71 71 # sensitive config sections affecting confighash
72 72 _configsections = [
73 73 'alias', # affects global state commands.table
74 74 'extdiff', # uisetup will register new commands
75 75 'extensions',
76 76 ]
77 77
78 78 # sensitive environment variables affecting confighash
79 79 _envre = re.compile(r'''\A(?:
80 80 CHGHG
81 81 |HG(?:[A-Z].*)?
82 82 |LANG(?:UAGE)?
83 83 |LC_.*
84 84 |LD_.*
85 85 |PATH
86 86 |PYTHON.*
87 87 |TERM(?:INFO)?
88 88 |TZ
89 89 )\Z''', re.X)
90 90
91 91 def _confighash(ui):
92 92 """return a quick hash for detecting config/env changes
93 93
94 94 confighash is the hash of sensitive config items and environment variables.
95 95
96 96 for chgserver, it is designed that once confighash changes, the server is
97 97 not qualified to serve its client and should redirect the client to a new
98 98 server. different from mtimehash, confighash change will not mark the
99 99 server outdated and exit since the user can have different configs at the
100 100 same time.
101 101 """
102 102 sectionitems = []
103 103 for section in _configsections:
104 104 sectionitems.append(ui.configitems(section))
105 105 sectionhash = _hashlist(sectionitems)
106 106 envitems = [(k, v) for k, v in encoding.environ.iteritems()
107 107 if _envre.match(k)]
108 108 envhash = _hashlist(sorted(envitems))
109 109 return sectionhash[:6] + envhash[:6]
110 110
111 111 def _getmtimepaths(ui):
112 112 """get a list of paths that should be checked to detect change
113 113
114 114 The list will include:
115 115 - extensions (will not cover all files for complex extensions)
116 116 - mercurial/__version__.py
117 117 - python binary
118 118 """
119 119 modules = [m for n, m in extensions.extensions(ui)]
120 120 try:
121 121 from . import __version__
122 122 modules.append(__version__)
123 123 except ImportError:
124 124 pass
125 125 files = [pycompat.sysexecutable]
126 126 for m in modules:
127 127 try:
128 128 files.append(inspect.getabsfile(m))
129 129 except TypeError:
130 130 pass
131 131 return sorted(set(files))
132 132
133 133 def _mtimehash(paths):
134 134 """return a quick hash for detecting file changes
135 135
136 136 mtimehash calls stat on given paths and calculate a hash based on size and
137 137 mtime of each file. mtimehash does not read file content because reading is
138 138 expensive. therefore it's not 100% reliable for detecting content changes.
139 139 it's possible to return different hashes for same file contents.
140 140 it's also possible to return a same hash for different file contents for
141 141 some carefully crafted situation.
142 142
143 143 for chgserver, it is designed that once mtimehash changes, the server is
144 144 considered outdated immediately and should no longer provide service.
145 145
146 146 mtimehash is not included in confighash because we only know the paths of
147 147 extensions after importing them (there is imp.find_module but that faces
148 148 race conditions). We need to calculate confighash without importing.
149 149 """
150 150 def trystat(path):
151 151 try:
152 152 st = os.stat(path)
153 153 return (st.st_mtime, st.st_size)
154 154 except OSError:
155 155 # could be ENOENT, EPERM etc. not fatal in any case
156 156 pass
157 157 return _hashlist(map(trystat, paths))[:12]
158 158
159 159 class hashstate(object):
160 160 """a structure storing confighash, mtimehash, paths used for mtimehash"""
161 161 def __init__(self, confighash, mtimehash, mtimepaths):
162 162 self.confighash = confighash
163 163 self.mtimehash = mtimehash
164 164 self.mtimepaths = mtimepaths
165 165
166 166 @staticmethod
167 167 def fromui(ui, mtimepaths=None):
168 168 if mtimepaths is None:
169 169 mtimepaths = _getmtimepaths(ui)
170 170 confighash = _confighash(ui)
171 171 mtimehash = _mtimehash(mtimepaths)
172 172 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
173 173 return hashstate(confighash, mtimehash, mtimepaths)
174 174
175 175 # copied from hgext/pager.py:uisetup()
176 176 def _setuppagercmd(ui, options, cmd):
177 177 from . import commands # avoid cycle
178 178
179 179 if not ui.formatted():
180 180 return
181 181
182 182 p = ui.config("pager", "pager", encoding.environ.get("PAGER"))
183 183 usepager = False
184 184 always = util.parsebool(options['pager'])
185 185 auto = options['pager'] == 'auto'
186 186
187 187 if not p:
188 188 pass
189 189 elif always:
190 190 usepager = True
191 191 elif not auto:
192 192 usepager = False
193 193 else:
194 194 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
195 195 attend = ui.configlist('pager', 'attend', attended)
196 196 ignore = ui.configlist('pager', 'ignore')
197 197 cmds, _ = cmdutil.findcmd(cmd, commands.table)
198 198
199 199 for cmd in cmds:
200 200 var = 'attend-%s' % cmd
201 201 if ui.config('pager', var):
202 202 usepager = ui.configbool('pager', var)
203 203 break
204 204 if (cmd in attend or
205 205 (cmd not in ignore and not attend)):
206 206 usepager = True
207 207 break
208 208
209 209 if usepager:
210 210 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
211 211 ui.setconfig('ui', 'interactive', False, 'pager')
212 212 return p
213 213
214 def _newchgui(srcui, csystem):
214 def _newchgui(srcui, csystem, attachio):
215 215 class chgui(srcui.__class__):
216 216 def __init__(self, src=None):
217 217 super(chgui, self).__init__(src)
218 218 if src:
219 219 self._csystem = getattr(src, '_csystem', csystem)
220 220 else:
221 221 self._csystem = csystem
222 222
223 223 def system(self, cmd, environ=None, cwd=None, onerr=None,
224 224 errprefix=None):
225 225 # fallback to the original system method if the output needs to be
226 226 # captured (to self._buffers), or the output stream is not stdout
227 227 # (e.g. stderr, cStringIO), because the chg client is not aware of
228 228 # these situations and will behave differently (write to stdout).
229 229 if (any(s[1] for s in self._bufferstates)
230 230 or not util.safehasattr(self.fout, 'fileno')
231 231 or self.fout.fileno() != util.stdout.fileno()):
232 232 return super(chgui, self).system(cmd, environ, cwd, onerr,
233 233 errprefix)
234 234 self.flush()
235 235 rc = self._csystem(cmd, util.shellenviron(environ), cwd)
236 236 if rc and onerr:
237 237 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
238 238 util.explainexit(rc)[0])
239 239 if errprefix:
240 240 errmsg = '%s: %s' % (errprefix, errmsg)
241 241 raise onerr(errmsg)
242 242 return rc
243 243
244 def _runpager(self, cmd):
245 self._csystem(cmd, util.shellenviron(), type='pager',
246 cmdtable={'attachio': attachio})
247
244 248 return chgui(srcui)
245 249
246 250 def _loadnewui(srcui, args):
247 251 from . import dispatch # avoid cycle
248 252
249 253 newui = srcui.__class__.load()
250 254 for a in ['fin', 'fout', 'ferr', 'environ']:
251 255 setattr(newui, a, getattr(srcui, a))
252 256 if util.safehasattr(srcui, '_csystem'):
253 257 newui._csystem = srcui._csystem
254 258
255 259 # command line args
256 260 args = args[:]
257 261 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
258 262
259 263 # stolen from tortoisehg.util.copydynamicconfig()
260 264 for section, name, value in srcui.walkconfig():
261 265 source = srcui.configsource(section, name)
262 266 if ':' in source or source == '--config':
263 267 # path:line or command line
264 268 continue
265 269 newui.setconfig(section, name, value, source)
266 270
267 271 # load wd and repo config, copied from dispatch.py
268 272 cwds = dispatch._earlygetopt(['--cwd'], args)
269 273 cwd = cwds and os.path.realpath(cwds[-1]) or None
270 274 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
271 275 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
272 276
273 277 return (newui, newlui)
274 278
275 279 class channeledsystem(object):
276 280 """Propagate ui.system() request in the following format:
277 281
278 282 payload length (unsigned int),
279 283 type, '\0',
280 284 cmd, '\0',
281 285 cwd, '\0',
282 286 envkey, '=', val, '\0',
283 287 ...
284 288 envkey, '=', val
285 289
286 290 if type == 'system', waits for:
287 291
288 292 exitcode length (unsigned int),
289 293 exitcode (int)
290 294
291 295 if type == 'pager', repetitively waits for a command name ending with '\n'
292 296 and executes it defined by cmdtable, or exits the loop if the command name
293 297 is empty.
294 298 """
295 299 def __init__(self, in_, out, channel):
296 300 self.in_ = in_
297 301 self.out = out
298 302 self.channel = channel
299 303
300 304 def __call__(self, cmd, environ, cwd=None, type='system', cmdtable=None):
301 305 args = [type, util.quotecommand(cmd), os.path.abspath(cwd or '.')]
302 306 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
303 307 data = '\0'.join(args)
304 308 self.out.write(struct.pack('>cI', self.channel, len(data)))
305 309 self.out.write(data)
306 310 self.out.flush()
307 311
308 312 if type == 'system':
309 313 length = self.in_.read(4)
310 314 length, = struct.unpack('>I', length)
311 315 if length != 4:
312 316 raise error.Abort(_('invalid response'))
313 317 rc, = struct.unpack('>i', self.in_.read(4))
314 318 return rc
315 319 elif type == 'pager':
316 320 while True:
317 321 cmd = self.in_.readline()[:-1]
318 322 if not cmd:
319 323 break
320 324 if cmdtable and cmd in cmdtable:
321 325 _log('pager subcommand: %s' % cmd)
322 326 cmdtable[cmd]()
323 327 else:
324 328 raise error.Abort(_('unexpected command: %s') % cmd)
325 329 else:
326 330 raise error.ProgrammingError('invalid S channel type: %s' % type)
327 331
328 332 _iochannels = [
329 333 # server.ch, ui.fp, mode
330 334 ('cin', 'fin', 'rb'),
331 335 ('cout', 'fout', 'wb'),
332 336 ('cerr', 'ferr', 'wb'),
333 337 ]
334 338
335 339 class chgcmdserver(commandserver.server):
336 340 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
337 341 super(chgcmdserver, self).__init__(
338 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
342 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
343 repo, fin, fout)
339 344 self.clientsock = sock
340 345 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
341 346 self.hashstate = hashstate
342 347 self.baseaddress = baseaddress
343 348 if hashstate is not None:
344 349 self.capabilities = self.capabilities.copy()
345 350 self.capabilities['validate'] = chgcmdserver.validate
346 351
347 352 def cleanup(self):
348 353 super(chgcmdserver, self).cleanup()
349 354 # dispatch._runcatch() does not flush outputs if exception is not
350 355 # handled by dispatch._dispatch()
351 356 self.ui.flush()
352 357 self._restoreio()
353 358
354 359 def attachio(self):
355 360 """Attach to client's stdio passed via unix domain socket; all
356 361 channels except cresult will no longer be used
357 362 """
358 363 # tell client to sendmsg() with 1-byte payload, which makes it
359 364 # distinctive from "attachio\n" command consumed by client.read()
360 365 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
361 366 clientfds = osutil.recvfds(self.clientsock.fileno())
362 367 _log('received fds: %r\n' % clientfds)
363 368
364 369 ui = self.ui
365 370 ui.flush()
366 371 first = self._saveio()
367 372 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
368 373 assert fd > 0
369 374 fp = getattr(ui, fn)
370 375 os.dup2(fd, fp.fileno())
371 376 os.close(fd)
372 377 if not first:
373 378 continue
374 379 # reset buffering mode when client is first attached. as we want
375 380 # to see output immediately on pager, the mode stays unchanged
376 381 # when client re-attached. ferr is unchanged because it should
377 382 # be unbuffered no matter if it is a tty or not.
378 383 if fn == 'ferr':
379 384 newfp = fp
380 385 else:
381 386 # make it line buffered explicitly because the default is
382 387 # decided on first write(), where fout could be a pager.
383 388 if fp.isatty():
384 389 bufsize = 1 # line buffered
385 390 else:
386 391 bufsize = -1 # system default
387 392 newfp = os.fdopen(fp.fileno(), mode, bufsize)
388 393 setattr(ui, fn, newfp)
389 394 setattr(self, cn, newfp)
390 395
391 396 self.cresult.write(struct.pack('>i', len(clientfds)))
392 397
393 398 def _saveio(self):
394 399 if self._oldios:
395 400 return False
396 401 ui = self.ui
397 402 for cn, fn, _mode in _iochannels:
398 403 ch = getattr(self, cn)
399 404 fp = getattr(ui, fn)
400 405 fd = os.dup(fp.fileno())
401 406 self._oldios.append((ch, fp, fd))
402 407 return True
403 408
404 409 def _restoreio(self):
405 410 ui = self.ui
406 411 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
407 412 newfp = getattr(ui, fn)
408 413 # close newfp while it's associated with client; otherwise it
409 414 # would be closed when newfp is deleted
410 415 if newfp is not fp:
411 416 newfp.close()
412 417 # restore original fd: fp is open again
413 418 os.dup2(fd, fp.fileno())
414 419 os.close(fd)
415 420 setattr(self, cn, ch)
416 421 setattr(ui, fn, fp)
417 422 del self._oldios[:]
418 423
419 424 def validate(self):
420 425 """Reload the config and check if the server is up to date
421 426
422 427 Read a list of '\0' separated arguments.
423 428 Write a non-empty list of '\0' separated instruction strings or '\0'
424 429 if the list is empty.
425 430 An instruction string could be either:
426 431 - "unlink $path", the client should unlink the path to stop the
427 432 outdated server.
428 433 - "redirect $path", the client should attempt to connect to $path
429 434 first. If it does not work, start a new server. It implies
430 435 "reconnect".
431 436 - "exit $n", the client should exit directly with code n.
432 437 This may happen if we cannot parse the config.
433 438 - "reconnect", the client should close the connection and
434 439 reconnect.
435 440 If neither "reconnect" nor "redirect" is included in the instruction
436 441 list, the client can continue with this server after completing all
437 442 the instructions.
438 443 """
439 444 from . import dispatch # avoid cycle
440 445
441 446 args = self._readlist()
442 447 try:
443 448 self.ui, lui = _loadnewui(self.ui, args)
444 449 except error.ParseError as inst:
445 450 dispatch._formatparse(self.ui.warn, inst)
446 451 self.ui.flush()
447 452 self.cresult.write('exit 255')
448 453 return
449 454 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
450 455 insts = []
451 456 if newhash.mtimehash != self.hashstate.mtimehash:
452 457 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
453 458 insts.append('unlink %s' % addr)
454 459 # mtimehash is empty if one or more extensions fail to load.
455 460 # to be compatible with hg, still serve the client this time.
456 461 if self.hashstate.mtimehash:
457 462 insts.append('reconnect')
458 463 if newhash.confighash != self.hashstate.confighash:
459 464 addr = _hashaddress(self.baseaddress, newhash.confighash)
460 465 insts.append('redirect %s' % addr)
461 466 _log('validate: %s\n' % insts)
462 467 self.cresult.write('\0'.join(insts) or '\0')
463 468
464 469 def chdir(self):
465 470 """Change current directory
466 471
467 472 Note that the behavior of --cwd option is bit different from this.
468 473 It does not affect --config parameter.
469 474 """
470 475 path = self._readstr()
471 476 if not path:
472 477 return
473 478 _log('chdir to %r\n' % path)
474 479 os.chdir(path)
475 480
476 481 def setumask(self):
477 482 """Change umask"""
478 483 mask = struct.unpack('>I', self._read(4))[0]
479 484 _log('setumask %r\n' % mask)
480 485 os.umask(mask)
481 486
482 487 def getpager(self):
483 488 """Read cmdargs and write pager command to r-channel if enabled
484 489
485 490 If pager isn't enabled, this writes '\0' because channeledoutput
486 491 does not allow to write empty data.
487 492 """
488 493 from . import dispatch # avoid cycle
489 494
490 495 args = self._readlist()
491 496 try:
492 497 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
493 498 args)
494 499 except (error.Abort, error.AmbiguousCommand, error.CommandError,
495 500 error.UnknownCommand):
496 501 cmd = None
497 502 options = {}
498 503 if not cmd or 'pager' not in options:
499 504 self.cresult.write('\0')
500 505 return
501 506
502 507 pagercmd = _setuppagercmd(self.ui, options, cmd)
503 508 if pagercmd:
504 509 # Python's SIGPIPE is SIG_IGN by default. change to SIG_DFL so
505 510 # we can exit if the pipe to the pager is closed
506 511 if util.safehasattr(signal, 'SIGPIPE') and \
507 512 signal.getsignal(signal.SIGPIPE) == signal.SIG_IGN:
508 513 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
509 514 self.cresult.write(pagercmd)
510 515 else:
511 516 self.cresult.write('\0')
512 517
513 518 def runcommand(self):
514 519 return super(chgcmdserver, self).runcommand()
515 520
516 521 def setenv(self):
517 522 """Clear and update os.environ
518 523
519 524 Note that not all variables can make an effect on the running process.
520 525 """
521 526 l = self._readlist()
522 527 try:
523 528 newenv = dict(s.split('=', 1) for s in l)
524 529 except ValueError:
525 530 raise ValueError('unexpected value in setenv request')
526 531 _log('setenv: %r\n' % sorted(newenv.keys()))
527 532 encoding.environ.clear()
528 533 encoding.environ.update(newenv)
529 534
530 535 capabilities = commandserver.server.capabilities.copy()
531 536 capabilities.update({'attachio': attachio,
532 537 'chdir': chdir,
533 538 'getpager': getpager,
534 539 'runcommand': runcommand,
535 540 'setenv': setenv,
536 541 'setumask': setumask})
537 542
538 543 def _tempaddress(address):
539 544 return '%s.%d.tmp' % (address, os.getpid())
540 545
541 546 def _hashaddress(address, hashstr):
542 547 # if the basename of address contains '.', use only the left part. this
543 548 # makes it possible for the client to pass 'server.tmp$PID' and follow by
544 549 # an atomic rename to avoid locking when spawning new servers.
545 550 dirname, basename = os.path.split(address)
546 551 basename = basename.split('.', 1)[0]
547 552 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
548 553
549 554 class chgunixservicehandler(object):
550 555 """Set of operations for chg services"""
551 556
552 557 pollinterval = 1 # [sec]
553 558
554 559 def __init__(self, ui):
555 560 self.ui = ui
556 561 self._idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
557 562 self._lastactive = time.time()
558 563
559 564 def bindsocket(self, sock, address):
560 565 self._inithashstate(address)
561 566 self._checkextensions()
562 567 self._bind(sock)
563 568 self._createsymlink()
564 569
565 570 def _inithashstate(self, address):
566 571 self._baseaddress = address
567 572 if self.ui.configbool('chgserver', 'skiphash', False):
568 573 self._hashstate = None
569 574 self._realaddress = address
570 575 return
571 576 self._hashstate = hashstate.fromui(self.ui)
572 577 self._realaddress = _hashaddress(address, self._hashstate.confighash)
573 578
574 579 def _checkextensions(self):
575 580 if not self._hashstate:
576 581 return
577 582 if extensions.notloaded():
578 583 # one or more extensions failed to load. mtimehash becomes
579 584 # meaningless because we do not know the paths of those extensions.
580 585 # set mtimehash to an illegal hash value to invalidate the server.
581 586 self._hashstate.mtimehash = ''
582 587
583 588 def _bind(self, sock):
584 589 # use a unique temp address so we can stat the file and do ownership
585 590 # check later
586 591 tempaddress = _tempaddress(self._realaddress)
587 592 util.bindunixsocket(sock, tempaddress)
588 593 self._socketstat = os.stat(tempaddress)
589 594 # rename will replace the old socket file if exists atomically. the
590 595 # old server will detect ownership change and exit.
591 596 util.rename(tempaddress, self._realaddress)
592 597
593 598 def _createsymlink(self):
594 599 if self._baseaddress == self._realaddress:
595 600 return
596 601 tempaddress = _tempaddress(self._baseaddress)
597 602 os.symlink(os.path.basename(self._realaddress), tempaddress)
598 603 util.rename(tempaddress, self._baseaddress)
599 604
600 605 def _issocketowner(self):
601 606 try:
602 607 stat = os.stat(self._realaddress)
603 608 return (stat.st_ino == self._socketstat.st_ino and
604 609 stat.st_mtime == self._socketstat.st_mtime)
605 610 except OSError:
606 611 return False
607 612
608 613 def unlinksocket(self, address):
609 614 if not self._issocketowner():
610 615 return
611 616 # it is possible to have a race condition here that we may
612 617 # remove another server's socket file. but that's okay
613 618 # since that server will detect and exit automatically and
614 619 # the client will start a new server on demand.
615 620 try:
616 621 os.unlink(self._realaddress)
617 622 except OSError as exc:
618 623 if exc.errno != errno.ENOENT:
619 624 raise
620 625
621 626 def printbanner(self, address):
622 627 # no "listening at" message should be printed to simulate hg behavior
623 628 pass
624 629
625 630 def shouldexit(self):
626 631 if not self._issocketowner():
627 632 self.ui.debug('%s is not owned, exiting.\n' % self._realaddress)
628 633 return True
629 634 if time.time() - self._lastactive > self._idletimeout:
630 635 self.ui.debug('being idle too long. exiting.\n')
631 636 return True
632 637 return False
633 638
634 639 def newconnection(self):
635 640 self._lastactive = time.time()
636 641
637 642 def createcmdserver(self, repo, conn, fin, fout):
638 643 return chgcmdserver(self.ui, repo, fin, fout, conn,
639 644 self._hashstate, self._baseaddress)
640 645
641 646 def chgunixservice(ui, repo, opts):
642 647 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
643 648 # start another chg. drop it to avoid possible side effects.
644 649 if 'CHGINTERNALMARK' in encoding.environ:
645 650 del encoding.environ['CHGINTERNALMARK']
646 651
647 652 if repo:
648 653 # one chgserver can serve multiple repos. drop repo information
649 654 ui.setconfig('bundle', 'mainreporoot', '', 'repo')
650 655 h = chgunixservicehandler(ui)
651 656 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now