##// END OF EJS Templates
chgserver: remove Python 2 support code...
Gregory Szorc -
r50143:0bb28b77 default
parent child Browse files
Show More
@@ -1,758 +1,755
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
8 """command server extension for cHg
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 'setenv' command
19 'setenv' command
20 replace os.environ completely
20 replace os.environ completely
21
21
22 'setumask' command (DEPRECATED)
22 'setumask' command (DEPRECATED)
23 'setumask2' command
23 'setumask2' command
24 set umask
24 set umask
25
25
26 'validate' command
26 'validate' command
27 reload the config and check if the server is up to date
27 reload the config and check if the server is up to date
28
28
29 Config
29 Config
30 ------
30 ------
31
31
32 ::
32 ::
33
33
34 [chgserver]
34 [chgserver]
35 # how long (in seconds) should an idle chg server exit
35 # how long (in seconds) should an idle chg server exit
36 idletimeout = 3600
36 idletimeout = 3600
37
37
38 # whether to skip config or env change checks
38 # whether to skip config or env change checks
39 skiphash = False
39 skiphash = False
40 """
40 """
41
41
42
42
43 import inspect
43 import inspect
44 import os
44 import os
45 import re
45 import re
46 import socket
46 import socket
47 import stat
47 import stat
48 import struct
48 import struct
49 import time
49 import time
50
50
51 from .i18n import _
51 from .i18n import _
52 from .pycompat import (
52 from .pycompat import (
53 getattr,
53 getattr,
54 setattr,
54 setattr,
55 )
55 )
56 from .node import hex
56 from .node import hex
57
57
58 from . import (
58 from . import (
59 commandserver,
59 commandserver,
60 encoding,
60 encoding,
61 error,
61 error,
62 extensions,
62 extensions,
63 pycompat,
63 pycompat,
64 util,
64 util,
65 )
65 )
66
66
67 from .utils import (
67 from .utils import (
68 hashutil,
68 hashutil,
69 procutil,
69 procutil,
70 stringutil,
70 stringutil,
71 )
71 )
72
72
73
73
74 def _hashlist(items):
74 def _hashlist(items):
75 """return sha1 hexdigest for a list"""
75 """return sha1 hexdigest for a list"""
76 return hex(hashutil.sha1(stringutil.pprint(items)).digest())
76 return hex(hashutil.sha1(stringutil.pprint(items)).digest())
77
77
78
78
79 # sensitive config sections affecting confighash
79 # sensitive config sections affecting confighash
80 _configsections = [
80 _configsections = [
81 b'alias', # affects global state commands.table
81 b'alias', # affects global state commands.table
82 b'diff-tools', # affects whether gui or not in extdiff's uisetup
82 b'diff-tools', # affects whether gui or not in extdiff's uisetup
83 b'eol', # uses setconfig('eol', ...)
83 b'eol', # uses setconfig('eol', ...)
84 b'extdiff', # uisetup will register new commands
84 b'extdiff', # uisetup will register new commands
85 b'extensions',
85 b'extensions',
86 b'fastannotate', # affects annotate command and adds fastannonate cmd
86 b'fastannotate', # affects annotate command and adds fastannonate cmd
87 b'merge-tools', # affects whether gui or not in extdiff's uisetup
87 b'merge-tools', # affects whether gui or not in extdiff's uisetup
88 b'schemes', # extsetup will update global hg.schemes
88 b'schemes', # extsetup will update global hg.schemes
89 ]
89 ]
90
90
91 _configsectionitems = [
91 _configsectionitems = [
92 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
92 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
93 ]
93 ]
94
94
95 # sensitive environment variables affecting confighash
95 # sensitive environment variables affecting confighash
96 _envre = re.compile(
96 _envre = re.compile(
97 br'''\A(?:
97 br'''\A(?:
98 CHGHG
98 CHGHG
99 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
99 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
100 |HG(?:ENCODING|PLAIN).*
100 |HG(?:ENCODING|PLAIN).*
101 |LANG(?:UAGE)?
101 |LANG(?:UAGE)?
102 |LC_.*
102 |LC_.*
103 |LD_.*
103 |LD_.*
104 |PATH
104 |PATH
105 |PYTHON.*
105 |PYTHON.*
106 |TERM(?:INFO)?
106 |TERM(?:INFO)?
107 |TZ
107 |TZ
108 )\Z''',
108 )\Z''',
109 re.X,
109 re.X,
110 )
110 )
111
111
112
112
113 def _confighash(ui):
113 def _confighash(ui):
114 """return a quick hash for detecting config/env changes
114 """return a quick hash for detecting config/env changes
115
115
116 confighash is the hash of sensitive config items and environment variables.
116 confighash is the hash of sensitive config items and environment variables.
117
117
118 for chgserver, it is designed that once confighash changes, the server is
118 for chgserver, it is designed that once confighash changes, the server is
119 not qualified to serve its client and should redirect the client to a new
119 not qualified to serve its client and should redirect the client to a new
120 server. different from mtimehash, confighash change will not mark the
120 server. different from mtimehash, confighash change will not mark the
121 server outdated and exit since the user can have different configs at the
121 server outdated and exit since the user can have different configs at the
122 same time.
122 same time.
123 """
123 """
124 sectionitems = []
124 sectionitems = []
125 for section in _configsections:
125 for section in _configsections:
126 sectionitems.append(ui.configitems(section))
126 sectionitems.append(ui.configitems(section))
127 for section, item in _configsectionitems:
127 for section, item in _configsectionitems:
128 sectionitems.append(ui.config(section, item))
128 sectionitems.append(ui.config(section, item))
129 sectionhash = _hashlist(sectionitems)
129 sectionhash = _hashlist(sectionitems)
130 # If $CHGHG is set, the change to $HG should not trigger a new chg server
130 # If $CHGHG is set, the change to $HG should not trigger a new chg server
131 if b'CHGHG' in encoding.environ:
131 if b'CHGHG' in encoding.environ:
132 ignored = {b'HG'}
132 ignored = {b'HG'}
133 else:
133 else:
134 ignored = set()
134 ignored = set()
135 envitems = [
135 envitems = [
136 (k, v)
136 (k, v)
137 for k, v in pycompat.iteritems(encoding.environ)
137 for k, v in pycompat.iteritems(encoding.environ)
138 if _envre.match(k) and k not in ignored
138 if _envre.match(k) and k not in ignored
139 ]
139 ]
140 envhash = _hashlist(sorted(envitems))
140 envhash = _hashlist(sorted(envitems))
141 return sectionhash[:6] + envhash[:6]
141 return sectionhash[:6] + envhash[:6]
142
142
143
143
144 def _getmtimepaths(ui):
144 def _getmtimepaths(ui):
145 """get a list of paths that should be checked to detect change
145 """get a list of paths that should be checked to detect change
146
146
147 The list will include:
147 The list will include:
148 - extensions (will not cover all files for complex extensions)
148 - extensions (will not cover all files for complex extensions)
149 - mercurial/__version__.py
149 - mercurial/__version__.py
150 - python binary
150 - python binary
151 """
151 """
152 modules = [m for n, m in extensions.extensions(ui)]
152 modules = [m for n, m in extensions.extensions(ui)]
153 try:
153 try:
154 from . import __version__
154 from . import __version__
155
155
156 modules.append(__version__)
156 modules.append(__version__)
157 except ImportError:
157 except ImportError:
158 pass
158 pass
159 files = []
159 files = []
160 if pycompat.sysexecutable:
160 if pycompat.sysexecutable:
161 files.append(pycompat.sysexecutable)
161 files.append(pycompat.sysexecutable)
162 for m in modules:
162 for m in modules:
163 try:
163 try:
164 files.append(pycompat.fsencode(inspect.getabsfile(m)))
164 files.append(pycompat.fsencode(inspect.getabsfile(m)))
165 except TypeError:
165 except TypeError:
166 pass
166 pass
167 return sorted(set(files))
167 return sorted(set(files))
168
168
169
169
170 def _mtimehash(paths):
170 def _mtimehash(paths):
171 """return a quick hash for detecting file changes
171 """return a quick hash for detecting file changes
172
172
173 mtimehash calls stat on given paths and calculate a hash based on size and
173 mtimehash calls stat on given paths and calculate a hash based on size and
174 mtime of each file. mtimehash does not read file content because reading is
174 mtime of each file. mtimehash does not read file content because reading is
175 expensive. therefore it's not 100% reliable for detecting content changes.
175 expensive. therefore it's not 100% reliable for detecting content changes.
176 it's possible to return different hashes for same file contents.
176 it's possible to return different hashes for same file contents.
177 it's also possible to return a same hash for different file contents for
177 it's also possible to return a same hash for different file contents for
178 some carefully crafted situation.
178 some carefully crafted situation.
179
179
180 for chgserver, it is designed that once mtimehash changes, the server is
180 for chgserver, it is designed that once mtimehash changes, the server is
181 considered outdated immediately and should no longer provide service.
181 considered outdated immediately and should no longer provide service.
182
182
183 mtimehash is not included in confighash because we only know the paths of
183 mtimehash is not included in confighash because we only know the paths of
184 extensions after importing them (there is imp.find_module but that faces
184 extensions after importing them (there is imp.find_module but that faces
185 race conditions). We need to calculate confighash without importing.
185 race conditions). We need to calculate confighash without importing.
186 """
186 """
187
187
188 def trystat(path):
188 def trystat(path):
189 try:
189 try:
190 st = os.stat(path)
190 st = os.stat(path)
191 return (st[stat.ST_MTIME], st.st_size)
191 return (st[stat.ST_MTIME], st.st_size)
192 except OSError:
192 except OSError:
193 # could be ENOENT, EPERM etc. not fatal in any case
193 # could be ENOENT, EPERM etc. not fatal in any case
194 pass
194 pass
195
195
196 return _hashlist(pycompat.maplist(trystat, paths))[:12]
196 return _hashlist(pycompat.maplist(trystat, paths))[:12]
197
197
198
198
199 class hashstate(object):
199 class hashstate(object):
200 """a structure storing confighash, mtimehash, paths used for mtimehash"""
200 """a structure storing confighash, mtimehash, paths used for mtimehash"""
201
201
202 def __init__(self, confighash, mtimehash, mtimepaths):
202 def __init__(self, confighash, mtimehash, mtimepaths):
203 self.confighash = confighash
203 self.confighash = confighash
204 self.mtimehash = mtimehash
204 self.mtimehash = mtimehash
205 self.mtimepaths = mtimepaths
205 self.mtimepaths = mtimepaths
206
206
207 @staticmethod
207 @staticmethod
208 def fromui(ui, mtimepaths=None):
208 def fromui(ui, mtimepaths=None):
209 if mtimepaths is None:
209 if mtimepaths is None:
210 mtimepaths = _getmtimepaths(ui)
210 mtimepaths = _getmtimepaths(ui)
211 confighash = _confighash(ui)
211 confighash = _confighash(ui)
212 mtimehash = _mtimehash(mtimepaths)
212 mtimehash = _mtimehash(mtimepaths)
213 ui.log(
213 ui.log(
214 b'cmdserver',
214 b'cmdserver',
215 b'confighash = %s mtimehash = %s\n',
215 b'confighash = %s mtimehash = %s\n',
216 confighash,
216 confighash,
217 mtimehash,
217 mtimehash,
218 )
218 )
219 return hashstate(confighash, mtimehash, mtimepaths)
219 return hashstate(confighash, mtimehash, mtimepaths)
220
220
221
221
222 def _newchgui(srcui, csystem, attachio):
222 def _newchgui(srcui, csystem, attachio):
223 class chgui(srcui.__class__):
223 class chgui(srcui.__class__):
224 def __init__(self, src=None):
224 def __init__(self, src=None):
225 super(chgui, self).__init__(src)
225 super(chgui, self).__init__(src)
226 if src:
226 if src:
227 self._csystem = getattr(src, '_csystem', csystem)
227 self._csystem = getattr(src, '_csystem', csystem)
228 else:
228 else:
229 self._csystem = csystem
229 self._csystem = csystem
230
230
231 def _runsystem(self, cmd, environ, cwd, out):
231 def _runsystem(self, cmd, environ, cwd, out):
232 # fallback to the original system method if
232 # fallback to the original system method if
233 # a. the output stream is not stdout (e.g. stderr, cStringIO),
233 # a. the output stream is not stdout (e.g. stderr, cStringIO),
234 # b. or stdout is redirected by protectfinout(),
234 # b. or stdout is redirected by protectfinout(),
235 # because the chg client is not aware of these situations and
235 # because the chg client is not aware of these situations and
236 # will behave differently (i.e. write to stdout).
236 # will behave differently (i.e. write to stdout).
237 if (
237 if (
238 out is not self.fout
238 out is not self.fout
239 or not util.safehasattr(self.fout, b'fileno')
239 or not util.safehasattr(self.fout, b'fileno')
240 or self.fout.fileno() != procutil.stdout.fileno()
240 or self.fout.fileno() != procutil.stdout.fileno()
241 or self._finoutredirected
241 or self._finoutredirected
242 ):
242 ):
243 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
243 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
244 self.flush()
244 self.flush()
245 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
245 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
246
246
247 def _runpager(self, cmd, env=None):
247 def _runpager(self, cmd, env=None):
248 self._csystem(
248 self._csystem(
249 cmd,
249 cmd,
250 procutil.shellenviron(env),
250 procutil.shellenviron(env),
251 type=b'pager',
251 type=b'pager',
252 cmdtable={b'attachio': attachio},
252 cmdtable={b'attachio': attachio},
253 )
253 )
254 return True
254 return True
255
255
256 return chgui(srcui)
256 return chgui(srcui)
257
257
258
258
259 def _loadnewui(srcui, args, cdebug):
259 def _loadnewui(srcui, args, cdebug):
260 from . import dispatch # avoid cycle
260 from . import dispatch # avoid cycle
261
261
262 newui = srcui.__class__.load()
262 newui = srcui.__class__.load()
263 for a in [b'fin', b'fout', b'ferr', b'environ']:
263 for a in [b'fin', b'fout', b'ferr', b'environ']:
264 setattr(newui, a, getattr(srcui, a))
264 setattr(newui, a, getattr(srcui, a))
265 if util.safehasattr(srcui, b'_csystem'):
265 if util.safehasattr(srcui, b'_csystem'):
266 newui._csystem = srcui._csystem
266 newui._csystem = srcui._csystem
267
267
268 # command line args
268 # command line args
269 options = dispatch._earlyparseopts(newui, args)
269 options = dispatch._earlyparseopts(newui, args)
270 dispatch._parseconfig(newui, options[b'config'])
270 dispatch._parseconfig(newui, options[b'config'])
271
271
272 # stolen from tortoisehg.util.copydynamicconfig()
272 # stolen from tortoisehg.util.copydynamicconfig()
273 for section, name, value in srcui.walkconfig():
273 for section, name, value in srcui.walkconfig():
274 source = srcui.configsource(section, name)
274 source = srcui.configsource(section, name)
275 if b':' in source or source == b'--config' or source.startswith(b'$'):
275 if b':' in source or source == b'--config' or source.startswith(b'$'):
276 # path:line or command line, or environ
276 # path:line or command line, or environ
277 continue
277 continue
278 newui.setconfig(section, name, value, source)
278 newui.setconfig(section, name, value, source)
279
279
280 # load wd and repo config, copied from dispatch.py
280 # load wd and repo config, copied from dispatch.py
281 cwd = options[b'cwd']
281 cwd = options[b'cwd']
282 cwd = cwd and os.path.realpath(cwd) or None
282 cwd = cwd and os.path.realpath(cwd) or None
283 rpath = options[b'repository']
283 rpath = options[b'repository']
284 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
284 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
285
285
286 extensions.populateui(newui)
286 extensions.populateui(newui)
287 commandserver.setuplogging(newui, fp=cdebug)
287 commandserver.setuplogging(newui, fp=cdebug)
288 if newui is not newlui:
288 if newui is not newlui:
289 extensions.populateui(newlui)
289 extensions.populateui(newlui)
290 commandserver.setuplogging(newlui, fp=cdebug)
290 commandserver.setuplogging(newlui, fp=cdebug)
291
291
292 return (newui, newlui)
292 return (newui, newlui)
293
293
294
294
295 class channeledsystem(object):
295 class channeledsystem(object):
296 """Propagate ui.system() request in the following format:
296 """Propagate ui.system() request in the following format:
297
297
298 payload length (unsigned int),
298 payload length (unsigned int),
299 type, '\0',
299 type, '\0',
300 cmd, '\0',
300 cmd, '\0',
301 cwd, '\0',
301 cwd, '\0',
302 envkey, '=', val, '\0',
302 envkey, '=', val, '\0',
303 ...
303 ...
304 envkey, '=', val
304 envkey, '=', val
305
305
306 if type == 'system', waits for:
306 if type == 'system', waits for:
307
307
308 exitcode length (unsigned int),
308 exitcode length (unsigned int),
309 exitcode (int)
309 exitcode (int)
310
310
311 if type == 'pager', repetitively waits for a command name ending with '\n'
311 if type == 'pager', repetitively waits for a command name ending with '\n'
312 and executes it defined by cmdtable, or exits the loop if the command name
312 and executes it defined by cmdtable, or exits the loop if the command name
313 is empty.
313 is empty.
314 """
314 """
315
315
316 def __init__(self, in_, out, channel):
316 def __init__(self, in_, out, channel):
317 self.in_ = in_
317 self.in_ = in_
318 self.out = out
318 self.out = out
319 self.channel = channel
319 self.channel = channel
320
320
321 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
321 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
322 args = [type, cmd, util.abspath(cwd or b'.')]
322 args = [type, cmd, util.abspath(cwd or b'.')]
323 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
323 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
324 data = b'\0'.join(args)
324 data = b'\0'.join(args)
325 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
325 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
326 self.out.write(data)
326 self.out.write(data)
327 self.out.flush()
327 self.out.flush()
328
328
329 if type == b'system':
329 if type == b'system':
330 length = self.in_.read(4)
330 length = self.in_.read(4)
331 (length,) = struct.unpack(b'>I', length)
331 (length,) = struct.unpack(b'>I', length)
332 if length != 4:
332 if length != 4:
333 raise error.Abort(_(b'invalid response'))
333 raise error.Abort(_(b'invalid response'))
334 (rc,) = struct.unpack(b'>i', self.in_.read(4))
334 (rc,) = struct.unpack(b'>i', self.in_.read(4))
335 return rc
335 return rc
336 elif type == b'pager':
336 elif type == b'pager':
337 while True:
337 while True:
338 cmd = self.in_.readline()[:-1]
338 cmd = self.in_.readline()[:-1]
339 if not cmd:
339 if not cmd:
340 break
340 break
341 if cmdtable and cmd in cmdtable:
341 if cmdtable and cmd in cmdtable:
342 cmdtable[cmd]()
342 cmdtable[cmd]()
343 else:
343 else:
344 raise error.Abort(_(b'unexpected command: %s') % cmd)
344 raise error.Abort(_(b'unexpected command: %s') % cmd)
345 else:
345 else:
346 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
346 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
347
347
348
348
349 _iochannels = [
349 _iochannels = [
350 # server.ch, ui.fp, mode
350 # server.ch, ui.fp, mode
351 (b'cin', b'fin', 'rb'),
351 (b'cin', b'fin', 'rb'),
352 (b'cout', b'fout', 'wb'),
352 (b'cout', b'fout', 'wb'),
353 (b'cerr', b'ferr', 'wb'),
353 (b'cerr', b'ferr', 'wb'),
354 ]
354 ]
355
355
356
356
357 class chgcmdserver(commandserver.server):
357 class chgcmdserver(commandserver.server):
358 def __init__(
358 def __init__(
359 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
359 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
360 ):
360 ):
361 super(chgcmdserver, self).__init__(
361 super(chgcmdserver, self).__init__(
362 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
362 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
363 repo,
363 repo,
364 fin,
364 fin,
365 fout,
365 fout,
366 prereposetups,
366 prereposetups,
367 )
367 )
368 self.clientsock = sock
368 self.clientsock = sock
369 self._ioattached = False
369 self._ioattached = False
370 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
370 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
371 self.hashstate = hashstate
371 self.hashstate = hashstate
372 self.baseaddress = baseaddress
372 self.baseaddress = baseaddress
373 if hashstate is not None:
373 if hashstate is not None:
374 self.capabilities = self.capabilities.copy()
374 self.capabilities = self.capabilities.copy()
375 self.capabilities[b'validate'] = chgcmdserver.validate
375 self.capabilities[b'validate'] = chgcmdserver.validate
376
376
377 def cleanup(self):
377 def cleanup(self):
378 super(chgcmdserver, self).cleanup()
378 super(chgcmdserver, self).cleanup()
379 # dispatch._runcatch() does not flush outputs if exception is not
379 # dispatch._runcatch() does not flush outputs if exception is not
380 # handled by dispatch._dispatch()
380 # handled by dispatch._dispatch()
381 self.ui.flush()
381 self.ui.flush()
382 self._restoreio()
382 self._restoreio()
383 self._ioattached = False
383 self._ioattached = False
384
384
385 def attachio(self):
385 def attachio(self):
386 """Attach to client's stdio passed via unix domain socket; all
386 """Attach to client's stdio passed via unix domain socket; all
387 channels except cresult will no longer be used
387 channels except cresult will no longer be used
388 """
388 """
389 # tell client to sendmsg() with 1-byte payload, which makes it
389 # tell client to sendmsg() with 1-byte payload, which makes it
390 # distinctive from "attachio\n" command consumed by client.read()
390 # distinctive from "attachio\n" command consumed by client.read()
391 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
391 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
392 clientfds = util.recvfds(self.clientsock.fileno())
392 clientfds = util.recvfds(self.clientsock.fileno())
393 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
393 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
394
394
395 ui = self.ui
395 ui = self.ui
396 ui.flush()
396 ui.flush()
397 self._saveio()
397 self._saveio()
398 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
398 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
399 assert fd > 0
399 assert fd > 0
400 fp = getattr(ui, fn)
400 fp = getattr(ui, fn)
401 os.dup2(fd, fp.fileno())
401 os.dup2(fd, fp.fileno())
402 os.close(fd)
402 os.close(fd)
403 if self._ioattached:
403 if self._ioattached:
404 continue
404 continue
405 # reset buffering mode when client is first attached. as we want
405 # reset buffering mode when client is first attached. as we want
406 # to see output immediately on pager, the mode stays unchanged
406 # to see output immediately on pager, the mode stays unchanged
407 # when client re-attached. ferr is unchanged because it should
407 # when client re-attached. ferr is unchanged because it should
408 # be unbuffered no matter if it is a tty or not.
408 # be unbuffered no matter if it is a tty or not.
409 if fn == b'ferr':
409 if fn == b'ferr':
410 newfp = fp
410 newfp = fp
411 else:
411 else:
412 # On Python 3, the standard library doesn't offer line-buffered
412 # On Python 3, the standard library doesn't offer line-buffered
413 # binary streams, so wrap/unwrap it.
413 # binary streams, so wrap/unwrap it.
414 if fp.isatty():
414 if fp.isatty():
415 newfp = procutil.make_line_buffered(fp)
415 newfp = procutil.make_line_buffered(fp)
416 else:
416 else:
417 newfp = procutil.unwrap_line_buffered(fp)
417 newfp = procutil.unwrap_line_buffered(fp)
418 if newfp is not fp:
418 if newfp is not fp:
419 setattr(ui, fn, newfp)
419 setattr(ui, fn, newfp)
420 setattr(self, cn, newfp)
420 setattr(self, cn, newfp)
421
421
422 self._ioattached = True
422 self._ioattached = True
423 self.cresult.write(struct.pack(b'>i', len(clientfds)))
423 self.cresult.write(struct.pack(b'>i', len(clientfds)))
424
424
425 def _saveio(self):
425 def _saveio(self):
426 if self._oldios:
426 if self._oldios:
427 return
427 return
428 ui = self.ui
428 ui = self.ui
429 for cn, fn, _mode in _iochannels:
429 for cn, fn, _mode in _iochannels:
430 ch = getattr(self, cn)
430 ch = getattr(self, cn)
431 fp = getattr(ui, fn)
431 fp = getattr(ui, fn)
432 fd = os.dup(fp.fileno())
432 fd = os.dup(fp.fileno())
433 self._oldios.append((ch, fp, fd))
433 self._oldios.append((ch, fp, fd))
434
434
435 def _restoreio(self):
435 def _restoreio(self):
436 if not self._oldios:
436 if not self._oldios:
437 return
437 return
438 nullfd = os.open(os.devnull, os.O_WRONLY)
438 nullfd = os.open(os.devnull, os.O_WRONLY)
439 ui = self.ui
439 ui = self.ui
440 for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels):
440 for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels):
441 newfp = getattr(ui, fn)
441 newfp = getattr(ui, fn)
442 # On Python 2, newfp and fp may be separate file objects associated
442 # On Python 3, newfp is just a wrapper around fp even if newfp is
443 # with the same fd, so we must close newfp while it's associated
443 # not fp, so deleting newfp is safe.
444 # with the client. Otherwise the new associated fd would be closed
444 if newfp is not fp:
445 # when newfp gets deleted. On Python 3, newfp is just a wrapper
446 # around fp even if newfp is not fp, so deleting newfp is safe.
447 if not (pycompat.ispy3 or newfp is fp):
448 newfp.close()
445 newfp.close()
449 # restore original fd: fp is open again
446 # restore original fd: fp is open again
450 try:
447 try:
451 if (pycompat.ispy3 or newfp is fp) and 'w' in mode:
448 if newfp is fp and 'w' in mode:
452 # Discard buffered data which couldn't be flushed because
449 # Discard buffered data which couldn't be flushed because
453 # of EPIPE. The data should belong to the current session
450 # of EPIPE. The data should belong to the current session
454 # and should never persist.
451 # and should never persist.
455 os.dup2(nullfd, fp.fileno())
452 os.dup2(nullfd, fp.fileno())
456 fp.flush()
453 fp.flush()
457 os.dup2(fd, fp.fileno())
454 os.dup2(fd, fp.fileno())
458 except OSError as err:
455 except OSError as err:
459 # According to issue6330, running chg on heavy loaded systems
456 # According to issue6330, running chg on heavy loaded systems
460 # can lead to EBUSY. [man dup2] indicates that, on Linux,
457 # can lead to EBUSY. [man dup2] indicates that, on Linux,
461 # EBUSY comes from a race condition between open() and dup2().
458 # EBUSY comes from a race condition between open() and dup2().
462 # However it's not clear why open() race occurred for
459 # However it's not clear why open() race occurred for
463 # newfd=stdin/out/err.
460 # newfd=stdin/out/err.
464 self.ui.log(
461 self.ui.log(
465 b'chgserver',
462 b'chgserver',
466 b'got %s while duplicating %s\n',
463 b'got %s while duplicating %s\n',
467 stringutil.forcebytestr(err),
464 stringutil.forcebytestr(err),
468 fn,
465 fn,
469 )
466 )
470 os.close(fd)
467 os.close(fd)
471 setattr(self, cn, ch)
468 setattr(self, cn, ch)
472 setattr(ui, fn, fp)
469 setattr(ui, fn, fp)
473 os.close(nullfd)
470 os.close(nullfd)
474 del self._oldios[:]
471 del self._oldios[:]
475
472
476 def validate(self):
473 def validate(self):
477 """Reload the config and check if the server is up to date
474 """Reload the config and check if the server is up to date
478
475
479 Read a list of '\0' separated arguments.
476 Read a list of '\0' separated arguments.
480 Write a non-empty list of '\0' separated instruction strings or '\0'
477 Write a non-empty list of '\0' separated instruction strings or '\0'
481 if the list is empty.
478 if the list is empty.
482 An instruction string could be either:
479 An instruction string could be either:
483 - "unlink $path", the client should unlink the path to stop the
480 - "unlink $path", the client should unlink the path to stop the
484 outdated server.
481 outdated server.
485 - "redirect $path", the client should attempt to connect to $path
482 - "redirect $path", the client should attempt to connect to $path
486 first. If it does not work, start a new server. It implies
483 first. If it does not work, start a new server. It implies
487 "reconnect".
484 "reconnect".
488 - "exit $n", the client should exit directly with code n.
485 - "exit $n", the client should exit directly with code n.
489 This may happen if we cannot parse the config.
486 This may happen if we cannot parse the config.
490 - "reconnect", the client should close the connection and
487 - "reconnect", the client should close the connection and
491 reconnect.
488 reconnect.
492 If neither "reconnect" nor "redirect" is included in the instruction
489 If neither "reconnect" nor "redirect" is included in the instruction
493 list, the client can continue with this server after completing all
490 list, the client can continue with this server after completing all
494 the instructions.
491 the instructions.
495 """
492 """
496 args = self._readlist()
493 args = self._readlist()
497 errorraised = False
494 errorraised = False
498 detailed_exit_code = 255
495 detailed_exit_code = 255
499 try:
496 try:
500 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
497 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
501 except error.RepoError as inst:
498 except error.RepoError as inst:
502 # RepoError can be raised while trying to read shared source
499 # RepoError can be raised while trying to read shared source
503 # configuration
500 # configuration
504 self.ui.error(_(b"abort: %s\n") % stringutil.forcebytestr(inst))
501 self.ui.error(_(b"abort: %s\n") % stringutil.forcebytestr(inst))
505 if inst.hint:
502 if inst.hint:
506 self.ui.error(_(b"(%s)\n") % inst.hint)
503 self.ui.error(_(b"(%s)\n") % inst.hint)
507 errorraised = True
504 errorraised = True
508 except error.Error as inst:
505 except error.Error as inst:
509 if inst.detailed_exit_code is not None:
506 if inst.detailed_exit_code is not None:
510 detailed_exit_code = inst.detailed_exit_code
507 detailed_exit_code = inst.detailed_exit_code
511 self.ui.error(inst.format())
508 self.ui.error(inst.format())
512 errorraised = True
509 errorraised = True
513
510
514 if errorraised:
511 if errorraised:
515 self.ui.flush()
512 self.ui.flush()
516 exit_code = 255
513 exit_code = 255
517 if self.ui.configbool(b'ui', b'detailed-exit-code'):
514 if self.ui.configbool(b'ui', b'detailed-exit-code'):
518 exit_code = detailed_exit_code
515 exit_code = detailed_exit_code
519 self.cresult.write(b'exit %d' % exit_code)
516 self.cresult.write(b'exit %d' % exit_code)
520 return
517 return
521 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
518 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
522 insts = []
519 insts = []
523 if newhash.mtimehash != self.hashstate.mtimehash:
520 if newhash.mtimehash != self.hashstate.mtimehash:
524 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
521 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
525 insts.append(b'unlink %s' % addr)
522 insts.append(b'unlink %s' % addr)
526 # mtimehash is empty if one or more extensions fail to load.
523 # mtimehash is empty if one or more extensions fail to load.
527 # to be compatible with hg, still serve the client this time.
524 # to be compatible with hg, still serve the client this time.
528 if self.hashstate.mtimehash:
525 if self.hashstate.mtimehash:
529 insts.append(b'reconnect')
526 insts.append(b'reconnect')
530 if newhash.confighash != self.hashstate.confighash:
527 if newhash.confighash != self.hashstate.confighash:
531 addr = _hashaddress(self.baseaddress, newhash.confighash)
528 addr = _hashaddress(self.baseaddress, newhash.confighash)
532 insts.append(b'redirect %s' % addr)
529 insts.append(b'redirect %s' % addr)
533 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
530 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
534 self.cresult.write(b'\0'.join(insts) or b'\0')
531 self.cresult.write(b'\0'.join(insts) or b'\0')
535
532
536 def chdir(self):
533 def chdir(self):
537 """Change current directory
534 """Change current directory
538
535
539 Note that the behavior of --cwd option is bit different from this.
536 Note that the behavior of --cwd option is bit different from this.
540 It does not affect --config parameter.
537 It does not affect --config parameter.
541 """
538 """
542 path = self._readstr()
539 path = self._readstr()
543 if not path:
540 if not path:
544 return
541 return
545 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
542 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
546 os.chdir(path)
543 os.chdir(path)
547
544
548 def setumask(self):
545 def setumask(self):
549 """Change umask (DEPRECATED)"""
546 """Change umask (DEPRECATED)"""
550 # BUG: this does not follow the message frame structure, but kept for
547 # BUG: this does not follow the message frame structure, but kept for
551 # backward compatibility with old chg clients for some time
548 # backward compatibility with old chg clients for some time
552 self._setumask(self._read(4))
549 self._setumask(self._read(4))
553
550
554 def setumask2(self):
551 def setumask2(self):
555 """Change umask"""
552 """Change umask"""
556 data = self._readstr()
553 data = self._readstr()
557 if len(data) != 4:
554 if len(data) != 4:
558 raise ValueError(b'invalid mask length in setumask2 request')
555 raise ValueError(b'invalid mask length in setumask2 request')
559 self._setumask(data)
556 self._setumask(data)
560
557
561 def _setumask(self, data):
558 def _setumask(self, data):
562 mask = struct.unpack(b'>I', data)[0]
559 mask = struct.unpack(b'>I', data)[0]
563 self.ui.log(b'chgserver', b'setumask %r\n', mask)
560 self.ui.log(b'chgserver', b'setumask %r\n', mask)
564 util.setumask(mask)
561 util.setumask(mask)
565
562
566 def runcommand(self):
563 def runcommand(self):
567 # pager may be attached within the runcommand session, which should
564 # pager may be attached within the runcommand session, which should
568 # be detached at the end of the session. otherwise the pager wouldn't
565 # be detached at the end of the session. otherwise the pager wouldn't
569 # receive EOF.
566 # receive EOF.
570 globaloldios = self._oldios
567 globaloldios = self._oldios
571 self._oldios = []
568 self._oldios = []
572 try:
569 try:
573 return super(chgcmdserver, self).runcommand()
570 return super(chgcmdserver, self).runcommand()
574 finally:
571 finally:
575 self._restoreio()
572 self._restoreio()
576 self._oldios = globaloldios
573 self._oldios = globaloldios
577
574
578 def setenv(self):
575 def setenv(self):
579 """Clear and update os.environ
576 """Clear and update os.environ
580
577
581 Note that not all variables can make an effect on the running process.
578 Note that not all variables can make an effect on the running process.
582 """
579 """
583 l = self._readlist()
580 l = self._readlist()
584 try:
581 try:
585 newenv = dict(s.split(b'=', 1) for s in l)
582 newenv = dict(s.split(b'=', 1) for s in l)
586 except ValueError:
583 except ValueError:
587 raise ValueError(b'unexpected value in setenv request')
584 raise ValueError(b'unexpected value in setenv request')
588 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
585 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
589
586
590 encoding.environ.clear()
587 encoding.environ.clear()
591 encoding.environ.update(newenv)
588 encoding.environ.update(newenv)
592
589
593 capabilities = commandserver.server.capabilities.copy()
590 capabilities = commandserver.server.capabilities.copy()
594 capabilities.update(
591 capabilities.update(
595 {
592 {
596 b'attachio': attachio,
593 b'attachio': attachio,
597 b'chdir': chdir,
594 b'chdir': chdir,
598 b'runcommand': runcommand,
595 b'runcommand': runcommand,
599 b'setenv': setenv,
596 b'setenv': setenv,
600 b'setumask': setumask,
597 b'setumask': setumask,
601 b'setumask2': setumask2,
598 b'setumask2': setumask2,
602 }
599 }
603 )
600 )
604
601
605 if util.safehasattr(procutil, b'setprocname'):
602 if util.safehasattr(procutil, b'setprocname'):
606
603
607 def setprocname(self):
604 def setprocname(self):
608 """Change process title"""
605 """Change process title"""
609 name = self._readstr()
606 name = self._readstr()
610 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
607 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
611 procutil.setprocname(name)
608 procutil.setprocname(name)
612
609
613 capabilities[b'setprocname'] = setprocname
610 capabilities[b'setprocname'] = setprocname
614
611
615
612
616 def _tempaddress(address):
613 def _tempaddress(address):
617 return b'%s.%d.tmp' % (address, os.getpid())
614 return b'%s.%d.tmp' % (address, os.getpid())
618
615
619
616
620 def _hashaddress(address, hashstr):
617 def _hashaddress(address, hashstr):
621 # if the basename of address contains '.', use only the left part. this
618 # if the basename of address contains '.', use only the left part. this
622 # makes it possible for the client to pass 'server.tmp$PID' and follow by
619 # makes it possible for the client to pass 'server.tmp$PID' and follow by
623 # an atomic rename to avoid locking when spawning new servers.
620 # an atomic rename to avoid locking when spawning new servers.
624 dirname, basename = os.path.split(address)
621 dirname, basename = os.path.split(address)
625 basename = basename.split(b'.', 1)[0]
622 basename = basename.split(b'.', 1)[0]
626 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
623 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
627
624
628
625
629 class chgunixservicehandler(object):
626 class chgunixservicehandler(object):
630 """Set of operations for chg services"""
627 """Set of operations for chg services"""
631
628
632 pollinterval = 1 # [sec]
629 pollinterval = 1 # [sec]
633
630
634 def __init__(self, ui):
631 def __init__(self, ui):
635 self.ui = ui
632 self.ui = ui
636
633
637 # TODO: use PEP 526 syntax (`_hashstate: hashstate` at the class level)
634 # TODO: use PEP 526 syntax (`_hashstate: hashstate` at the class level)
638 # when 3.5 support is dropped.
635 # when 3.5 support is dropped.
639 self._hashstate = None # type: hashstate
636 self._hashstate = None # type: hashstate
640 self._baseaddress = None # type: bytes
637 self._baseaddress = None # type: bytes
641 self._realaddress = None # type: bytes
638 self._realaddress = None # type: bytes
642
639
643 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
640 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
644 self._lastactive = time.time()
641 self._lastactive = time.time()
645
642
646 def bindsocket(self, sock, address):
643 def bindsocket(self, sock, address):
647 self._inithashstate(address)
644 self._inithashstate(address)
648 self._checkextensions()
645 self._checkextensions()
649 self._bind(sock)
646 self._bind(sock)
650 self._createsymlink()
647 self._createsymlink()
651 # no "listening at" message should be printed to simulate hg behavior
648 # no "listening at" message should be printed to simulate hg behavior
652
649
653 def _inithashstate(self, address):
650 def _inithashstate(self, address):
654 self._baseaddress = address
651 self._baseaddress = address
655 if self.ui.configbool(b'chgserver', b'skiphash'):
652 if self.ui.configbool(b'chgserver', b'skiphash'):
656 self._hashstate = None
653 self._hashstate = None
657 self._realaddress = address
654 self._realaddress = address
658 return
655 return
659 self._hashstate = hashstate.fromui(self.ui)
656 self._hashstate = hashstate.fromui(self.ui)
660 self._realaddress = _hashaddress(address, self._hashstate.confighash)
657 self._realaddress = _hashaddress(address, self._hashstate.confighash)
661
658
662 def _checkextensions(self):
659 def _checkextensions(self):
663 if not self._hashstate:
660 if not self._hashstate:
664 return
661 return
665 if extensions.notloaded():
662 if extensions.notloaded():
666 # one or more extensions failed to load. mtimehash becomes
663 # one or more extensions failed to load. mtimehash becomes
667 # meaningless because we do not know the paths of those extensions.
664 # meaningless because we do not know the paths of those extensions.
668 # set mtimehash to an illegal hash value to invalidate the server.
665 # set mtimehash to an illegal hash value to invalidate the server.
669 self._hashstate.mtimehash = b''
666 self._hashstate.mtimehash = b''
670
667
671 def _bind(self, sock):
668 def _bind(self, sock):
672 # use a unique temp address so we can stat the file and do ownership
669 # use a unique temp address so we can stat the file and do ownership
673 # check later
670 # check later
674 tempaddress = _tempaddress(self._realaddress)
671 tempaddress = _tempaddress(self._realaddress)
675 util.bindunixsocket(sock, tempaddress)
672 util.bindunixsocket(sock, tempaddress)
676 self._socketstat = os.stat(tempaddress)
673 self._socketstat = os.stat(tempaddress)
677 sock.listen(socket.SOMAXCONN)
674 sock.listen(socket.SOMAXCONN)
678 # rename will replace the old socket file if exists atomically. the
675 # rename will replace the old socket file if exists atomically. the
679 # old server will detect ownership change and exit.
676 # old server will detect ownership change and exit.
680 util.rename(tempaddress, self._realaddress)
677 util.rename(tempaddress, self._realaddress)
681
678
682 def _createsymlink(self):
679 def _createsymlink(self):
683 if self._baseaddress == self._realaddress:
680 if self._baseaddress == self._realaddress:
684 return
681 return
685 tempaddress = _tempaddress(self._baseaddress)
682 tempaddress = _tempaddress(self._baseaddress)
686 os.symlink(os.path.basename(self._realaddress), tempaddress)
683 os.symlink(os.path.basename(self._realaddress), tempaddress)
687 util.rename(tempaddress, self._baseaddress)
684 util.rename(tempaddress, self._baseaddress)
688
685
689 def _issocketowner(self):
686 def _issocketowner(self):
690 try:
687 try:
691 st = os.stat(self._realaddress)
688 st = os.stat(self._realaddress)
692 return (
689 return (
693 st.st_ino == self._socketstat.st_ino
690 st.st_ino == self._socketstat.st_ino
694 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
691 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
695 )
692 )
696 except OSError:
693 except OSError:
697 return False
694 return False
698
695
699 def unlinksocket(self, address):
696 def unlinksocket(self, address):
700 if not self._issocketowner():
697 if not self._issocketowner():
701 return
698 return
702 # it is possible to have a race condition here that we may
699 # it is possible to have a race condition here that we may
703 # remove another server's socket file. but that's okay
700 # remove another server's socket file. but that's okay
704 # since that server will detect and exit automatically and
701 # since that server will detect and exit automatically and
705 # the client will start a new server on demand.
702 # the client will start a new server on demand.
706 util.tryunlink(self._realaddress)
703 util.tryunlink(self._realaddress)
707
704
708 def shouldexit(self):
705 def shouldexit(self):
709 if not self._issocketowner():
706 if not self._issocketowner():
710 self.ui.log(
707 self.ui.log(
711 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
708 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
712 )
709 )
713 return True
710 return True
714 if time.time() - self._lastactive > self._idletimeout:
711 if time.time() - self._lastactive > self._idletimeout:
715 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
712 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
716 return True
713 return True
717 return False
714 return False
718
715
719 def newconnection(self):
716 def newconnection(self):
720 self._lastactive = time.time()
717 self._lastactive = time.time()
721
718
722 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
719 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
723 return chgcmdserver(
720 return chgcmdserver(
724 self.ui,
721 self.ui,
725 repo,
722 repo,
726 fin,
723 fin,
727 fout,
724 fout,
728 conn,
725 conn,
729 prereposetups,
726 prereposetups,
730 self._hashstate,
727 self._hashstate,
731 self._baseaddress,
728 self._baseaddress,
732 )
729 )
733
730
734
731
735 def chgunixservice(ui, repo, opts):
732 def chgunixservice(ui, repo, opts):
736 # CHGINTERNALMARK is set by chg client. It is an indication of things are
733 # CHGINTERNALMARK is set by chg client. It is an indication of things are
737 # started by chg so other code can do things accordingly, like disabling
734 # started by chg so other code can do things accordingly, like disabling
738 # demandimport or detecting chg client started by chg client. When executed
735 # demandimport or detecting chg client started by chg client. When executed
739 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
736 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
740 # environ cleaner.
737 # environ cleaner.
741 if b'CHGINTERNALMARK' in encoding.environ:
738 if b'CHGINTERNALMARK' in encoding.environ:
742 del encoding.environ[b'CHGINTERNALMARK']
739 del encoding.environ[b'CHGINTERNALMARK']
743 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
740 # Python3.7+ "coerces" the LC_CTYPE environment variable to a UTF-8 one if
744 # it thinks the current value is "C". This breaks the hash computation and
741 # it thinks the current value is "C". This breaks the hash computation and
745 # causes chg to restart loop.
742 # causes chg to restart loop.
746 if b'CHGORIG_LC_CTYPE' in encoding.environ:
743 if b'CHGORIG_LC_CTYPE' in encoding.environ:
747 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
744 encoding.environ[b'LC_CTYPE'] = encoding.environ[b'CHGORIG_LC_CTYPE']
748 del encoding.environ[b'CHGORIG_LC_CTYPE']
745 del encoding.environ[b'CHGORIG_LC_CTYPE']
749 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
746 elif b'CHG_CLEAR_LC_CTYPE' in encoding.environ:
750 if b'LC_CTYPE' in encoding.environ:
747 if b'LC_CTYPE' in encoding.environ:
751 del encoding.environ[b'LC_CTYPE']
748 del encoding.environ[b'LC_CTYPE']
752 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
749 del encoding.environ[b'CHG_CLEAR_LC_CTYPE']
753
750
754 if repo:
751 if repo:
755 # one chgserver can serve multiple repos. drop repo information
752 # one chgserver can serve multiple repos. drop repo information
756 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
753 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
757 h = chgunixservicehandler(ui)
754 h = chgunixservicehandler(ui)
758 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
755 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now