##// END OF EJS Templates
chgserver: create new process group after fork (issue5051)...
Jun Wu -
r28014:83fc0c05 default
parent child Browse files
Show More
@@ -1,396 +1,400 b''
1 # chgserver.py - command server extension for cHg
1 # chgserver.py - command server extension for cHg
2 #
2 #
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """command server extension for cHg (EXPERIMENTAL)
8 """command server extension for cHg (EXPERIMENTAL)
9
9
10 'S' channel (read/write)
10 'S' channel (read/write)
11 propagate ui.system() request to client
11 propagate ui.system() request to client
12
12
13 'attachio' command
13 'attachio' command
14 attach client's stdio passed by sendmsg()
14 attach client's stdio passed by sendmsg()
15
15
16 'chdir' command
16 'chdir' command
17 change current directory
17 change current directory
18
18
19 'getpager' command
19 'getpager' command
20 checks if pager is enabled and which pager should be executed
20 checks if pager is enabled and which pager should be executed
21
21
22 'setenv' command
22 'setenv' command
23 replace os.environ completely
23 replace os.environ completely
24
24
25 'SIGHUP' signal
25 'SIGHUP' signal
26 reload configuration files
26 reload configuration files
27 """
27 """
28
28
29 from __future__ import absolute_import
29 from __future__ import absolute_import
30
30
31 import SocketServer
31 import SocketServer
32 import errno
32 import errno
33 import os
33 import os
34 import re
34 import re
35 import signal
35 import signal
36 import struct
36 import struct
37 import traceback
37 import traceback
38
38
39 from mercurial.i18n import _
39 from mercurial.i18n import _
40
40
41 from mercurial import (
41 from mercurial import (
42 cmdutil,
42 cmdutil,
43 commands,
43 commands,
44 commandserver,
44 commandserver,
45 dispatch,
45 dispatch,
46 error,
46 error,
47 osutil,
47 osutil,
48 util,
48 util,
49 )
49 )
50
50
51 # Note for extension authors: ONLY specify testedwith = 'internal' for
51 # Note for extension authors: ONLY specify testedwith = 'internal' for
52 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
52 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
53 # be specifying the version(s) of Mercurial they are tested with, or
53 # be specifying the version(s) of Mercurial they are tested with, or
54 # leave the attribute unspecified.
54 # leave the attribute unspecified.
55 testedwith = 'internal'
55 testedwith = 'internal'
56
56
57 _log = commandserver.log
57 _log = commandserver.log
58
58
59 # copied from hgext/pager.py:uisetup()
59 # copied from hgext/pager.py:uisetup()
60 def _setuppagercmd(ui, options, cmd):
60 def _setuppagercmd(ui, options, cmd):
61 if not ui.formatted():
61 if not ui.formatted():
62 return
62 return
63
63
64 p = ui.config("pager", "pager", os.environ.get("PAGER"))
64 p = ui.config("pager", "pager", os.environ.get("PAGER"))
65 usepager = False
65 usepager = False
66 always = util.parsebool(options['pager'])
66 always = util.parsebool(options['pager'])
67 auto = options['pager'] == 'auto'
67 auto = options['pager'] == 'auto'
68
68
69 if not p:
69 if not p:
70 pass
70 pass
71 elif always:
71 elif always:
72 usepager = True
72 usepager = True
73 elif not auto:
73 elif not auto:
74 usepager = False
74 usepager = False
75 else:
75 else:
76 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
76 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
77 attend = ui.configlist('pager', 'attend', attended)
77 attend = ui.configlist('pager', 'attend', attended)
78 ignore = ui.configlist('pager', 'ignore')
78 ignore = ui.configlist('pager', 'ignore')
79 cmds, _ = cmdutil.findcmd(cmd, commands.table)
79 cmds, _ = cmdutil.findcmd(cmd, commands.table)
80
80
81 for cmd in cmds:
81 for cmd in cmds:
82 var = 'attend-%s' % cmd
82 var = 'attend-%s' % cmd
83 if ui.config('pager', var):
83 if ui.config('pager', var):
84 usepager = ui.configbool('pager', var)
84 usepager = ui.configbool('pager', var)
85 break
85 break
86 if (cmd in attend or
86 if (cmd in attend or
87 (cmd not in ignore and not attend)):
87 (cmd not in ignore and not attend)):
88 usepager = True
88 usepager = True
89 break
89 break
90
90
91 if usepager:
91 if usepager:
92 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
92 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
93 ui.setconfig('ui', 'interactive', False, 'pager')
93 ui.setconfig('ui', 'interactive', False, 'pager')
94 return p
94 return p
95
95
96 _envvarre = re.compile(r'\$[a-zA-Z_]+')
96 _envvarre = re.compile(r'\$[a-zA-Z_]+')
97
97
98 def _clearenvaliases(cmdtable):
98 def _clearenvaliases(cmdtable):
99 """Remove stale command aliases referencing env vars; variable expansion
99 """Remove stale command aliases referencing env vars; variable expansion
100 is done at dispatch.addaliases()"""
100 is done at dispatch.addaliases()"""
101 for name, tab in cmdtable.items():
101 for name, tab in cmdtable.items():
102 cmddef = tab[0]
102 cmddef = tab[0]
103 if (isinstance(cmddef, dispatch.cmdalias) and
103 if (isinstance(cmddef, dispatch.cmdalias) and
104 not cmddef.definition.startswith('!') and # shell alias
104 not cmddef.definition.startswith('!') and # shell alias
105 _envvarre.search(cmddef.definition)):
105 _envvarre.search(cmddef.definition)):
106 del cmdtable[name]
106 del cmdtable[name]
107
107
108 def _newchgui(srcui, csystem):
108 def _newchgui(srcui, csystem):
109 class chgui(srcui.__class__):
109 class chgui(srcui.__class__):
110 def __init__(self, src=None):
110 def __init__(self, src=None):
111 super(chgui, self).__init__(src)
111 super(chgui, self).__init__(src)
112 if src:
112 if src:
113 self._csystem = getattr(src, '_csystem', csystem)
113 self._csystem = getattr(src, '_csystem', csystem)
114 else:
114 else:
115 self._csystem = csystem
115 self._csystem = csystem
116
116
117 def system(self, cmd, environ=None, cwd=None, onerr=None,
117 def system(self, cmd, environ=None, cwd=None, onerr=None,
118 errprefix=None):
118 errprefix=None):
119 # copied from mercurial/util.py:system()
119 # copied from mercurial/util.py:system()
120 self.flush()
120 self.flush()
121 def py2shell(val):
121 def py2shell(val):
122 if val is None or val is False:
122 if val is None or val is False:
123 return '0'
123 return '0'
124 if val is True:
124 if val is True:
125 return '1'
125 return '1'
126 return str(val)
126 return str(val)
127 env = os.environ.copy()
127 env = os.environ.copy()
128 if environ:
128 if environ:
129 env.update((k, py2shell(v)) for k, v in environ.iteritems())
129 env.update((k, py2shell(v)) for k, v in environ.iteritems())
130 env['HG'] = util.hgexecutable()
130 env['HG'] = util.hgexecutable()
131 rc = self._csystem(cmd, env, cwd)
131 rc = self._csystem(cmd, env, cwd)
132 if rc and onerr:
132 if rc and onerr:
133 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
133 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
134 util.explainexit(rc)[0])
134 util.explainexit(rc)[0])
135 if errprefix:
135 if errprefix:
136 errmsg = '%s: %s' % (errprefix, errmsg)
136 errmsg = '%s: %s' % (errprefix, errmsg)
137 raise onerr(errmsg)
137 raise onerr(errmsg)
138 return rc
138 return rc
139
139
140 return chgui(srcui)
140 return chgui(srcui)
141
141
142 def _renewui(srcui):
142 def _renewui(srcui):
143 newui = srcui.__class__()
143 newui = srcui.__class__()
144 for a in ['fin', 'fout', 'ferr', 'environ']:
144 for a in ['fin', 'fout', 'ferr', 'environ']:
145 setattr(newui, a, getattr(srcui, a))
145 setattr(newui, a, getattr(srcui, a))
146 if util.safehasattr(srcui, '_csystem'):
146 if util.safehasattr(srcui, '_csystem'):
147 newui._csystem = srcui._csystem
147 newui._csystem = srcui._csystem
148 # stolen from tortoisehg.util.copydynamicconfig()
148 # stolen from tortoisehg.util.copydynamicconfig()
149 for section, name, value in srcui.walkconfig():
149 for section, name, value in srcui.walkconfig():
150 source = srcui.configsource(section, name)
150 source = srcui.configsource(section, name)
151 if ':' in source:
151 if ':' in source:
152 # path:line
152 # path:line
153 continue
153 continue
154 if source == 'none':
154 if source == 'none':
155 # ui.configsource returns 'none' by default
155 # ui.configsource returns 'none' by default
156 source = ''
156 source = ''
157 newui.setconfig(section, name, value, source)
157 newui.setconfig(section, name, value, source)
158 return newui
158 return newui
159
159
160 class channeledsystem(object):
160 class channeledsystem(object):
161 """Propagate ui.system() request in the following format:
161 """Propagate ui.system() request in the following format:
162
162
163 payload length (unsigned int),
163 payload length (unsigned int),
164 cmd, '\0',
164 cmd, '\0',
165 cwd, '\0',
165 cwd, '\0',
166 envkey, '=', val, '\0',
166 envkey, '=', val, '\0',
167 ...
167 ...
168 envkey, '=', val
168 envkey, '=', val
169
169
170 and waits:
170 and waits:
171
171
172 exitcode length (unsigned int),
172 exitcode length (unsigned int),
173 exitcode (int)
173 exitcode (int)
174 """
174 """
175 def __init__(self, in_, out, channel):
175 def __init__(self, in_, out, channel):
176 self.in_ = in_
176 self.in_ = in_
177 self.out = out
177 self.out = out
178 self.channel = channel
178 self.channel = channel
179
179
180 def __call__(self, cmd, environ, cwd):
180 def __call__(self, cmd, environ, cwd):
181 args = [util.quotecommand(cmd), cwd or '.']
181 args = [util.quotecommand(cmd), cwd or '.']
182 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
182 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
183 data = '\0'.join(args)
183 data = '\0'.join(args)
184 self.out.write(struct.pack('>cI', self.channel, len(data)))
184 self.out.write(struct.pack('>cI', self.channel, len(data)))
185 self.out.write(data)
185 self.out.write(data)
186 self.out.flush()
186 self.out.flush()
187
187
188 length = self.in_.read(4)
188 length = self.in_.read(4)
189 length, = struct.unpack('>I', length)
189 length, = struct.unpack('>I', length)
190 if length != 4:
190 if length != 4:
191 raise error.Abort(_('invalid response'))
191 raise error.Abort(_('invalid response'))
192 rc, = struct.unpack('>i', self.in_.read(4))
192 rc, = struct.unpack('>i', self.in_.read(4))
193 return rc
193 return rc
194
194
195 _iochannels = [
195 _iochannels = [
196 # server.ch, ui.fp, mode
196 # server.ch, ui.fp, mode
197 ('cin', 'fin', 'rb'),
197 ('cin', 'fin', 'rb'),
198 ('cout', 'fout', 'wb'),
198 ('cout', 'fout', 'wb'),
199 ('cerr', 'ferr', 'wb'),
199 ('cerr', 'ferr', 'wb'),
200 ]
200 ]
201
201
202 class chgcmdserver(commandserver.server):
202 class chgcmdserver(commandserver.server):
203 def __init__(self, ui, repo, fin, fout, sock):
203 def __init__(self, ui, repo, fin, fout, sock):
204 super(chgcmdserver, self).__init__(
204 super(chgcmdserver, self).__init__(
205 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
205 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
206 self.clientsock = sock
206 self.clientsock = sock
207 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
207 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
208
208
209 def cleanup(self):
209 def cleanup(self):
210 # dispatch._runcatch() does not flush outputs if exception is not
210 # dispatch._runcatch() does not flush outputs if exception is not
211 # handled by dispatch._dispatch()
211 # handled by dispatch._dispatch()
212 self.ui.flush()
212 self.ui.flush()
213 self._restoreio()
213 self._restoreio()
214
214
215 def attachio(self):
215 def attachio(self):
216 """Attach to client's stdio passed via unix domain socket; all
216 """Attach to client's stdio passed via unix domain socket; all
217 channels except cresult will no longer be used
217 channels except cresult will no longer be used
218 """
218 """
219 # tell client to sendmsg() with 1-byte payload, which makes it
219 # tell client to sendmsg() with 1-byte payload, which makes it
220 # distinctive from "attachio\n" command consumed by client.read()
220 # distinctive from "attachio\n" command consumed by client.read()
221 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
221 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
222 clientfds = osutil.recvfds(self.clientsock.fileno())
222 clientfds = osutil.recvfds(self.clientsock.fileno())
223 _log('received fds: %r\n' % clientfds)
223 _log('received fds: %r\n' % clientfds)
224
224
225 ui = self.ui
225 ui = self.ui
226 ui.flush()
226 ui.flush()
227 first = self._saveio()
227 first = self._saveio()
228 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
228 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
229 assert fd > 0
229 assert fd > 0
230 fp = getattr(ui, fn)
230 fp = getattr(ui, fn)
231 os.dup2(fd, fp.fileno())
231 os.dup2(fd, fp.fileno())
232 os.close(fd)
232 os.close(fd)
233 if not first:
233 if not first:
234 continue
234 continue
235 # reset buffering mode when client is first attached. as we want
235 # reset buffering mode when client is first attached. as we want
236 # to see output immediately on pager, the mode stays unchanged
236 # to see output immediately on pager, the mode stays unchanged
237 # when client re-attached. ferr is unchanged because it should
237 # when client re-attached. ferr is unchanged because it should
238 # be unbuffered no matter if it is a tty or not.
238 # be unbuffered no matter if it is a tty or not.
239 if fn == 'ferr':
239 if fn == 'ferr':
240 newfp = fp
240 newfp = fp
241 else:
241 else:
242 # make it line buffered explicitly because the default is
242 # make it line buffered explicitly because the default is
243 # decided on first write(), where fout could be a pager.
243 # decided on first write(), where fout could be a pager.
244 if fp.isatty():
244 if fp.isatty():
245 bufsize = 1 # line buffered
245 bufsize = 1 # line buffered
246 else:
246 else:
247 bufsize = -1 # system default
247 bufsize = -1 # system default
248 newfp = os.fdopen(fp.fileno(), mode, bufsize)
248 newfp = os.fdopen(fp.fileno(), mode, bufsize)
249 setattr(ui, fn, newfp)
249 setattr(ui, fn, newfp)
250 setattr(self, cn, newfp)
250 setattr(self, cn, newfp)
251
251
252 self.cresult.write(struct.pack('>i', len(clientfds)))
252 self.cresult.write(struct.pack('>i', len(clientfds)))
253
253
254 def _saveio(self):
254 def _saveio(self):
255 if self._oldios:
255 if self._oldios:
256 return False
256 return False
257 ui = self.ui
257 ui = self.ui
258 for cn, fn, _mode in _iochannels:
258 for cn, fn, _mode in _iochannels:
259 ch = getattr(self, cn)
259 ch = getattr(self, cn)
260 fp = getattr(ui, fn)
260 fp = getattr(ui, fn)
261 fd = os.dup(fp.fileno())
261 fd = os.dup(fp.fileno())
262 self._oldios.append((ch, fp, fd))
262 self._oldios.append((ch, fp, fd))
263 return True
263 return True
264
264
265 def _restoreio(self):
265 def _restoreio(self):
266 ui = self.ui
266 ui = self.ui
267 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
267 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
268 newfp = getattr(ui, fn)
268 newfp = getattr(ui, fn)
269 # close newfp while it's associated with client; otherwise it
269 # close newfp while it's associated with client; otherwise it
270 # would be closed when newfp is deleted
270 # would be closed when newfp is deleted
271 if newfp is not fp:
271 if newfp is not fp:
272 newfp.close()
272 newfp.close()
273 # restore original fd: fp is open again
273 # restore original fd: fp is open again
274 os.dup2(fd, fp.fileno())
274 os.dup2(fd, fp.fileno())
275 os.close(fd)
275 os.close(fd)
276 setattr(self, cn, ch)
276 setattr(self, cn, ch)
277 setattr(ui, fn, fp)
277 setattr(ui, fn, fp)
278 del self._oldios[:]
278 del self._oldios[:]
279
279
280 def chdir(self):
280 def chdir(self):
281 """Change current directory
281 """Change current directory
282
282
283 Note that the behavior of --cwd option is bit different from this.
283 Note that the behavior of --cwd option is bit different from this.
284 It does not affect --config parameter.
284 It does not affect --config parameter.
285 """
285 """
286 length = struct.unpack('>I', self._read(4))[0]
286 length = struct.unpack('>I', self._read(4))[0]
287 if not length:
287 if not length:
288 return
288 return
289 path = self._read(length)
289 path = self._read(length)
290 _log('chdir to %r\n' % path)
290 _log('chdir to %r\n' % path)
291 os.chdir(path)
291 os.chdir(path)
292
292
293 def getpager(self):
293 def getpager(self):
294 """Read cmdargs and write pager command to r-channel if enabled
294 """Read cmdargs and write pager command to r-channel if enabled
295
295
296 If pager isn't enabled, this writes '\0' because channeledoutput
296 If pager isn't enabled, this writes '\0' because channeledoutput
297 does not allow to write empty data.
297 does not allow to write empty data.
298 """
298 """
299 length = struct.unpack('>I', self._read(4))[0]
299 length = struct.unpack('>I', self._read(4))[0]
300 if not length:
300 if not length:
301 args = []
301 args = []
302 else:
302 else:
303 args = self._read(length).split('\0')
303 args = self._read(length).split('\0')
304 try:
304 try:
305 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
305 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
306 args)
306 args)
307 except (error.Abort, error.AmbiguousCommand, error.CommandError,
307 except (error.Abort, error.AmbiguousCommand, error.CommandError,
308 error.UnknownCommand):
308 error.UnknownCommand):
309 cmd = None
309 cmd = None
310 options = {}
310 options = {}
311 if not cmd or 'pager' not in options:
311 if not cmd or 'pager' not in options:
312 self.cresult.write('\0')
312 self.cresult.write('\0')
313 return
313 return
314
314
315 pagercmd = _setuppagercmd(self.ui, options, cmd)
315 pagercmd = _setuppagercmd(self.ui, options, cmd)
316 if pagercmd:
316 if pagercmd:
317 self.cresult.write(pagercmd)
317 self.cresult.write(pagercmd)
318 else:
318 else:
319 self.cresult.write('\0')
319 self.cresult.write('\0')
320
320
321 def setenv(self):
321 def setenv(self):
322 """Clear and update os.environ
322 """Clear and update os.environ
323
323
324 Note that not all variables can make an effect on the running process.
324 Note that not all variables can make an effect on the running process.
325 """
325 """
326 length = struct.unpack('>I', self._read(4))[0]
326 length = struct.unpack('>I', self._read(4))[0]
327 if not length:
327 if not length:
328 return
328 return
329 s = self._read(length)
329 s = self._read(length)
330 try:
330 try:
331 newenv = dict(l.split('=', 1) for l in s.split('\0'))
331 newenv = dict(l.split('=', 1) for l in s.split('\0'))
332 except ValueError:
332 except ValueError:
333 raise ValueError('unexpected value in setenv request')
333 raise ValueError('unexpected value in setenv request')
334
334
335 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
335 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
336 if os.environ.get(k) != newenv.get(k))
336 if os.environ.get(k) != newenv.get(k))
337 _log('change env: %r\n' % sorted(diffkeys))
337 _log('change env: %r\n' % sorted(diffkeys))
338
338
339 os.environ.clear()
339 os.environ.clear()
340 os.environ.update(newenv)
340 os.environ.update(newenv)
341
341
342 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
342 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
343 # reload config so that ui.plain() takes effect
343 # reload config so that ui.plain() takes effect
344 self.ui = _renewui(self.ui)
344 self.ui = _renewui(self.ui)
345
345
346 _clearenvaliases(commands.table)
346 _clearenvaliases(commands.table)
347
347
348 capabilities = commandserver.server.capabilities.copy()
348 capabilities = commandserver.server.capabilities.copy()
349 capabilities.update({'attachio': attachio,
349 capabilities.update({'attachio': attachio,
350 'chdir': chdir,
350 'chdir': chdir,
351 'getpager': getpager,
351 'getpager': getpager,
352 'setenv': setenv})
352 'setenv': setenv})
353
353
354 # copied from mercurial/commandserver.py
354 # copied from mercurial/commandserver.py
355 class _requesthandler(SocketServer.StreamRequestHandler):
355 class _requesthandler(SocketServer.StreamRequestHandler):
356 def handle(self):
356 def handle(self):
357 # use a different process group from the master process, making this
358 # process pass kernel "is_current_pgrp_orphaned" check so signals like
359 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
360 os.setpgid(0, 0)
357 ui = self.server.ui
361 ui = self.server.ui
358 repo = self.server.repo
362 repo = self.server.repo
359 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection)
363 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection)
360 try:
364 try:
361 try:
365 try:
362 sv.serve()
366 sv.serve()
363 # handle exceptions that may be raised by command server. most of
367 # handle exceptions that may be raised by command server. most of
364 # known exceptions are caught by dispatch.
368 # known exceptions are caught by dispatch.
365 except error.Abort as inst:
369 except error.Abort as inst:
366 ui.warn(_('abort: %s\n') % inst)
370 ui.warn(_('abort: %s\n') % inst)
367 except IOError as inst:
371 except IOError as inst:
368 if inst.errno != errno.EPIPE:
372 if inst.errno != errno.EPIPE:
369 raise
373 raise
370 except KeyboardInterrupt:
374 except KeyboardInterrupt:
371 pass
375 pass
372 finally:
376 finally:
373 sv.cleanup()
377 sv.cleanup()
374 except: # re-raises
378 except: # re-raises
375 # also write traceback to error channel. otherwise client cannot
379 # also write traceback to error channel. otherwise client cannot
376 # see it because it is written to server's stderr by default.
380 # see it because it is written to server's stderr by default.
377 traceback.print_exc(file=sv.cerr)
381 traceback.print_exc(file=sv.cerr)
378 raise
382 raise
379
383
380 class chgunixservice(commandserver.unixservice):
384 class chgunixservice(commandserver.unixservice):
381 def init(self):
385 def init(self):
382 # drop options set for "hg serve --cmdserver" command
386 # drop options set for "hg serve --cmdserver" command
383 self.ui.setconfig('progress', 'assume-tty', None)
387 self.ui.setconfig('progress', 'assume-tty', None)
384 signal.signal(signal.SIGHUP, self._reloadconfig)
388 signal.signal(signal.SIGHUP, self._reloadconfig)
385 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
389 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
386 ui = self.ui
390 ui = self.ui
387 repo = self.repo
391 repo = self.repo
388 self.server = cls(self.address, _requesthandler)
392 self.server = cls(self.address, _requesthandler)
389 # avoid writing "listening at" message to stdout before attachio
393 # avoid writing "listening at" message to stdout before attachio
390 # request, which calls setvbuf()
394 # request, which calls setvbuf()
391
395
392 def _reloadconfig(self, signum, frame):
396 def _reloadconfig(self, signum, frame):
393 self.ui = self.server.ui = _renewui(self.ui)
397 self.ui = self.server.ui = _renewui(self.ui)
394
398
395 def uisetup(ui):
399 def uisetup(ui):
396 commandserver._servicemap['chgunix'] = chgunixservice
400 commandserver._servicemap['chgunix'] = chgunixservice
General Comments 0
You need to be logged in to leave comments. Login now