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