##// END OF EJS Templates
chgserver: do not keep repo object...
Jun Wu -
r28537:881d027d default
parent child Browse files
Show More
@@ -1,691 +1,692
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 (EXPERIMENTAL)
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 SocketServer
44 44 import errno
45 45 import inspect
46 46 import os
47 47 import re
48 48 import struct
49 49 import sys
50 50 import threading
51 51 import time
52 52 import traceback
53 53
54 54 from mercurial.i18n import _
55 55
56 56 from mercurial import (
57 57 cmdutil,
58 58 commands,
59 59 commandserver,
60 60 dispatch,
61 61 error,
62 62 extensions,
63 63 osutil,
64 64 util,
65 65 )
66 66
67 67 # Note for extension authors: ONLY specify testedwith = 'internal' for
68 68 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
69 69 # be specifying the version(s) of Mercurial they are tested with, or
70 70 # leave the attribute unspecified.
71 71 testedwith = 'internal'
72 72
73 73 _log = commandserver.log
74 74
75 75 def _hashlist(items):
76 76 """return sha1 hexdigest for a list"""
77 77 return util.sha1(str(items)).hexdigest()
78 78
79 79 # sensitive config sections affecting confighash
80 80 _configsections = [
81 81 'extdiff', # uisetup will register new commands
82 82 'extensions',
83 83 ]
84 84
85 85 # sensitive environment variables affecting confighash
86 86 _envre = re.compile(r'''\A(?:
87 87 CHGHG
88 88 |HG.*
89 89 |LANG(?:UAGE)?
90 90 |LC_.*
91 91 |LD_.*
92 92 |PATH
93 93 |PYTHON.*
94 94 |TERM(?:INFO)?
95 95 |TZ
96 96 )\Z''', re.X)
97 97
98 98 def _confighash(ui):
99 99 """return a quick hash for detecting config/env changes
100 100
101 101 confighash is the hash of sensitive config items and environment variables.
102 102
103 103 for chgserver, it is designed that once confighash changes, the server is
104 104 not qualified to serve its client and should redirect the client to a new
105 105 server. different from mtimehash, confighash change will not mark the
106 106 server outdated and exit since the user can have different configs at the
107 107 same time.
108 108 """
109 109 sectionitems = []
110 110 for section in _configsections:
111 111 sectionitems.append(ui.configitems(section))
112 112 sectionhash = _hashlist(sectionitems)
113 113 envitems = [(k, v) for k, v in os.environ.iteritems() if _envre.match(k)]
114 114 envhash = _hashlist(sorted(envitems))
115 115 return sectionhash[:6] + envhash[:6]
116 116
117 117 def _getmtimepaths(ui):
118 118 """get a list of paths that should be checked to detect change
119 119
120 120 The list will include:
121 121 - extensions (will not cover all files for complex extensions)
122 122 - mercurial/__version__.py
123 123 - python binary
124 124 """
125 125 modules = [m for n, m in extensions.extensions(ui)]
126 126 try:
127 127 from mercurial import __version__
128 128 modules.append(__version__)
129 129 except ImportError:
130 130 pass
131 131 files = [sys.executable]
132 132 for m in modules:
133 133 try:
134 134 files.append(inspect.getabsfile(m))
135 135 except TypeError:
136 136 pass
137 137 return sorted(set(files))
138 138
139 139 def _mtimehash(paths):
140 140 """return a quick hash for detecting file changes
141 141
142 142 mtimehash calls stat on given paths and calculate a hash based on size and
143 143 mtime of each file. mtimehash does not read file content because reading is
144 144 expensive. therefore it's not 100% reliable for detecting content changes.
145 145 it's possible to return different hashes for same file contents.
146 146 it's also possible to return a same hash for different file contents for
147 147 some carefully crafted situation.
148 148
149 149 for chgserver, it is designed that once mtimehash changes, the server is
150 150 considered outdated immediately and should no longer provide service.
151 151 """
152 152 def trystat(path):
153 153 try:
154 154 st = os.stat(path)
155 155 return (st.st_mtime, st.st_size)
156 156 except OSError:
157 157 # could be ENOENT, EPERM etc. not fatal in any case
158 158 pass
159 159 return _hashlist(map(trystat, paths))[:12]
160 160
161 161 class hashstate(object):
162 162 """a structure storing confighash, mtimehash, paths used for mtimehash"""
163 163 def __init__(self, confighash, mtimehash, mtimepaths):
164 164 self.confighash = confighash
165 165 self.mtimehash = mtimehash
166 166 self.mtimepaths = mtimepaths
167 167
168 168 @staticmethod
169 169 def fromui(ui, mtimepaths=None):
170 170 if mtimepaths is None:
171 171 mtimepaths = _getmtimepaths(ui)
172 172 confighash = _confighash(ui)
173 173 mtimehash = _mtimehash(mtimepaths)
174 174 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
175 175 return hashstate(confighash, mtimehash, mtimepaths)
176 176
177 177 # copied from hgext/pager.py:uisetup()
178 178 def _setuppagercmd(ui, options, cmd):
179 179 if not ui.formatted():
180 180 return
181 181
182 182 p = ui.config("pager", "pager", os.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 214 _envvarre = re.compile(r'\$[a-zA-Z_]+')
215 215
216 216 def _clearenvaliases(cmdtable):
217 217 """Remove stale command aliases referencing env vars; variable expansion
218 218 is done at dispatch.addaliases()"""
219 219 for name, tab in cmdtable.items():
220 220 cmddef = tab[0]
221 221 if (isinstance(cmddef, dispatch.cmdalias) and
222 222 not cmddef.definition.startswith('!') and # shell alias
223 223 _envvarre.search(cmddef.definition)):
224 224 del cmdtable[name]
225 225
226 226 def _newchgui(srcui, csystem):
227 227 class chgui(srcui.__class__):
228 228 def __init__(self, src=None):
229 229 super(chgui, self).__init__(src)
230 230 if src:
231 231 self._csystem = getattr(src, '_csystem', csystem)
232 232 else:
233 233 self._csystem = csystem
234 234
235 235 def system(self, cmd, environ=None, cwd=None, onerr=None,
236 236 errprefix=None):
237 237 # copied from mercurial/util.py:system()
238 238 self.flush()
239 239 def py2shell(val):
240 240 if val is None or val is False:
241 241 return '0'
242 242 if val is True:
243 243 return '1'
244 244 return str(val)
245 245 env = os.environ.copy()
246 246 if environ:
247 247 env.update((k, py2shell(v)) for k, v in environ.iteritems())
248 248 env['HG'] = util.hgexecutable()
249 249 rc = self._csystem(cmd, env, cwd)
250 250 if rc and onerr:
251 251 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
252 252 util.explainexit(rc)[0])
253 253 if errprefix:
254 254 errmsg = '%s: %s' % (errprefix, errmsg)
255 255 raise onerr(errmsg)
256 256 return rc
257 257
258 258 return chgui(srcui)
259 259
260 260 def _renewui(srcui, args=None):
261 261 if not args:
262 262 args = []
263 263
264 264 newui = srcui.__class__()
265 265 for a in ['fin', 'fout', 'ferr', 'environ']:
266 266 setattr(newui, a, getattr(srcui, a))
267 267 if util.safehasattr(srcui, '_csystem'):
268 268 newui._csystem = srcui._csystem
269 269
270 270 # load wd and repo config, copied from dispatch.py
271 271 cwds = dispatch._earlygetopt(['--cwd'], args)
272 272 cwd = cwds and os.path.realpath(cwds[-1]) or None
273 273 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
274 274 path, newui = dispatch._getlocal(newui, rpath, wd=cwd)
275 275
276 276 # internal config: extensions.chgserver
277 277 # copy it. it can only be overrided from command line.
278 278 newui.setconfig('extensions', 'chgserver',
279 279 srcui.config('extensions', 'chgserver'), '--config')
280 280
281 281 # command line args
282 282 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
283 283
284 284 # stolen from tortoisehg.util.copydynamicconfig()
285 285 for section, name, value in srcui.walkconfig():
286 286 source = srcui.configsource(section, name)
287 287 if ':' in source or source == '--config':
288 288 # path:line or command line
289 289 continue
290 290 if source == 'none':
291 291 # ui.configsource returns 'none' by default
292 292 source = ''
293 293 newui.setconfig(section, name, value, source)
294 294 return newui
295 295
296 296 class channeledsystem(object):
297 297 """Propagate ui.system() request in the following format:
298 298
299 299 payload length (unsigned int),
300 300 cmd, '\0',
301 301 cwd, '\0',
302 302 envkey, '=', val, '\0',
303 303 ...
304 304 envkey, '=', val
305 305
306 306 and waits:
307 307
308 308 exitcode length (unsigned int),
309 309 exitcode (int)
310 310 """
311 311 def __init__(self, in_, out, channel):
312 312 self.in_ = in_
313 313 self.out = out
314 314 self.channel = channel
315 315
316 316 def __call__(self, cmd, environ, cwd):
317 317 args = [util.quotecommand(cmd), os.path.abspath(cwd or '.')]
318 318 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
319 319 data = '\0'.join(args)
320 320 self.out.write(struct.pack('>cI', self.channel, len(data)))
321 321 self.out.write(data)
322 322 self.out.flush()
323 323
324 324 length = self.in_.read(4)
325 325 length, = struct.unpack('>I', length)
326 326 if length != 4:
327 327 raise error.Abort(_('invalid response'))
328 328 rc, = struct.unpack('>i', self.in_.read(4))
329 329 return rc
330 330
331 331 _iochannels = [
332 332 # server.ch, ui.fp, mode
333 333 ('cin', 'fin', 'rb'),
334 334 ('cout', 'fout', 'wb'),
335 335 ('cerr', 'ferr', 'wb'),
336 336 ]
337 337
338 338 class chgcmdserver(commandserver.server):
339 339 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
340 340 super(chgcmdserver, self).__init__(
341 341 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
342 342 self.clientsock = sock
343 343 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
344 344 self.hashstate = hashstate
345 345 self.baseaddress = baseaddress
346 346 if hashstate is not None:
347 347 self.capabilities = self.capabilities.copy()
348 348 self.capabilities['validate'] = chgcmdserver.validate
349 349
350 350 def cleanup(self):
351 351 # dispatch._runcatch() does not flush outputs if exception is not
352 352 # handled by dispatch._dispatch()
353 353 self.ui.flush()
354 354 self._restoreio()
355 355
356 356 def attachio(self):
357 357 """Attach to client's stdio passed via unix domain socket; all
358 358 channels except cresult will no longer be used
359 359 """
360 360 # tell client to sendmsg() with 1-byte payload, which makes it
361 361 # distinctive from "attachio\n" command consumed by client.read()
362 362 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
363 363 clientfds = osutil.recvfds(self.clientsock.fileno())
364 364 _log('received fds: %r\n' % clientfds)
365 365
366 366 ui = self.ui
367 367 ui.flush()
368 368 first = self._saveio()
369 369 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
370 370 assert fd > 0
371 371 fp = getattr(ui, fn)
372 372 os.dup2(fd, fp.fileno())
373 373 os.close(fd)
374 374 if not first:
375 375 continue
376 376 # reset buffering mode when client is first attached. as we want
377 377 # to see output immediately on pager, the mode stays unchanged
378 378 # when client re-attached. ferr is unchanged because it should
379 379 # be unbuffered no matter if it is a tty or not.
380 380 if fn == 'ferr':
381 381 newfp = fp
382 382 else:
383 383 # make it line buffered explicitly because the default is
384 384 # decided on first write(), where fout could be a pager.
385 385 if fp.isatty():
386 386 bufsize = 1 # line buffered
387 387 else:
388 388 bufsize = -1 # system default
389 389 newfp = os.fdopen(fp.fileno(), mode, bufsize)
390 390 setattr(ui, fn, newfp)
391 391 setattr(self, cn, newfp)
392 392
393 393 self.cresult.write(struct.pack('>i', len(clientfds)))
394 394
395 395 def _saveio(self):
396 396 if self._oldios:
397 397 return False
398 398 ui = self.ui
399 399 for cn, fn, _mode in _iochannels:
400 400 ch = getattr(self, cn)
401 401 fp = getattr(ui, fn)
402 402 fd = os.dup(fp.fileno())
403 403 self._oldios.append((ch, fp, fd))
404 404 return True
405 405
406 406 def _restoreio(self):
407 407 ui = self.ui
408 408 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
409 409 newfp = getattr(ui, fn)
410 410 # close newfp while it's associated with client; otherwise it
411 411 # would be closed when newfp is deleted
412 412 if newfp is not fp:
413 413 newfp.close()
414 414 # restore original fd: fp is open again
415 415 os.dup2(fd, fp.fileno())
416 416 os.close(fd)
417 417 setattr(self, cn, ch)
418 418 setattr(ui, fn, fp)
419 419 del self._oldios[:]
420 420
421 421 def validate(self):
422 422 """Reload the config and check if the server is up to date
423 423
424 424 Read a list of '\0' separated arguments.
425 425 Write a non-empty list of '\0' separated instruction strings or '\0'
426 426 if the list is empty.
427 427 An instruction string could be either:
428 428 - "unlink $path", the client should unlink the path to stop the
429 429 outdated server.
430 430 - "redirect $path", the client should attempt to connect to $path
431 431 first. If it does not work, start a new server. It implies
432 432 "reconnect".
433 433 - "exit $n", the client should exit directly with code n.
434 434 This may happen if we cannot parse the config.
435 435 - "reconnect", the client should close the connection and
436 436 reconnect.
437 437 If neither "reconnect" nor "redirect" is included in the instruction
438 438 list, the client can continue with this server after completing all
439 439 the instructions.
440 440 """
441 441 args = self._readlist()
442 442 try:
443 443 self.ui = _renewui(self.ui, args)
444 444 except error.ParseError as inst:
445 445 dispatch._formatparse(self.ui.warn, inst)
446 446 self.ui.flush()
447 447 self.cresult.write('exit 255')
448 448 return
449 449 newhash = hashstate.fromui(self.ui, self.hashstate.mtimepaths)
450 450 insts = []
451 451 if newhash.mtimehash != self.hashstate.mtimehash:
452 452 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
453 453 insts.append('unlink %s' % addr)
454 454 # mtimehash is empty if one or more extensions fail to load.
455 455 # to be compatible with hg, still serve the client this time.
456 456 if self.hashstate.mtimehash:
457 457 insts.append('reconnect')
458 458 if newhash.confighash != self.hashstate.confighash:
459 459 addr = _hashaddress(self.baseaddress, newhash.confighash)
460 460 insts.append('redirect %s' % addr)
461 461 _log('validate: %s\n' % insts)
462 462 self.cresult.write('\0'.join(insts) or '\0')
463 463
464 464 def chdir(self):
465 465 """Change current directory
466 466
467 467 Note that the behavior of --cwd option is bit different from this.
468 468 It does not affect --config parameter.
469 469 """
470 470 path = self._readstr()
471 471 if not path:
472 472 return
473 473 _log('chdir to %r\n' % path)
474 474 os.chdir(path)
475 475
476 476 def setumask(self):
477 477 """Change umask"""
478 478 mask = struct.unpack('>I', self._read(4))[0]
479 479 _log('setumask %r\n' % mask)
480 480 os.umask(mask)
481 481
482 482 def getpager(self):
483 483 """Read cmdargs and write pager command to r-channel if enabled
484 484
485 485 If pager isn't enabled, this writes '\0' because channeledoutput
486 486 does not allow to write empty data.
487 487 """
488 488 args = self._readlist()
489 489 try:
490 490 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
491 491 args)
492 492 except (error.Abort, error.AmbiguousCommand, error.CommandError,
493 493 error.UnknownCommand):
494 494 cmd = None
495 495 options = {}
496 496 if not cmd or 'pager' not in options:
497 497 self.cresult.write('\0')
498 498 return
499 499
500 500 pagercmd = _setuppagercmd(self.ui, options, cmd)
501 501 if pagercmd:
502 502 self.cresult.write(pagercmd)
503 503 else:
504 504 self.cresult.write('\0')
505 505
506 506 def setenv(self):
507 507 """Clear and update os.environ
508 508
509 509 Note that not all variables can make an effect on the running process.
510 510 """
511 511 l = self._readlist()
512 512 try:
513 513 newenv = dict(s.split('=', 1) for s in l)
514 514 except ValueError:
515 515 raise ValueError('unexpected value in setenv request')
516 516
517 517 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
518 518 if os.environ.get(k) != newenv.get(k))
519 519 _log('change env: %r\n' % sorted(diffkeys))
520 520
521 521 os.environ.clear()
522 522 os.environ.update(newenv)
523 523
524 524 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
525 525 # reload config so that ui.plain() takes effect
526 526 self.ui = _renewui(self.ui)
527 527
528 528 _clearenvaliases(commands.table)
529 529
530 530 capabilities = commandserver.server.capabilities.copy()
531 531 capabilities.update({'attachio': attachio,
532 532 'chdir': chdir,
533 533 'getpager': getpager,
534 534 'setenv': setenv,
535 535 'setumask': setumask})
536 536
537 537 # copied from mercurial/commandserver.py
538 538 class _requesthandler(SocketServer.StreamRequestHandler):
539 539 def handle(self):
540 540 # use a different process group from the master process, making this
541 541 # process pass kernel "is_current_pgrp_orphaned" check so signals like
542 542 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
543 543 os.setpgid(0, 0)
544 544 ui = self.server.ui
545 545 repo = self.server.repo
546 546 sv = None
547 547 try:
548 548 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection,
549 549 self.server.hashstate, self.server.baseaddress)
550 550 try:
551 551 sv.serve()
552 552 # handle exceptions that may be raised by command server. most of
553 553 # known exceptions are caught by dispatch.
554 554 except error.Abort as inst:
555 555 ui.warn(_('abort: %s\n') % inst)
556 556 except IOError as inst:
557 557 if inst.errno != errno.EPIPE:
558 558 raise
559 559 except KeyboardInterrupt:
560 560 pass
561 561 finally:
562 562 sv.cleanup()
563 563 except: # re-raises
564 564 # also write traceback to error channel. otherwise client cannot
565 565 # see it because it is written to server's stderr by default.
566 566 if sv:
567 567 cerr = sv.cerr
568 568 else:
569 569 cerr = commandserver.channeledoutput(self.wfile, 'e')
570 570 traceback.print_exc(file=cerr)
571 571 raise
572 572
573 573 def _tempaddress(address):
574 574 return '%s.%d.tmp' % (address, os.getpid())
575 575
576 576 def _hashaddress(address, hashstr):
577 577 return '%s-%s' % (address, hashstr)
578 578
579 579 class AutoExitMixIn: # use old-style to comply with SocketServer design
580 580 lastactive = time.time()
581 581 idletimeout = 3600 # default 1 hour
582 582
583 583 def startautoexitthread(self):
584 584 # note: the auto-exit check here is cheap enough to not use a thread,
585 585 # be done in serve_forever. however SocketServer is hook-unfriendly,
586 586 # you simply cannot hook serve_forever without copying a lot of code.
587 587 # besides, serve_forever's docstring suggests using thread.
588 588 thread = threading.Thread(target=self._autoexitloop)
589 589 thread.daemon = True
590 590 thread.start()
591 591
592 592 def _autoexitloop(self, interval=1):
593 593 while True:
594 594 time.sleep(interval)
595 595 if not self.issocketowner():
596 596 _log('%s is not owned, exiting.\n' % self.server_address)
597 597 break
598 598 if time.time() - self.lastactive > self.idletimeout:
599 599 _log('being idle too long. exiting.\n')
600 600 break
601 601 self.shutdown()
602 602
603 603 def process_request(self, request, address):
604 604 self.lastactive = time.time()
605 605 return SocketServer.ForkingMixIn.process_request(
606 606 self, request, address)
607 607
608 608 def server_bind(self):
609 609 # use a unique temp address so we can stat the file and do ownership
610 610 # check later
611 611 tempaddress = _tempaddress(self.server_address)
612 612 self.socket.bind(tempaddress)
613 613 self._socketstat = os.stat(tempaddress)
614 614 # rename will replace the old socket file if exists atomically. the
615 615 # old server will detect ownership change and exit.
616 616 util.rename(tempaddress, self.server_address)
617 617
618 618 def issocketowner(self):
619 619 try:
620 620 stat = os.stat(self.server_address)
621 621 return (stat.st_ino == self._socketstat.st_ino and
622 622 stat.st_mtime == self._socketstat.st_mtime)
623 623 except OSError:
624 624 return False
625 625
626 626 def unlinksocketfile(self):
627 627 if not self.issocketowner():
628 628 return
629 629 # it is possible to have a race condition here that we may
630 630 # remove another server's socket file. but that's okay
631 631 # since that server will detect and exit automatically and
632 632 # the client will start a new server on demand.
633 633 try:
634 634 os.unlink(self.server_address)
635 635 except OSError as exc:
636 636 if exc.errno != errno.ENOENT:
637 637 raise
638 638
639 639 class chgunixservice(commandserver.unixservice):
640 640 def init(self):
641 self.repo = None
641 642 self._inithashstate()
642 643 self._checkextensions()
643 644 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
644 645 SocketServer.UnixStreamServer):
645 646 ui = self.ui
646 647 repo = self.repo
647 648 hashstate = self.hashstate
648 649 baseaddress = self.baseaddress
649 650 self.server = cls(self.address, _requesthandler)
650 651 self.server.idletimeout = self.ui.configint(
651 652 'chgserver', 'idletimeout', self.server.idletimeout)
652 653 self.server.startautoexitthread()
653 654 self._createsymlink()
654 655
655 656 def _inithashstate(self):
656 657 self.baseaddress = self.address
657 658 if self.ui.configbool('chgserver', 'skiphash', False):
658 659 self.hashstate = None
659 660 return
660 661 self.hashstate = hashstate.fromui(self.ui)
661 662 self.address = _hashaddress(self.address, self.hashstate.confighash)
662 663
663 664 def _checkextensions(self):
664 665 if not self.hashstate:
665 666 return
666 667 if extensions.notloaded():
667 668 # one or more extensions failed to load. mtimehash becomes
668 669 # meaningless because we do not know the paths of those extensions.
669 670 # set mtimehash to an illegal hash value to invalidate the server.
670 671 self.hashstate.mtimehash = ''
671 672
672 673 def _createsymlink(self):
673 674 if self.baseaddress == self.address:
674 675 return
675 676 tempaddress = _tempaddress(self.baseaddress)
676 677 os.symlink(os.path.basename(self.address), tempaddress)
677 678 util.rename(tempaddress, self.baseaddress)
678 679
679 680 def run(self):
680 681 try:
681 682 self.server.serve_forever()
682 683 finally:
683 684 self.server.unlinksocketfile()
684 685
685 686 def uisetup(ui):
686 687 commandserver._servicemap['chgunix'] = chgunixservice
687 688
688 689 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
689 690 # start another chg. drop it to avoid possible side effects.
690 691 if 'CHGINTERNALMARK' in os.environ:
691 692 del os.environ['CHGINTERNALMARK']
General Comments 0
You need to be logged in to leave comments. Login now