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