##// END OF EJS Templates
profiling: add --no-profile to disable profiling enabled via config...
Kyle Lippincott -
r47787:a2bf93ab default
parent child Browse files
Show More
@@ -1,1380 +1,1390 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
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 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import errno
10 import errno
11 import getopt
11 import getopt
12 import io
12 import io
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import traceback
18 import traceback
19
19
20
20
21 from .i18n import _
21 from .i18n import _
22 from .pycompat import getattr
22 from .pycompat import getattr
23
23
24 from hgdemandimport import tracing
24 from hgdemandimport import tracing
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
28 color,
28 color,
29 commands,
29 commands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 help,
35 help,
36 hg,
36 hg,
37 hook,
37 hook,
38 localrepo,
38 localrepo,
39 profiling,
39 profiling,
40 pycompat,
40 pycompat,
41 rcutil,
41 rcutil,
42 registrar,
42 registrar,
43 requirements as requirementsmod,
43 requirements as requirementsmod,
44 scmutil,
44 scmutil,
45 ui as uimod,
45 ui as uimod,
46 util,
46 util,
47 vfs,
47 vfs,
48 )
48 )
49
49
50 from .utils import (
50 from .utils import (
51 procutil,
51 procutil,
52 stringutil,
52 stringutil,
53 urlutil,
53 urlutil,
54 )
54 )
55
55
56
56
57 class request(object):
57 class request(object):
58 def __init__(
58 def __init__(
59 self,
59 self,
60 args,
60 args,
61 ui=None,
61 ui=None,
62 repo=None,
62 repo=None,
63 fin=None,
63 fin=None,
64 fout=None,
64 fout=None,
65 ferr=None,
65 ferr=None,
66 fmsg=None,
66 fmsg=None,
67 prereposetups=None,
67 prereposetups=None,
68 ):
68 ):
69 self.args = args
69 self.args = args
70 self.ui = ui
70 self.ui = ui
71 self.repo = repo
71 self.repo = repo
72
72
73 # input/output/error streams
73 # input/output/error streams
74 self.fin = fin
74 self.fin = fin
75 self.fout = fout
75 self.fout = fout
76 self.ferr = ferr
76 self.ferr = ferr
77 # separate stream for status/error messages
77 # separate stream for status/error messages
78 self.fmsg = fmsg
78 self.fmsg = fmsg
79
79
80 # remember options pre-parsed by _earlyparseopts()
80 # remember options pre-parsed by _earlyparseopts()
81 self.earlyoptions = {}
81 self.earlyoptions = {}
82
82
83 # reposetups which run before extensions, useful for chg to pre-fill
83 # reposetups which run before extensions, useful for chg to pre-fill
84 # low-level repo state (for example, changelog) before extensions.
84 # low-level repo state (for example, changelog) before extensions.
85 self.prereposetups = prereposetups or []
85 self.prereposetups = prereposetups or []
86
86
87 # store the parsed and canonical command
87 # store the parsed and canonical command
88 self.canonical_command = None
88 self.canonical_command = None
89
89
90 def _runexithandlers(self):
90 def _runexithandlers(self):
91 exc = None
91 exc = None
92 handlers = self.ui._exithandlers
92 handlers = self.ui._exithandlers
93 try:
93 try:
94 while handlers:
94 while handlers:
95 func, args, kwargs = handlers.pop()
95 func, args, kwargs = handlers.pop()
96 try:
96 try:
97 func(*args, **kwargs)
97 func(*args, **kwargs)
98 except: # re-raises below
98 except: # re-raises below
99 if exc is None:
99 if exc is None:
100 exc = sys.exc_info()[1]
100 exc = sys.exc_info()[1]
101 self.ui.warnnoi18n(b'error in exit handlers:\n')
101 self.ui.warnnoi18n(b'error in exit handlers:\n')
102 self.ui.traceback(force=True)
102 self.ui.traceback(force=True)
103 finally:
103 finally:
104 if exc is not None:
104 if exc is not None:
105 raise exc
105 raise exc
106
106
107
107
108 def _flushstdio(ui, err):
108 def _flushstdio(ui, err):
109 status = None
109 status = None
110 # In all cases we try to flush stdio streams.
110 # In all cases we try to flush stdio streams.
111 if util.safehasattr(ui, b'fout'):
111 if util.safehasattr(ui, b'fout'):
112 assert ui is not None # help pytype
112 assert ui is not None # help pytype
113 assert ui.fout is not None # help pytype
113 assert ui.fout is not None # help pytype
114 try:
114 try:
115 ui.fout.flush()
115 ui.fout.flush()
116 except IOError as e:
116 except IOError as e:
117 err = e
117 err = e
118 status = -1
118 status = -1
119
119
120 if util.safehasattr(ui, b'ferr'):
120 if util.safehasattr(ui, b'ferr'):
121 assert ui is not None # help pytype
121 assert ui is not None # help pytype
122 assert ui.ferr is not None # help pytype
122 assert ui.ferr is not None # help pytype
123 try:
123 try:
124 if err is not None and err.errno != errno.EPIPE:
124 if err is not None and err.errno != errno.EPIPE:
125 ui.ferr.write(
125 ui.ferr.write(
126 b'abort: %s\n' % encoding.strtolocal(err.strerror)
126 b'abort: %s\n' % encoding.strtolocal(err.strerror)
127 )
127 )
128 ui.ferr.flush()
128 ui.ferr.flush()
129 # There's not much we can do about an I/O error here. So (possibly)
129 # There's not much we can do about an I/O error here. So (possibly)
130 # change the status code and move on.
130 # change the status code and move on.
131 except IOError:
131 except IOError:
132 status = -1
132 status = -1
133
133
134 return status
134 return status
135
135
136
136
137 def run():
137 def run():
138 """run the command in sys.argv"""
138 """run the command in sys.argv"""
139 try:
139 try:
140 initstdio()
140 initstdio()
141 with tracing.log('parse args into request'):
141 with tracing.log('parse args into request'):
142 req = request(pycompat.sysargv[1:])
142 req = request(pycompat.sysargv[1:])
143
143
144 status = dispatch(req)
144 status = dispatch(req)
145 _silencestdio()
145 _silencestdio()
146 except KeyboardInterrupt:
146 except KeyboardInterrupt:
147 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
147 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
148 # be printed to console to avoid another IOError/KeyboardInterrupt.
148 # be printed to console to avoid another IOError/KeyboardInterrupt.
149 status = -1
149 status = -1
150 sys.exit(status & 255)
150 sys.exit(status & 255)
151
151
152
152
153 if pycompat.ispy3:
153 if pycompat.ispy3:
154
154
155 def initstdio():
155 def initstdio():
156 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
156 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
157 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
157 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
158 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
158 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
159 # instances, which write to the underlying stdio file descriptor in binary
159 # instances, which write to the underlying stdio file descriptor in binary
160 # mode. ui.write() uses \n for line endings and no line ending normalization
160 # mode. ui.write() uses \n for line endings and no line ending normalization
161 # is attempted through this interface. This "just works," even if the system
161 # is attempted through this interface. This "just works," even if the system
162 # preferred line ending is not \n.
162 # preferred line ending is not \n.
163 #
163 #
164 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
164 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
165 # and sys.stderr. They will inherit the line ending normalization settings,
165 # and sys.stderr. They will inherit the line ending normalization settings,
166 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
166 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
167 # "just work," here we change the sys.* streams to disable line ending
167 # "just work," here we change the sys.* streams to disable line ending
168 # normalization, ensuring compatibility with our ui type.
168 # normalization, ensuring compatibility with our ui type.
169
169
170 if sys.stdout is not None:
170 if sys.stdout is not None:
171 # write_through is new in Python 3.7.
171 # write_through is new in Python 3.7.
172 kwargs = {
172 kwargs = {
173 "newline": "\n",
173 "newline": "\n",
174 "line_buffering": sys.stdout.line_buffering,
174 "line_buffering": sys.stdout.line_buffering,
175 }
175 }
176 if util.safehasattr(sys.stdout, "write_through"):
176 if util.safehasattr(sys.stdout, "write_through"):
177 # pytype: disable=attribute-error
177 # pytype: disable=attribute-error
178 kwargs["write_through"] = sys.stdout.write_through
178 kwargs["write_through"] = sys.stdout.write_through
179 # pytype: enable=attribute-error
179 # pytype: enable=attribute-error
180 sys.stdout = io.TextIOWrapper(
180 sys.stdout = io.TextIOWrapper(
181 sys.stdout.buffer,
181 sys.stdout.buffer,
182 sys.stdout.encoding,
182 sys.stdout.encoding,
183 sys.stdout.errors,
183 sys.stdout.errors,
184 **kwargs
184 **kwargs
185 )
185 )
186
186
187 if sys.stderr is not None:
187 if sys.stderr is not None:
188 kwargs = {
188 kwargs = {
189 "newline": "\n",
189 "newline": "\n",
190 "line_buffering": sys.stderr.line_buffering,
190 "line_buffering": sys.stderr.line_buffering,
191 }
191 }
192 if util.safehasattr(sys.stderr, "write_through"):
192 if util.safehasattr(sys.stderr, "write_through"):
193 # pytype: disable=attribute-error
193 # pytype: disable=attribute-error
194 kwargs["write_through"] = sys.stderr.write_through
194 kwargs["write_through"] = sys.stderr.write_through
195 # pytype: enable=attribute-error
195 # pytype: enable=attribute-error
196 sys.stderr = io.TextIOWrapper(
196 sys.stderr = io.TextIOWrapper(
197 sys.stderr.buffer,
197 sys.stderr.buffer,
198 sys.stderr.encoding,
198 sys.stderr.encoding,
199 sys.stderr.errors,
199 sys.stderr.errors,
200 **kwargs
200 **kwargs
201 )
201 )
202
202
203 if sys.stdin is not None:
203 if sys.stdin is not None:
204 # No write_through on read-only stream.
204 # No write_through on read-only stream.
205 sys.stdin = io.TextIOWrapper(
205 sys.stdin = io.TextIOWrapper(
206 sys.stdin.buffer,
206 sys.stdin.buffer,
207 sys.stdin.encoding,
207 sys.stdin.encoding,
208 sys.stdin.errors,
208 sys.stdin.errors,
209 # None is universal newlines mode.
209 # None is universal newlines mode.
210 newline=None,
210 newline=None,
211 line_buffering=sys.stdin.line_buffering,
211 line_buffering=sys.stdin.line_buffering,
212 )
212 )
213
213
214 def _silencestdio():
214 def _silencestdio():
215 for fp in (sys.stdout, sys.stderr):
215 for fp in (sys.stdout, sys.stderr):
216 if fp is None:
216 if fp is None:
217 continue
217 continue
218 # Check if the file is okay
218 # Check if the file is okay
219 try:
219 try:
220 fp.flush()
220 fp.flush()
221 continue
221 continue
222 except IOError:
222 except IOError:
223 pass
223 pass
224 # Otherwise mark it as closed to silence "Exception ignored in"
224 # Otherwise mark it as closed to silence "Exception ignored in"
225 # message emitted by the interpreter finalizer.
225 # message emitted by the interpreter finalizer.
226 try:
226 try:
227 fp.close()
227 fp.close()
228 except IOError:
228 except IOError:
229 pass
229 pass
230
230
231
231
232 else:
232 else:
233
233
234 def initstdio():
234 def initstdio():
235 for fp in (sys.stdin, sys.stdout, sys.stderr):
235 for fp in (sys.stdin, sys.stdout, sys.stderr):
236 procutil.setbinary(fp)
236 procutil.setbinary(fp)
237
237
238 def _silencestdio():
238 def _silencestdio():
239 pass
239 pass
240
240
241
241
242 def _formatargs(args):
242 def _formatargs(args):
243 return b' '.join(procutil.shellquote(a) for a in args)
243 return b' '.join(procutil.shellquote(a) for a in args)
244
244
245
245
246 def dispatch(req):
246 def dispatch(req):
247 """run the command specified in req.args; returns an integer status code"""
247 """run the command specified in req.args; returns an integer status code"""
248 err = None
248 err = None
249 try:
249 try:
250 status = _rundispatch(req)
250 status = _rundispatch(req)
251 except error.StdioError as e:
251 except error.StdioError as e:
252 err = e
252 err = e
253 status = -1
253 status = -1
254
254
255 ret = _flushstdio(req.ui, err)
255 ret = _flushstdio(req.ui, err)
256 if ret:
256 if ret:
257 status = ret
257 status = ret
258 return status
258 return status
259
259
260
260
261 def _rundispatch(req):
261 def _rundispatch(req):
262 with tracing.log('dispatch._rundispatch'):
262 with tracing.log('dispatch._rundispatch'):
263 if req.ferr:
263 if req.ferr:
264 ferr = req.ferr
264 ferr = req.ferr
265 elif req.ui:
265 elif req.ui:
266 ferr = req.ui.ferr
266 ferr = req.ui.ferr
267 else:
267 else:
268 ferr = procutil.stderr
268 ferr = procutil.stderr
269
269
270 try:
270 try:
271 if not req.ui:
271 if not req.ui:
272 req.ui = uimod.ui.load()
272 req.ui = uimod.ui.load()
273 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
273 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
274 if req.earlyoptions[b'traceback']:
274 if req.earlyoptions[b'traceback']:
275 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
275 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
276
276
277 # set ui streams from the request
277 # set ui streams from the request
278 if req.fin:
278 if req.fin:
279 req.ui.fin = req.fin
279 req.ui.fin = req.fin
280 if req.fout:
280 if req.fout:
281 req.ui.fout = req.fout
281 req.ui.fout = req.fout
282 if req.ferr:
282 if req.ferr:
283 req.ui.ferr = req.ferr
283 req.ui.ferr = req.ferr
284 if req.fmsg:
284 if req.fmsg:
285 req.ui.fmsg = req.fmsg
285 req.ui.fmsg = req.fmsg
286 except error.Abort as inst:
286 except error.Abort as inst:
287 ferr.write(inst.format())
287 ferr.write(inst.format())
288 return -1
288 return -1
289
289
290 msg = _formatargs(req.args)
290 msg = _formatargs(req.args)
291 starttime = util.timer()
291 starttime = util.timer()
292 ret = 1 # default of Python exit code on unhandled exception
292 ret = 1 # default of Python exit code on unhandled exception
293 try:
293 try:
294 ret = _runcatch(req) or 0
294 ret = _runcatch(req) or 0
295 except error.ProgrammingError as inst:
295 except error.ProgrammingError as inst:
296 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
296 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
297 if inst.hint:
297 if inst.hint:
298 req.ui.error(_(b'** (%s)\n') % inst.hint)
298 req.ui.error(_(b'** (%s)\n') % inst.hint)
299 raise
299 raise
300 except KeyboardInterrupt as inst:
300 except KeyboardInterrupt as inst:
301 try:
301 try:
302 if isinstance(inst, error.SignalInterrupt):
302 if isinstance(inst, error.SignalInterrupt):
303 msg = _(b"killed!\n")
303 msg = _(b"killed!\n")
304 else:
304 else:
305 msg = _(b"interrupted!\n")
305 msg = _(b"interrupted!\n")
306 req.ui.error(msg)
306 req.ui.error(msg)
307 except error.SignalInterrupt:
307 except error.SignalInterrupt:
308 # maybe pager would quit without consuming all the output, and
308 # maybe pager would quit without consuming all the output, and
309 # SIGPIPE was raised. we cannot print anything in this case.
309 # SIGPIPE was raised. we cannot print anything in this case.
310 pass
310 pass
311 except IOError as inst:
311 except IOError as inst:
312 if inst.errno != errno.EPIPE:
312 if inst.errno != errno.EPIPE:
313 raise
313 raise
314 ret = -1
314 ret = -1
315 finally:
315 finally:
316 duration = util.timer() - starttime
316 duration = util.timer() - starttime
317 req.ui.flush() # record blocked times
317 req.ui.flush() # record blocked times
318 if req.ui.logblockedtimes:
318 if req.ui.logblockedtimes:
319 req.ui._blockedtimes[b'command_duration'] = duration * 1000
319 req.ui._blockedtimes[b'command_duration'] = duration * 1000
320 req.ui.log(
320 req.ui.log(
321 b'uiblocked',
321 b'uiblocked',
322 b'ui blocked ms\n',
322 b'ui blocked ms\n',
323 **pycompat.strkwargs(req.ui._blockedtimes)
323 **pycompat.strkwargs(req.ui._blockedtimes)
324 )
324 )
325 return_code = ret & 255
325 return_code = ret & 255
326 req.ui.log(
326 req.ui.log(
327 b"commandfinish",
327 b"commandfinish",
328 b"%s exited %d after %0.2f seconds\n",
328 b"%s exited %d after %0.2f seconds\n",
329 msg,
329 msg,
330 return_code,
330 return_code,
331 duration,
331 duration,
332 return_code=return_code,
332 return_code=return_code,
333 duration=duration,
333 duration=duration,
334 canonical_command=req.canonical_command,
334 canonical_command=req.canonical_command,
335 )
335 )
336 try:
336 try:
337 req._runexithandlers()
337 req._runexithandlers()
338 except: # exiting, so no re-raises
338 except: # exiting, so no re-raises
339 ret = ret or -1
339 ret = ret or -1
340 # do flush again since ui.log() and exit handlers may write to ui
340 # do flush again since ui.log() and exit handlers may write to ui
341 req.ui.flush()
341 req.ui.flush()
342 return ret
342 return ret
343
343
344
344
345 def _runcatch(req):
345 def _runcatch(req):
346 with tracing.log('dispatch._runcatch'):
346 with tracing.log('dispatch._runcatch'):
347
347
348 def catchterm(*args):
348 def catchterm(*args):
349 raise error.SignalInterrupt
349 raise error.SignalInterrupt
350
350
351 ui = req.ui
351 ui = req.ui
352 try:
352 try:
353 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
353 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
354 num = getattr(signal, name, None)
354 num = getattr(signal, name, None)
355 if num:
355 if num:
356 signal.signal(num, catchterm)
356 signal.signal(num, catchterm)
357 except ValueError:
357 except ValueError:
358 pass # happens if called in a thread
358 pass # happens if called in a thread
359
359
360 def _runcatchfunc():
360 def _runcatchfunc():
361 realcmd = None
361 realcmd = None
362 try:
362 try:
363 cmdargs = fancyopts.fancyopts(
363 cmdargs = fancyopts.fancyopts(
364 req.args[:], commands.globalopts, {}
364 req.args[:], commands.globalopts, {}
365 )
365 )
366 cmd = cmdargs[0]
366 cmd = cmdargs[0]
367 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
367 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
368 realcmd = aliases[0]
368 realcmd = aliases[0]
369 except (
369 except (
370 error.UnknownCommand,
370 error.UnknownCommand,
371 error.AmbiguousCommand,
371 error.AmbiguousCommand,
372 IndexError,
372 IndexError,
373 getopt.GetoptError,
373 getopt.GetoptError,
374 ):
374 ):
375 # Don't handle this here. We know the command is
375 # Don't handle this here. We know the command is
376 # invalid, but all we're worried about for now is that
376 # invalid, but all we're worried about for now is that
377 # it's not a command that server operators expect to
377 # it's not a command that server operators expect to
378 # be safe to offer to users in a sandbox.
378 # be safe to offer to users in a sandbox.
379 pass
379 pass
380 if realcmd == b'serve' and b'--stdio' in cmdargs:
380 if realcmd == b'serve' and b'--stdio' in cmdargs:
381 # We want to constrain 'hg serve --stdio' instances pretty
381 # We want to constrain 'hg serve --stdio' instances pretty
382 # closely, as many shared-ssh access tools want to grant
382 # closely, as many shared-ssh access tools want to grant
383 # access to run *only* 'hg -R $repo serve --stdio'. We
383 # access to run *only* 'hg -R $repo serve --stdio'. We
384 # restrict to exactly that set of arguments, and prohibit
384 # restrict to exactly that set of arguments, and prohibit
385 # any repo name that starts with '--' to prevent
385 # any repo name that starts with '--' to prevent
386 # shenanigans wherein a user does something like pass
386 # shenanigans wherein a user does something like pass
387 # --debugger or --config=ui.debugger=1 as a repo
387 # --debugger or --config=ui.debugger=1 as a repo
388 # name. This used to actually run the debugger.
388 # name. This used to actually run the debugger.
389 if (
389 if (
390 len(req.args) != 4
390 len(req.args) != 4
391 or req.args[0] != b'-R'
391 or req.args[0] != b'-R'
392 or req.args[1].startswith(b'--')
392 or req.args[1].startswith(b'--')
393 or req.args[2] != b'serve'
393 or req.args[2] != b'serve'
394 or req.args[3] != b'--stdio'
394 or req.args[3] != b'--stdio'
395 ):
395 ):
396 raise error.Abort(
396 raise error.Abort(
397 _(b'potentially unsafe serve --stdio invocation: %s')
397 _(b'potentially unsafe serve --stdio invocation: %s')
398 % (stringutil.pprint(req.args),)
398 % (stringutil.pprint(req.args),)
399 )
399 )
400
400
401 try:
401 try:
402 debugger = b'pdb'
402 debugger = b'pdb'
403 debugtrace = {b'pdb': pdb.set_trace}
403 debugtrace = {b'pdb': pdb.set_trace}
404 debugmortem = {b'pdb': pdb.post_mortem}
404 debugmortem = {b'pdb': pdb.post_mortem}
405
405
406 # read --config before doing anything else
406 # read --config before doing anything else
407 # (e.g. to change trust settings for reading .hg/hgrc)
407 # (e.g. to change trust settings for reading .hg/hgrc)
408 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
408 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
409
409
410 if req.repo:
410 if req.repo:
411 # copy configs that were passed on the cmdline (--config) to
411 # copy configs that were passed on the cmdline (--config) to
412 # the repo ui
412 # the repo ui
413 for sec, name, val in cfgs:
413 for sec, name, val in cfgs:
414 req.repo.ui.setconfig(
414 req.repo.ui.setconfig(
415 sec, name, val, source=b'--config'
415 sec, name, val, source=b'--config'
416 )
416 )
417
417
418 # developer config: ui.debugger
418 # developer config: ui.debugger
419 debugger = ui.config(b"ui", b"debugger")
419 debugger = ui.config(b"ui", b"debugger")
420 debugmod = pdb
420 debugmod = pdb
421 if not debugger or ui.plain():
421 if not debugger or ui.plain():
422 # if we are in HGPLAIN mode, then disable custom debugging
422 # if we are in HGPLAIN mode, then disable custom debugging
423 debugger = b'pdb'
423 debugger = b'pdb'
424 elif req.earlyoptions[b'debugger']:
424 elif req.earlyoptions[b'debugger']:
425 # This import can be slow for fancy debuggers, so only
425 # This import can be slow for fancy debuggers, so only
426 # do it when absolutely necessary, i.e. when actual
426 # do it when absolutely necessary, i.e. when actual
427 # debugging has been requested
427 # debugging has been requested
428 with demandimport.deactivated():
428 with demandimport.deactivated():
429 try:
429 try:
430 debugmod = __import__(debugger)
430 debugmod = __import__(debugger)
431 except ImportError:
431 except ImportError:
432 pass # Leave debugmod = pdb
432 pass # Leave debugmod = pdb
433
433
434 debugtrace[debugger] = debugmod.set_trace
434 debugtrace[debugger] = debugmod.set_trace
435 debugmortem[debugger] = debugmod.post_mortem
435 debugmortem[debugger] = debugmod.post_mortem
436
436
437 # enter the debugger before command execution
437 # enter the debugger before command execution
438 if req.earlyoptions[b'debugger']:
438 if req.earlyoptions[b'debugger']:
439 ui.warn(
439 ui.warn(
440 _(
440 _(
441 b"entering debugger - "
441 b"entering debugger - "
442 b"type c to continue starting hg or h for help\n"
442 b"type c to continue starting hg or h for help\n"
443 )
443 )
444 )
444 )
445
445
446 if (
446 if (
447 debugger != b'pdb'
447 debugger != b'pdb'
448 and debugtrace[debugger] == debugtrace[b'pdb']
448 and debugtrace[debugger] == debugtrace[b'pdb']
449 ):
449 ):
450 ui.warn(
450 ui.warn(
451 _(
451 _(
452 b"%s debugger specified "
452 b"%s debugger specified "
453 b"but its module was not found\n"
453 b"but its module was not found\n"
454 )
454 )
455 % debugger
455 % debugger
456 )
456 )
457 with demandimport.deactivated():
457 with demandimport.deactivated():
458 debugtrace[debugger]()
458 debugtrace[debugger]()
459 try:
459 try:
460 return _dispatch(req)
460 return _dispatch(req)
461 finally:
461 finally:
462 ui.flush()
462 ui.flush()
463 except: # re-raises
463 except: # re-raises
464 # enter the debugger when we hit an exception
464 # enter the debugger when we hit an exception
465 if req.earlyoptions[b'debugger']:
465 if req.earlyoptions[b'debugger']:
466 traceback.print_exc()
466 traceback.print_exc()
467 debugmortem[debugger](sys.exc_info()[2])
467 debugmortem[debugger](sys.exc_info()[2])
468 raise
468 raise
469
469
470 return _callcatch(ui, _runcatchfunc)
470 return _callcatch(ui, _runcatchfunc)
471
471
472
472
473 def _callcatch(ui, func):
473 def _callcatch(ui, func):
474 """like scmutil.callcatch but handles more high-level exceptions about
474 """like scmutil.callcatch but handles more high-level exceptions about
475 config parsing and commands. besides, use handlecommandexception to handle
475 config parsing and commands. besides, use handlecommandexception to handle
476 uncaught exceptions.
476 uncaught exceptions.
477 """
477 """
478 detailed_exit_code = -1
478 detailed_exit_code = -1
479 try:
479 try:
480 return scmutil.callcatch(ui, func)
480 return scmutil.callcatch(ui, func)
481 except error.AmbiguousCommand as inst:
481 except error.AmbiguousCommand as inst:
482 detailed_exit_code = 10
482 detailed_exit_code = 10
483 ui.warn(
483 ui.warn(
484 _(b"hg: command '%s' is ambiguous:\n %s\n")
484 _(b"hg: command '%s' is ambiguous:\n %s\n")
485 % (inst.prefix, b" ".join(inst.matches))
485 % (inst.prefix, b" ".join(inst.matches))
486 )
486 )
487 except error.CommandError as inst:
487 except error.CommandError as inst:
488 detailed_exit_code = 10
488 detailed_exit_code = 10
489 if inst.command:
489 if inst.command:
490 ui.pager(b'help')
490 ui.pager(b'help')
491 msgbytes = pycompat.bytestr(inst.message)
491 msgbytes = pycompat.bytestr(inst.message)
492 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
492 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
493 commands.help_(ui, inst.command, full=False, command=True)
493 commands.help_(ui, inst.command, full=False, command=True)
494 else:
494 else:
495 ui.warn(_(b"hg: %s\n") % inst.message)
495 ui.warn(_(b"hg: %s\n") % inst.message)
496 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
496 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
497 except error.UnknownCommand as inst:
497 except error.UnknownCommand as inst:
498 detailed_exit_code = 10
498 detailed_exit_code = 10
499 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
499 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
500 try:
500 try:
501 # check if the command is in a disabled extension
501 # check if the command is in a disabled extension
502 # (but don't check for extensions themselves)
502 # (but don't check for extensions themselves)
503 formatted = help.formattedhelp(
503 formatted = help.formattedhelp(
504 ui, commands, inst.command, unknowncmd=True
504 ui, commands, inst.command, unknowncmd=True
505 )
505 )
506 ui.warn(nocmdmsg)
506 ui.warn(nocmdmsg)
507 ui.write(formatted)
507 ui.write(formatted)
508 except (error.UnknownCommand, error.Abort):
508 except (error.UnknownCommand, error.Abort):
509 suggested = False
509 suggested = False
510 if inst.all_commands:
510 if inst.all_commands:
511 sim = error.getsimilar(inst.all_commands, inst.command)
511 sim = error.getsimilar(inst.all_commands, inst.command)
512 if sim:
512 if sim:
513 ui.warn(nocmdmsg)
513 ui.warn(nocmdmsg)
514 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
514 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
515 suggested = True
515 suggested = True
516 if not suggested:
516 if not suggested:
517 ui.warn(nocmdmsg)
517 ui.warn(nocmdmsg)
518 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
518 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
519 except IOError:
519 except IOError:
520 raise
520 raise
521 except KeyboardInterrupt:
521 except KeyboardInterrupt:
522 raise
522 raise
523 except: # probably re-raises
523 except: # probably re-raises
524 if not handlecommandexception(ui):
524 if not handlecommandexception(ui):
525 raise
525 raise
526
526
527 if ui.configbool(b'ui', b'detailed-exit-code'):
527 if ui.configbool(b'ui', b'detailed-exit-code'):
528 return detailed_exit_code
528 return detailed_exit_code
529 else:
529 else:
530 return -1
530 return -1
531
531
532
532
533 def aliasargs(fn, givenargs):
533 def aliasargs(fn, givenargs):
534 args = []
534 args = []
535 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
535 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
536 if not util.safehasattr(fn, b'_origfunc'):
536 if not util.safehasattr(fn, b'_origfunc'):
537 args = getattr(fn, 'args', args)
537 args = getattr(fn, 'args', args)
538 if args:
538 if args:
539 cmd = b' '.join(map(procutil.shellquote, args))
539 cmd = b' '.join(map(procutil.shellquote, args))
540
540
541 nums = []
541 nums = []
542
542
543 def replacer(m):
543 def replacer(m):
544 num = int(m.group(1)) - 1
544 num = int(m.group(1)) - 1
545 nums.append(num)
545 nums.append(num)
546 if num < len(givenargs):
546 if num < len(givenargs):
547 return givenargs[num]
547 return givenargs[num]
548 raise error.InputError(_(b'too few arguments for command alias'))
548 raise error.InputError(_(b'too few arguments for command alias'))
549
549
550 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
550 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
551 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
551 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
552 args = pycompat.shlexsplit(cmd)
552 args = pycompat.shlexsplit(cmd)
553 return args + givenargs
553 return args + givenargs
554
554
555
555
556 def aliasinterpolate(name, args, cmd):
556 def aliasinterpolate(name, args, cmd):
557 """interpolate args into cmd for shell aliases
557 """interpolate args into cmd for shell aliases
558
558
559 This also handles $0, $@ and "$@".
559 This also handles $0, $@ and "$@".
560 """
560 """
561 # util.interpolate can't deal with "$@" (with quotes) because it's only
561 # util.interpolate can't deal with "$@" (with quotes) because it's only
562 # built to match prefix + patterns.
562 # built to match prefix + patterns.
563 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
563 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
564 replacemap[b'$0'] = name
564 replacemap[b'$0'] = name
565 replacemap[b'$$'] = b'$'
565 replacemap[b'$$'] = b'$'
566 replacemap[b'$@'] = b' '.join(args)
566 replacemap[b'$@'] = b' '.join(args)
567 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
567 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
568 # parameters, separated out into words. Emulate the same behavior here by
568 # parameters, separated out into words. Emulate the same behavior here by
569 # quoting the arguments individually. POSIX shells will then typically
569 # quoting the arguments individually. POSIX shells will then typically
570 # tokenize each argument into exactly one word.
570 # tokenize each argument into exactly one word.
571 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
571 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
572 # escape '\$' for regex
572 # escape '\$' for regex
573 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
573 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
574 r = re.compile(regex)
574 r = re.compile(regex)
575 return r.sub(lambda x: replacemap[x.group()], cmd)
575 return r.sub(lambda x: replacemap[x.group()], cmd)
576
576
577
577
578 class cmdalias(object):
578 class cmdalias(object):
579 def __init__(self, ui, name, definition, cmdtable, source):
579 def __init__(self, ui, name, definition, cmdtable, source):
580 self.name = self.cmd = name
580 self.name = self.cmd = name
581 self.cmdname = b''
581 self.cmdname = b''
582 self.definition = definition
582 self.definition = definition
583 self.fn = None
583 self.fn = None
584 self.givenargs = []
584 self.givenargs = []
585 self.opts = []
585 self.opts = []
586 self.help = b''
586 self.help = b''
587 self.badalias = None
587 self.badalias = None
588 self.unknowncmd = False
588 self.unknowncmd = False
589 self.source = source
589 self.source = source
590
590
591 try:
591 try:
592 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
592 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
593 for alias, e in pycompat.iteritems(cmdtable):
593 for alias, e in pycompat.iteritems(cmdtable):
594 if e is entry:
594 if e is entry:
595 self.cmd = alias
595 self.cmd = alias
596 break
596 break
597 self.shadows = True
597 self.shadows = True
598 except error.UnknownCommand:
598 except error.UnknownCommand:
599 self.shadows = False
599 self.shadows = False
600
600
601 if not self.definition:
601 if not self.definition:
602 self.badalias = _(b"no definition for alias '%s'") % self.name
602 self.badalias = _(b"no definition for alias '%s'") % self.name
603 return
603 return
604
604
605 if self.definition.startswith(b'!'):
605 if self.definition.startswith(b'!'):
606 shdef = self.definition[1:]
606 shdef = self.definition[1:]
607 self.shell = True
607 self.shell = True
608
608
609 def fn(ui, *args):
609 def fn(ui, *args):
610 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
610 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
611
611
612 def _checkvar(m):
612 def _checkvar(m):
613 if m.groups()[0] == b'$':
613 if m.groups()[0] == b'$':
614 return m.group()
614 return m.group()
615 elif int(m.groups()[0]) <= len(args):
615 elif int(m.groups()[0]) <= len(args):
616 return m.group()
616 return m.group()
617 else:
617 else:
618 ui.debug(
618 ui.debug(
619 b"No argument found for substitution "
619 b"No argument found for substitution "
620 b"of %i variable in alias '%s' definition.\n"
620 b"of %i variable in alias '%s' definition.\n"
621 % (int(m.groups()[0]), self.name)
621 % (int(m.groups()[0]), self.name)
622 )
622 )
623 return b''
623 return b''
624
624
625 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
625 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
626 cmd = aliasinterpolate(self.name, args, cmd)
626 cmd = aliasinterpolate(self.name, args, cmd)
627 return ui.system(
627 return ui.system(
628 cmd, environ=env, blockedtag=b'alias_%s' % self.name
628 cmd, environ=env, blockedtag=b'alias_%s' % self.name
629 )
629 )
630
630
631 self.fn = fn
631 self.fn = fn
632 self.alias = True
632 self.alias = True
633 self._populatehelp(ui, name, shdef, self.fn)
633 self._populatehelp(ui, name, shdef, self.fn)
634 return
634 return
635
635
636 try:
636 try:
637 args = pycompat.shlexsplit(self.definition)
637 args = pycompat.shlexsplit(self.definition)
638 except ValueError as inst:
638 except ValueError as inst:
639 self.badalias = _(b"error in definition for alias '%s': %s") % (
639 self.badalias = _(b"error in definition for alias '%s': %s") % (
640 self.name,
640 self.name,
641 stringutil.forcebytestr(inst),
641 stringutil.forcebytestr(inst),
642 )
642 )
643 return
643 return
644 earlyopts, args = _earlysplitopts(args)
644 earlyopts, args = _earlysplitopts(args)
645 if earlyopts:
645 if earlyopts:
646 self.badalias = _(
646 self.badalias = _(
647 b"error in definition for alias '%s': %s may "
647 b"error in definition for alias '%s': %s may "
648 b"only be given on the command line"
648 b"only be given on the command line"
649 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
649 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
650 return
650 return
651 self.cmdname = cmd = args.pop(0)
651 self.cmdname = cmd = args.pop(0)
652 self.givenargs = args
652 self.givenargs = args
653
653
654 try:
654 try:
655 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
655 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
656 if len(tableentry) > 2:
656 if len(tableentry) > 2:
657 self.fn, self.opts, cmdhelp = tableentry
657 self.fn, self.opts, cmdhelp = tableentry
658 else:
658 else:
659 self.fn, self.opts = tableentry
659 self.fn, self.opts = tableentry
660 cmdhelp = None
660 cmdhelp = None
661
661
662 self.alias = True
662 self.alias = True
663 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
663 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
664
664
665 except error.UnknownCommand:
665 except error.UnknownCommand:
666 self.badalias = _(
666 self.badalias = _(
667 b"alias '%s' resolves to unknown command '%s'"
667 b"alias '%s' resolves to unknown command '%s'"
668 ) % (
668 ) % (
669 self.name,
669 self.name,
670 cmd,
670 cmd,
671 )
671 )
672 self.unknowncmd = True
672 self.unknowncmd = True
673 except error.AmbiguousCommand:
673 except error.AmbiguousCommand:
674 self.badalias = _(
674 self.badalias = _(
675 b"alias '%s' resolves to ambiguous command '%s'"
675 b"alias '%s' resolves to ambiguous command '%s'"
676 ) % (
676 ) % (
677 self.name,
677 self.name,
678 cmd,
678 cmd,
679 )
679 )
680
680
681 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
681 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
682 # confine strings to be passed to i18n.gettext()
682 # confine strings to be passed to i18n.gettext()
683 cfg = {}
683 cfg = {}
684 for k in (b'doc', b'help', b'category'):
684 for k in (b'doc', b'help', b'category'):
685 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
685 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
686 if v is None:
686 if v is None:
687 continue
687 continue
688 if not encoding.isasciistr(v):
688 if not encoding.isasciistr(v):
689 self.badalias = _(
689 self.badalias = _(
690 b"non-ASCII character in alias definition '%s:%s'"
690 b"non-ASCII character in alias definition '%s:%s'"
691 ) % (name, k)
691 ) % (name, k)
692 return
692 return
693 cfg[k] = v
693 cfg[k] = v
694
694
695 self.help = cfg.get(b'help', defaulthelp or b'')
695 self.help = cfg.get(b'help', defaulthelp or b'')
696 if self.help and self.help.startswith(b"hg " + cmd):
696 if self.help and self.help.startswith(b"hg " + cmd):
697 # drop prefix in old-style help lines so hg shows the alias
697 # drop prefix in old-style help lines so hg shows the alias
698 self.help = self.help[4 + len(cmd) :]
698 self.help = self.help[4 + len(cmd) :]
699
699
700 self.owndoc = b'doc' in cfg
700 self.owndoc = b'doc' in cfg
701 doc = cfg.get(b'doc', pycompat.getdoc(fn))
701 doc = cfg.get(b'doc', pycompat.getdoc(fn))
702 if doc is not None:
702 if doc is not None:
703 doc = pycompat.sysstr(doc)
703 doc = pycompat.sysstr(doc)
704 self.__doc__ = doc
704 self.__doc__ = doc
705
705
706 self.helpcategory = cfg.get(
706 self.helpcategory = cfg.get(
707 b'category', registrar.command.CATEGORY_NONE
707 b'category', registrar.command.CATEGORY_NONE
708 )
708 )
709
709
710 @property
710 @property
711 def args(self):
711 def args(self):
712 args = pycompat.maplist(util.expandpath, self.givenargs)
712 args = pycompat.maplist(util.expandpath, self.givenargs)
713 return aliasargs(self.fn, args)
713 return aliasargs(self.fn, args)
714
714
715 def __getattr__(self, name):
715 def __getattr__(self, name):
716 adefaults = {
716 adefaults = {
717 'norepo': True,
717 'norepo': True,
718 'intents': set(),
718 'intents': set(),
719 'optionalrepo': False,
719 'optionalrepo': False,
720 'inferrepo': False,
720 'inferrepo': False,
721 }
721 }
722 if name not in adefaults:
722 if name not in adefaults:
723 raise AttributeError(name)
723 raise AttributeError(name)
724 if self.badalias or util.safehasattr(self, b'shell'):
724 if self.badalias or util.safehasattr(self, b'shell'):
725 return adefaults[name]
725 return adefaults[name]
726 return getattr(self.fn, name)
726 return getattr(self.fn, name)
727
727
728 def __call__(self, ui, *args, **opts):
728 def __call__(self, ui, *args, **opts):
729 if self.badalias:
729 if self.badalias:
730 hint = None
730 hint = None
731 if self.unknowncmd:
731 if self.unknowncmd:
732 try:
732 try:
733 # check if the command is in a disabled extension
733 # check if the command is in a disabled extension
734 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
734 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
735 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
735 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
736 except error.UnknownCommand:
736 except error.UnknownCommand:
737 pass
737 pass
738 raise error.ConfigError(self.badalias, hint=hint)
738 raise error.ConfigError(self.badalias, hint=hint)
739 if self.shadows:
739 if self.shadows:
740 ui.debug(
740 ui.debug(
741 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
741 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
742 )
742 )
743
743
744 ui.log(
744 ui.log(
745 b'commandalias',
745 b'commandalias',
746 b"alias '%s' expands to '%s'\n",
746 b"alias '%s' expands to '%s'\n",
747 self.name,
747 self.name,
748 self.definition,
748 self.definition,
749 )
749 )
750 if util.safehasattr(self, b'shell'):
750 if util.safehasattr(self, b'shell'):
751 return self.fn(ui, *args, **opts)
751 return self.fn(ui, *args, **opts)
752 else:
752 else:
753 try:
753 try:
754 return util.checksignature(self.fn)(ui, *args, **opts)
754 return util.checksignature(self.fn)(ui, *args, **opts)
755 except error.SignatureError:
755 except error.SignatureError:
756 args = b' '.join([self.cmdname] + self.args)
756 args = b' '.join([self.cmdname] + self.args)
757 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
757 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
758 raise
758 raise
759
759
760
760
761 class lazyaliasentry(object):
761 class lazyaliasentry(object):
762 """like a typical command entry (func, opts, help), but is lazy"""
762 """like a typical command entry (func, opts, help), but is lazy"""
763
763
764 def __init__(self, ui, name, definition, cmdtable, source):
764 def __init__(self, ui, name, definition, cmdtable, source):
765 self.ui = ui
765 self.ui = ui
766 self.name = name
766 self.name = name
767 self.definition = definition
767 self.definition = definition
768 self.cmdtable = cmdtable.copy()
768 self.cmdtable = cmdtable.copy()
769 self.source = source
769 self.source = source
770 self.alias = True
770 self.alias = True
771
771
772 @util.propertycache
772 @util.propertycache
773 def _aliasdef(self):
773 def _aliasdef(self):
774 return cmdalias(
774 return cmdalias(
775 self.ui, self.name, self.definition, self.cmdtable, self.source
775 self.ui, self.name, self.definition, self.cmdtable, self.source
776 )
776 )
777
777
778 def __getitem__(self, n):
778 def __getitem__(self, n):
779 aliasdef = self._aliasdef
779 aliasdef = self._aliasdef
780 if n == 0:
780 if n == 0:
781 return aliasdef
781 return aliasdef
782 elif n == 1:
782 elif n == 1:
783 return aliasdef.opts
783 return aliasdef.opts
784 elif n == 2:
784 elif n == 2:
785 return aliasdef.help
785 return aliasdef.help
786 else:
786 else:
787 raise IndexError
787 raise IndexError
788
788
789 def __iter__(self):
789 def __iter__(self):
790 for i in range(3):
790 for i in range(3):
791 yield self[i]
791 yield self[i]
792
792
793 def __len__(self):
793 def __len__(self):
794 return 3
794 return 3
795
795
796
796
797 def addaliases(ui, cmdtable):
797 def addaliases(ui, cmdtable):
798 # aliases are processed after extensions have been loaded, so they
798 # aliases are processed after extensions have been loaded, so they
799 # may use extension commands. Aliases can also use other alias definitions,
799 # may use extension commands. Aliases can also use other alias definitions,
800 # but only if they have been defined prior to the current definition.
800 # but only if they have been defined prior to the current definition.
801 for alias, definition in ui.configitems(b'alias', ignoresub=True):
801 for alias, definition in ui.configitems(b'alias', ignoresub=True):
802 try:
802 try:
803 if cmdtable[alias].definition == definition:
803 if cmdtable[alias].definition == definition:
804 continue
804 continue
805 except (KeyError, AttributeError):
805 except (KeyError, AttributeError):
806 # definition might not exist or it might not be a cmdalias
806 # definition might not exist or it might not be a cmdalias
807 pass
807 pass
808
808
809 source = ui.configsource(b'alias', alias)
809 source = ui.configsource(b'alias', alias)
810 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
810 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
811 cmdtable[alias] = entry
811 cmdtable[alias] = entry
812
812
813
813
814 def _parse(ui, args):
814 def _parse(ui, args):
815 options = {}
815 options = {}
816 cmdoptions = {}
816 cmdoptions = {}
817
817
818 try:
818 try:
819 args = fancyopts.fancyopts(args, commands.globalopts, options)
819 args = fancyopts.fancyopts(args, commands.globalopts, options)
820 except getopt.GetoptError as inst:
820 except getopt.GetoptError as inst:
821 raise error.CommandError(None, stringutil.forcebytestr(inst))
821 raise error.CommandError(None, stringutil.forcebytestr(inst))
822
822
823 if args:
823 if args:
824 cmd, args = args[0], args[1:]
824 cmd, args = args[0], args[1:]
825 aliases, entry = cmdutil.findcmd(
825 aliases, entry = cmdutil.findcmd(
826 cmd, commands.table, ui.configbool(b"ui", b"strict")
826 cmd, commands.table, ui.configbool(b"ui", b"strict")
827 )
827 )
828 cmd = aliases[0]
828 cmd = aliases[0]
829 args = aliasargs(entry[0], args)
829 args = aliasargs(entry[0], args)
830 defaults = ui.config(b"defaults", cmd)
830 defaults = ui.config(b"defaults", cmd)
831 if defaults:
831 if defaults:
832 args = (
832 args = (
833 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
833 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
834 + args
834 + args
835 )
835 )
836 c = list(entry[1])
836 c = list(entry[1])
837 else:
837 else:
838 cmd = None
838 cmd = None
839 c = []
839 c = []
840
840
841 # combine global options into local
841 # combine global options into local
842 for o in commands.globalopts:
842 for o in commands.globalopts:
843 c.append((o[0], o[1], options[o[1]], o[3]))
843 c.append((o[0], o[1], options[o[1]], o[3]))
844
844
845 try:
845 try:
846 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
846 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
847 except getopt.GetoptError as inst:
847 except getopt.GetoptError as inst:
848 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
848 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
849
849
850 # separate global options back out
850 # separate global options back out
851 for o in commands.globalopts:
851 for o in commands.globalopts:
852 n = o[1]
852 n = o[1]
853 options[n] = cmdoptions[n]
853 options[n] = cmdoptions[n]
854 del cmdoptions[n]
854 del cmdoptions[n]
855
855
856 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
856 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
857
857
858
858
859 def _parseconfig(ui, config):
859 def _parseconfig(ui, config):
860 """parse the --config options from the command line"""
860 """parse the --config options from the command line"""
861 configs = []
861 configs = []
862
862
863 for cfg in config:
863 for cfg in config:
864 try:
864 try:
865 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
865 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
866 section, name = name.split(b'.', 1)
866 section, name = name.split(b'.', 1)
867 if not section or not name:
867 if not section or not name:
868 raise IndexError
868 raise IndexError
869 ui.setconfig(section, name, value, b'--config')
869 ui.setconfig(section, name, value, b'--config')
870 configs.append((section, name, value))
870 configs.append((section, name, value))
871 except (IndexError, ValueError):
871 except (IndexError, ValueError):
872 raise error.InputError(
872 raise error.InputError(
873 _(
873 _(
874 b'malformed --config option: %r '
874 b'malformed --config option: %r '
875 b'(use --config section.name=value)'
875 b'(use --config section.name=value)'
876 )
876 )
877 % pycompat.bytestr(cfg)
877 % pycompat.bytestr(cfg)
878 )
878 )
879
879
880 return configs
880 return configs
881
881
882
882
883 def _earlyparseopts(ui, args):
883 def _earlyparseopts(ui, args):
884 options = {}
884 options = {}
885 fancyopts.fancyopts(
885 fancyopts.fancyopts(
886 args,
886 args,
887 commands.globalopts,
887 commands.globalopts,
888 options,
888 options,
889 gnu=not ui.plain(b'strictflags'),
889 gnu=not ui.plain(b'strictflags'),
890 early=True,
890 early=True,
891 optaliases={b'repository': [b'repo']},
891 optaliases={b'repository': [b'repo']},
892 )
892 )
893 return options
893 return options
894
894
895
895
896 def _earlysplitopts(args):
896 def _earlysplitopts(args):
897 """Split args into a list of possible early options and remainder args"""
897 """Split args into a list of possible early options and remainder args"""
898 shortoptions = b'R:'
898 shortoptions = b'R:'
899 # TODO: perhaps 'debugger' should be included
899 # TODO: perhaps 'debugger' should be included
900 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
900 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
901 return fancyopts.earlygetopt(
901 return fancyopts.earlygetopt(
902 args, shortoptions, longoptions, gnu=True, keepsep=True
902 args, shortoptions, longoptions, gnu=True, keepsep=True
903 )
903 )
904
904
905
905
906 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
906 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
907 # run pre-hook, and abort if it fails
907 # run pre-hook, and abort if it fails
908 hook.hook(
908 hook.hook(
909 lui,
909 lui,
910 repo,
910 repo,
911 b"pre-%s" % cmd,
911 b"pre-%s" % cmd,
912 True,
912 True,
913 args=b" ".join(fullargs),
913 args=b" ".join(fullargs),
914 pats=cmdpats,
914 pats=cmdpats,
915 opts=cmdoptions,
915 opts=cmdoptions,
916 )
916 )
917 try:
917 try:
918 ret = _runcommand(ui, options, cmd, d)
918 ret = _runcommand(ui, options, cmd, d)
919 # run post-hook, passing command result
919 # run post-hook, passing command result
920 hook.hook(
920 hook.hook(
921 lui,
921 lui,
922 repo,
922 repo,
923 b"post-%s" % cmd,
923 b"post-%s" % cmd,
924 False,
924 False,
925 args=b" ".join(fullargs),
925 args=b" ".join(fullargs),
926 result=ret,
926 result=ret,
927 pats=cmdpats,
927 pats=cmdpats,
928 opts=cmdoptions,
928 opts=cmdoptions,
929 )
929 )
930 except Exception:
930 except Exception:
931 # run failure hook and re-raise
931 # run failure hook and re-raise
932 hook.hook(
932 hook.hook(
933 lui,
933 lui,
934 repo,
934 repo,
935 b"fail-%s" % cmd,
935 b"fail-%s" % cmd,
936 False,
936 False,
937 args=b" ".join(fullargs),
937 args=b" ".join(fullargs),
938 pats=cmdpats,
938 pats=cmdpats,
939 opts=cmdoptions,
939 opts=cmdoptions,
940 )
940 )
941 raise
941 raise
942 return ret
942 return ret
943
943
944
944
945 def _readsharedsourceconfig(ui, path):
945 def _readsharedsourceconfig(ui, path):
946 """if the current repository is shared one, this tries to read
946 """if the current repository is shared one, this tries to read
947 .hg/hgrc of shared source if we are in share-safe mode
947 .hg/hgrc of shared source if we are in share-safe mode
948
948
949 Config read is loaded into the ui object passed
949 Config read is loaded into the ui object passed
950
950
951 This should be called before reading .hg/hgrc or the main repo
951 This should be called before reading .hg/hgrc or the main repo
952 as that overrides config set in shared source"""
952 as that overrides config set in shared source"""
953 try:
953 try:
954 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
954 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
955 requirements = set(fp.read().splitlines())
955 requirements = set(fp.read().splitlines())
956 if not (
956 if not (
957 requirementsmod.SHARESAFE_REQUIREMENT in requirements
957 requirementsmod.SHARESAFE_REQUIREMENT in requirements
958 and requirementsmod.SHARED_REQUIREMENT in requirements
958 and requirementsmod.SHARED_REQUIREMENT in requirements
959 ):
959 ):
960 return
960 return
961 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
961 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
962 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
962 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
963 root = sharedvfs.base
963 root = sharedvfs.base
964 ui.readconfig(sharedvfs.join(b"hgrc"), root)
964 ui.readconfig(sharedvfs.join(b"hgrc"), root)
965 except IOError:
965 except IOError:
966 pass
966 pass
967
967
968
968
969 def _getlocal(ui, rpath, wd=None):
969 def _getlocal(ui, rpath, wd=None):
970 """Return (path, local ui object) for the given target path.
970 """Return (path, local ui object) for the given target path.
971
971
972 Takes paths in [cwd]/.hg/hgrc into account."
972 Takes paths in [cwd]/.hg/hgrc into account."
973 """
973 """
974 if wd is None:
974 if wd is None:
975 try:
975 try:
976 wd = encoding.getcwd()
976 wd = encoding.getcwd()
977 except OSError as e:
977 except OSError as e:
978 raise error.Abort(
978 raise error.Abort(
979 _(b"error getting current working directory: %s")
979 _(b"error getting current working directory: %s")
980 % encoding.strtolocal(e.strerror)
980 % encoding.strtolocal(e.strerror)
981 )
981 )
982
982
983 path = cmdutil.findrepo(wd) or b""
983 path = cmdutil.findrepo(wd) or b""
984 if not path:
984 if not path:
985 lui = ui
985 lui = ui
986 else:
986 else:
987 lui = ui.copy()
987 lui = ui.copy()
988 if rcutil.use_repo_hgrc():
988 if rcutil.use_repo_hgrc():
989 _readsharedsourceconfig(lui, path)
989 _readsharedsourceconfig(lui, path)
990 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
990 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
991 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
991 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
992
992
993 if rpath:
993 if rpath:
994 path = urlutil.get_clone_path(lui, rpath)[0]
994 path = urlutil.get_clone_path(lui, rpath)[0]
995 lui = ui.copy()
995 lui = ui.copy()
996 if rcutil.use_repo_hgrc():
996 if rcutil.use_repo_hgrc():
997 _readsharedsourceconfig(lui, path)
997 _readsharedsourceconfig(lui, path)
998 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
998 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
999 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
999 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1000
1000
1001 return path, lui
1001 return path, lui
1002
1002
1003
1003
1004 def _checkshellalias(lui, ui, args):
1004 def _checkshellalias(lui, ui, args):
1005 """Return the function to run the shell alias, if it is required"""
1005 """Return the function to run the shell alias, if it is required"""
1006 options = {}
1006 options = {}
1007
1007
1008 try:
1008 try:
1009 args = fancyopts.fancyopts(args, commands.globalopts, options)
1009 args = fancyopts.fancyopts(args, commands.globalopts, options)
1010 except getopt.GetoptError:
1010 except getopt.GetoptError:
1011 return
1011 return
1012
1012
1013 if not args:
1013 if not args:
1014 return
1014 return
1015
1015
1016 cmdtable = commands.table
1016 cmdtable = commands.table
1017
1017
1018 cmd = args[0]
1018 cmd = args[0]
1019 try:
1019 try:
1020 strict = ui.configbool(b"ui", b"strict")
1020 strict = ui.configbool(b"ui", b"strict")
1021 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1021 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1022 except (error.AmbiguousCommand, error.UnknownCommand):
1022 except (error.AmbiguousCommand, error.UnknownCommand):
1023 return
1023 return
1024
1024
1025 cmd = aliases[0]
1025 cmd = aliases[0]
1026 fn = entry[0]
1026 fn = entry[0]
1027
1027
1028 if cmd and util.safehasattr(fn, b'shell'):
1028 if cmd and util.safehasattr(fn, b'shell'):
1029 # shell alias shouldn't receive early options which are consumed by hg
1029 # shell alias shouldn't receive early options which are consumed by hg
1030 _earlyopts, args = _earlysplitopts(args)
1030 _earlyopts, args = _earlysplitopts(args)
1031 d = lambda: fn(ui, *args[1:])
1031 d = lambda: fn(ui, *args[1:])
1032 return lambda: runcommand(
1032 return lambda: runcommand(
1033 lui, None, cmd, args[:1], ui, options, d, [], {}
1033 lui, None, cmd, args[:1], ui, options, d, [], {}
1034 )
1034 )
1035
1035
1036
1036
1037 def _dispatch(req):
1037 def _dispatch(req):
1038 args = req.args
1038 args = req.args
1039 ui = req.ui
1039 ui = req.ui
1040
1040
1041 # check for cwd
1041 # check for cwd
1042 cwd = req.earlyoptions[b'cwd']
1042 cwd = req.earlyoptions[b'cwd']
1043 if cwd:
1043 if cwd:
1044 os.chdir(cwd)
1044 os.chdir(cwd)
1045
1045
1046 rpath = req.earlyoptions[b'repository']
1046 rpath = req.earlyoptions[b'repository']
1047 path, lui = _getlocal(ui, rpath)
1047 path, lui = _getlocal(ui, rpath)
1048
1048
1049 uis = {ui, lui}
1049 uis = {ui, lui}
1050
1050
1051 if req.repo:
1051 if req.repo:
1052 uis.add(req.repo.ui)
1052 uis.add(req.repo.ui)
1053
1053
1054 if (
1054 if (
1055 req.earlyoptions[b'verbose']
1055 req.earlyoptions[b'verbose']
1056 or req.earlyoptions[b'debug']
1056 or req.earlyoptions[b'debug']
1057 or req.earlyoptions[b'quiet']
1057 or req.earlyoptions[b'quiet']
1058 ):
1058 ):
1059 for opt in (b'verbose', b'debug', b'quiet'):
1059 for opt in (b'verbose', b'debug', b'quiet'):
1060 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1060 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1061 for ui_ in uis:
1061 for ui_ in uis:
1062 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1062 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1063
1063
1064 if req.earlyoptions[b'profile']:
1064 if req.earlyoptions[b'profile']:
1065 for ui_ in uis:
1065 for ui_ in uis:
1066 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1066 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1067 elif req.earlyoptions[b'profile'] is False:
1068 # Check for it being set already, so that we don't pollute the config
1069 # with this when using chg in the very common case that it's not
1070 # enabled.
1071 if lui.configbool(b'profiling', b'enabled'):
1072 # Only do this on lui so that `chg foo` with a user config setting
1073 # profiling.enabled=1 still shows profiling information (chg will
1074 # specify `--no-profile` when `hg serve` is starting up, we don't
1075 # want that to propagate to every later invocation).
1076 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1067
1077
1068 profile = lui.configbool(b'profiling', b'enabled')
1078 profile = lui.configbool(b'profiling', b'enabled')
1069 with profiling.profile(lui, enabled=profile) as profiler:
1079 with profiling.profile(lui, enabled=profile) as profiler:
1070 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1080 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1071 # reposetup
1081 # reposetup
1072 extensions.loadall(lui)
1082 extensions.loadall(lui)
1073 # Propagate any changes to lui.__class__ by extensions
1083 # Propagate any changes to lui.__class__ by extensions
1074 ui.__class__ = lui.__class__
1084 ui.__class__ = lui.__class__
1075
1085
1076 # (uisetup and extsetup are handled in extensions.loadall)
1086 # (uisetup and extsetup are handled in extensions.loadall)
1077
1087
1078 # (reposetup is handled in hg.repository)
1088 # (reposetup is handled in hg.repository)
1079
1089
1080 addaliases(lui, commands.table)
1090 addaliases(lui, commands.table)
1081
1091
1082 # All aliases and commands are completely defined, now.
1092 # All aliases and commands are completely defined, now.
1083 # Check abbreviation/ambiguity of shell alias.
1093 # Check abbreviation/ambiguity of shell alias.
1084 shellaliasfn = _checkshellalias(lui, ui, args)
1094 shellaliasfn = _checkshellalias(lui, ui, args)
1085 if shellaliasfn:
1095 if shellaliasfn:
1086 # no additional configs will be set, set up the ui instances
1096 # no additional configs will be set, set up the ui instances
1087 for ui_ in uis:
1097 for ui_ in uis:
1088 extensions.populateui(ui_)
1098 extensions.populateui(ui_)
1089 return shellaliasfn()
1099 return shellaliasfn()
1090
1100
1091 # check for fallback encoding
1101 # check for fallback encoding
1092 fallback = lui.config(b'ui', b'fallbackencoding')
1102 fallback = lui.config(b'ui', b'fallbackencoding')
1093 if fallback:
1103 if fallback:
1094 encoding.fallbackencoding = fallback
1104 encoding.fallbackencoding = fallback
1095
1105
1096 fullargs = args
1106 fullargs = args
1097 cmd, func, args, options, cmdoptions = _parse(lui, args)
1107 cmd, func, args, options, cmdoptions = _parse(lui, args)
1098
1108
1099 # store the canonical command name in request object for later access
1109 # store the canonical command name in request object for later access
1100 req.canonical_command = cmd
1110 req.canonical_command = cmd
1101
1111
1102 if options[b"config"] != req.earlyoptions[b"config"]:
1112 if options[b"config"] != req.earlyoptions[b"config"]:
1103 raise error.InputError(_(b"option --config may not be abbreviated"))
1113 raise error.InputError(_(b"option --config may not be abbreviated"))
1104 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1114 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1105 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1115 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1106 if options[b"repository"] != req.earlyoptions[b"repository"]:
1116 if options[b"repository"] != req.earlyoptions[b"repository"]:
1107 raise error.InputError(
1117 raise error.InputError(
1108 _(
1118 _(
1109 b"option -R has to be separated from other options (e.g. not "
1119 b"option -R has to be separated from other options (e.g. not "
1110 b"-qR) and --repository may only be abbreviated as --repo"
1120 b"-qR) and --repository may only be abbreviated as --repo"
1111 )
1121 )
1112 )
1122 )
1113 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1123 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1114 raise error.InputError(
1124 raise error.InputError(
1115 _(b"option --debugger may not be abbreviated")
1125 _(b"option --debugger may not be abbreviated")
1116 )
1126 )
1117 # don't validate --profile/--traceback, which can be enabled from now
1127 # don't validate --profile/--traceback, which can be enabled from now
1118
1128
1119 if options[b"encoding"]:
1129 if options[b"encoding"]:
1120 encoding.encoding = options[b"encoding"]
1130 encoding.encoding = options[b"encoding"]
1121 if options[b"encodingmode"]:
1131 if options[b"encodingmode"]:
1122 encoding.encodingmode = options[b"encodingmode"]
1132 encoding.encodingmode = options[b"encodingmode"]
1123 if options[b"time"]:
1133 if options[b"time"]:
1124
1134
1125 def get_times():
1135 def get_times():
1126 t = os.times()
1136 t = os.times()
1127 if t[4] == 0.0:
1137 if t[4] == 0.0:
1128 # Windows leaves this as zero, so use time.perf_counter()
1138 # Windows leaves this as zero, so use time.perf_counter()
1129 t = (t[0], t[1], t[2], t[3], util.timer())
1139 t = (t[0], t[1], t[2], t[3], util.timer())
1130 return t
1140 return t
1131
1141
1132 s = get_times()
1142 s = get_times()
1133
1143
1134 def print_time():
1144 def print_time():
1135 t = get_times()
1145 t = get_times()
1136 ui.warn(
1146 ui.warn(
1137 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1147 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1138 % (
1148 % (
1139 t[4] - s[4],
1149 t[4] - s[4],
1140 t[0] - s[0],
1150 t[0] - s[0],
1141 t[2] - s[2],
1151 t[2] - s[2],
1142 t[1] - s[1],
1152 t[1] - s[1],
1143 t[3] - s[3],
1153 t[3] - s[3],
1144 )
1154 )
1145 )
1155 )
1146
1156
1147 ui.atexit(print_time)
1157 ui.atexit(print_time)
1148 if options[b"profile"]:
1158 if options[b"profile"]:
1149 profiler.start()
1159 profiler.start()
1150
1160
1151 # if abbreviated version of this were used, take them in account, now
1161 # if abbreviated version of this were used, take them in account, now
1152 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1162 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1153 for opt in (b'verbose', b'debug', b'quiet'):
1163 for opt in (b'verbose', b'debug', b'quiet'):
1154 if options[opt] == req.earlyoptions[opt]:
1164 if options[opt] == req.earlyoptions[opt]:
1155 continue
1165 continue
1156 val = pycompat.bytestr(bool(options[opt]))
1166 val = pycompat.bytestr(bool(options[opt]))
1157 for ui_ in uis:
1167 for ui_ in uis:
1158 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1168 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1159
1169
1160 if options[b'traceback']:
1170 if options[b'traceback']:
1161 for ui_ in uis:
1171 for ui_ in uis:
1162 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1172 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1163
1173
1164 if options[b'noninteractive']:
1174 if options[b'noninteractive']:
1165 for ui_ in uis:
1175 for ui_ in uis:
1166 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1176 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1167
1177
1168 if cmdoptions.get(b'insecure', False):
1178 if cmdoptions.get(b'insecure', False):
1169 for ui_ in uis:
1179 for ui_ in uis:
1170 ui_.insecureconnections = True
1180 ui_.insecureconnections = True
1171
1181
1172 # setup color handling before pager, because setting up pager
1182 # setup color handling before pager, because setting up pager
1173 # might cause incorrect console information
1183 # might cause incorrect console information
1174 coloropt = options[b'color']
1184 coloropt = options[b'color']
1175 for ui_ in uis:
1185 for ui_ in uis:
1176 if coloropt:
1186 if coloropt:
1177 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1187 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1178 color.setup(ui_)
1188 color.setup(ui_)
1179
1189
1180 if stringutil.parsebool(options[b'pager']):
1190 if stringutil.parsebool(options[b'pager']):
1181 # ui.pager() expects 'internal-always-' prefix in this case
1191 # ui.pager() expects 'internal-always-' prefix in this case
1182 ui.pager(b'internal-always-' + cmd)
1192 ui.pager(b'internal-always-' + cmd)
1183 elif options[b'pager'] != b'auto':
1193 elif options[b'pager'] != b'auto':
1184 for ui_ in uis:
1194 for ui_ in uis:
1185 ui_.disablepager()
1195 ui_.disablepager()
1186
1196
1187 # configs are fully loaded, set up the ui instances
1197 # configs are fully loaded, set up the ui instances
1188 for ui_ in uis:
1198 for ui_ in uis:
1189 extensions.populateui(ui_)
1199 extensions.populateui(ui_)
1190
1200
1191 if options[b'version']:
1201 if options[b'version']:
1192 return commands.version_(ui)
1202 return commands.version_(ui)
1193 if options[b'help']:
1203 if options[b'help']:
1194 return commands.help_(ui, cmd, command=cmd is not None)
1204 return commands.help_(ui, cmd, command=cmd is not None)
1195 elif not cmd:
1205 elif not cmd:
1196 return commands.help_(ui, b'shortlist')
1206 return commands.help_(ui, b'shortlist')
1197
1207
1198 repo = None
1208 repo = None
1199 cmdpats = args[:]
1209 cmdpats = args[:]
1200 assert func is not None # help out pytype
1210 assert func is not None # help out pytype
1201 if not func.norepo:
1211 if not func.norepo:
1202 # use the repo from the request only if we don't have -R
1212 # use the repo from the request only if we don't have -R
1203 if not rpath and not cwd:
1213 if not rpath and not cwd:
1204 repo = req.repo
1214 repo = req.repo
1205
1215
1206 if repo:
1216 if repo:
1207 # set the descriptors of the repo ui to those of ui
1217 # set the descriptors of the repo ui to those of ui
1208 repo.ui.fin = ui.fin
1218 repo.ui.fin = ui.fin
1209 repo.ui.fout = ui.fout
1219 repo.ui.fout = ui.fout
1210 repo.ui.ferr = ui.ferr
1220 repo.ui.ferr = ui.ferr
1211 repo.ui.fmsg = ui.fmsg
1221 repo.ui.fmsg = ui.fmsg
1212 else:
1222 else:
1213 try:
1223 try:
1214 repo = hg.repository(
1224 repo = hg.repository(
1215 ui,
1225 ui,
1216 path=path,
1226 path=path,
1217 presetupfuncs=req.prereposetups,
1227 presetupfuncs=req.prereposetups,
1218 intents=func.intents,
1228 intents=func.intents,
1219 )
1229 )
1220 if not repo.local():
1230 if not repo.local():
1221 raise error.InputError(
1231 raise error.InputError(
1222 _(b"repository '%s' is not local") % path
1232 _(b"repository '%s' is not local") % path
1223 )
1233 )
1224 repo.ui.setconfig(
1234 repo.ui.setconfig(
1225 b"bundle", b"mainreporoot", repo.root, b'repo'
1235 b"bundle", b"mainreporoot", repo.root, b'repo'
1226 )
1236 )
1227 except error.RequirementError:
1237 except error.RequirementError:
1228 raise
1238 raise
1229 except error.RepoError:
1239 except error.RepoError:
1230 if rpath: # invalid -R path
1240 if rpath: # invalid -R path
1231 raise
1241 raise
1232 if not func.optionalrepo:
1242 if not func.optionalrepo:
1233 if func.inferrepo and args and not path:
1243 if func.inferrepo and args and not path:
1234 # try to infer -R from command args
1244 # try to infer -R from command args
1235 repos = pycompat.maplist(cmdutil.findrepo, args)
1245 repos = pycompat.maplist(cmdutil.findrepo, args)
1236 guess = repos[0]
1246 guess = repos[0]
1237 if guess and repos.count(guess) == len(repos):
1247 if guess and repos.count(guess) == len(repos):
1238 req.args = [b'--repository', guess] + fullargs
1248 req.args = [b'--repository', guess] + fullargs
1239 req.earlyoptions[b'repository'] = guess
1249 req.earlyoptions[b'repository'] = guess
1240 return _dispatch(req)
1250 return _dispatch(req)
1241 if not path:
1251 if not path:
1242 raise error.InputError(
1252 raise error.InputError(
1243 _(
1253 _(
1244 b"no repository found in"
1254 b"no repository found in"
1245 b" '%s' (.hg not found)"
1255 b" '%s' (.hg not found)"
1246 )
1256 )
1247 % encoding.getcwd()
1257 % encoding.getcwd()
1248 )
1258 )
1249 raise
1259 raise
1250 if repo:
1260 if repo:
1251 ui = repo.ui
1261 ui = repo.ui
1252 if options[b'hidden']:
1262 if options[b'hidden']:
1253 repo = repo.unfiltered()
1263 repo = repo.unfiltered()
1254 args.insert(0, repo)
1264 args.insert(0, repo)
1255 elif rpath:
1265 elif rpath:
1256 ui.warn(_(b"warning: --repository ignored\n"))
1266 ui.warn(_(b"warning: --repository ignored\n"))
1257
1267
1258 msg = _formatargs(fullargs)
1268 msg = _formatargs(fullargs)
1259 ui.log(b"command", b'%s\n', msg)
1269 ui.log(b"command", b'%s\n', msg)
1260 strcmdopt = pycompat.strkwargs(cmdoptions)
1270 strcmdopt = pycompat.strkwargs(cmdoptions)
1261 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1271 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1262 try:
1272 try:
1263 return runcommand(
1273 return runcommand(
1264 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1274 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1265 )
1275 )
1266 finally:
1276 finally:
1267 if repo and repo != req.repo:
1277 if repo and repo != req.repo:
1268 repo.close()
1278 repo.close()
1269
1279
1270
1280
1271 def _runcommand(ui, options, cmd, cmdfunc):
1281 def _runcommand(ui, options, cmd, cmdfunc):
1272 """Run a command function, possibly with profiling enabled."""
1282 """Run a command function, possibly with profiling enabled."""
1273 try:
1283 try:
1274 with tracing.log("Running %s command" % cmd):
1284 with tracing.log("Running %s command" % cmd):
1275 return cmdfunc()
1285 return cmdfunc()
1276 except error.SignatureError:
1286 except error.SignatureError:
1277 raise error.CommandError(cmd, _(b'invalid arguments'))
1287 raise error.CommandError(cmd, _(b'invalid arguments'))
1278
1288
1279
1289
1280 def _exceptionwarning(ui):
1290 def _exceptionwarning(ui):
1281 """Produce a warning message for the current active exception"""
1291 """Produce a warning message for the current active exception"""
1282
1292
1283 # For compatibility checking, we discard the portion of the hg
1293 # For compatibility checking, we discard the portion of the hg
1284 # version after the + on the assumption that if a "normal
1294 # version after the + on the assumption that if a "normal
1285 # user" is running a build with a + in it the packager
1295 # user" is running a build with a + in it the packager
1286 # probably built from fairly close to a tag and anyone with a
1296 # probably built from fairly close to a tag and anyone with a
1287 # 'make local' copy of hg (where the version number can be out
1297 # 'make local' copy of hg (where the version number can be out
1288 # of date) will be clueful enough to notice the implausible
1298 # of date) will be clueful enough to notice the implausible
1289 # version number and try updating.
1299 # version number and try updating.
1290 ct = util.versiontuple(n=2)
1300 ct = util.versiontuple(n=2)
1291 worst = None, ct, b'', b''
1301 worst = None, ct, b'', b''
1292 if ui.config(b'ui', b'supportcontact') is None:
1302 if ui.config(b'ui', b'supportcontact') is None:
1293 for name, mod in extensions.extensions():
1303 for name, mod in extensions.extensions():
1294 # 'testedwith' should be bytes, but not all extensions are ported
1304 # 'testedwith' should be bytes, but not all extensions are ported
1295 # to py3 and we don't want UnicodeException because of that.
1305 # to py3 and we don't want UnicodeException because of that.
1296 testedwith = stringutil.forcebytestr(
1306 testedwith = stringutil.forcebytestr(
1297 getattr(mod, 'testedwith', b'')
1307 getattr(mod, 'testedwith', b'')
1298 )
1308 )
1299 version = extensions.moduleversion(mod)
1309 version = extensions.moduleversion(mod)
1300 report = getattr(mod, 'buglink', _(b'the extension author.'))
1310 report = getattr(mod, 'buglink', _(b'the extension author.'))
1301 if not testedwith.strip():
1311 if not testedwith.strip():
1302 # We found an untested extension. It's likely the culprit.
1312 # We found an untested extension. It's likely the culprit.
1303 worst = name, b'unknown', report, version
1313 worst = name, b'unknown', report, version
1304 break
1314 break
1305
1315
1306 # Never blame on extensions bundled with Mercurial.
1316 # Never blame on extensions bundled with Mercurial.
1307 if extensions.ismoduleinternal(mod):
1317 if extensions.ismoduleinternal(mod):
1308 continue
1318 continue
1309
1319
1310 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1320 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1311 if ct in tested:
1321 if ct in tested:
1312 continue
1322 continue
1313
1323
1314 lower = [t for t in tested if t < ct]
1324 lower = [t for t in tested if t < ct]
1315 nearest = max(lower or tested)
1325 nearest = max(lower or tested)
1316 if worst[0] is None or nearest < worst[1]:
1326 if worst[0] is None or nearest < worst[1]:
1317 worst = name, nearest, report, version
1327 worst = name, nearest, report, version
1318 if worst[0] is not None:
1328 if worst[0] is not None:
1319 name, testedwith, report, version = worst
1329 name, testedwith, report, version = worst
1320 if not isinstance(testedwith, (bytes, str)):
1330 if not isinstance(testedwith, (bytes, str)):
1321 testedwith = b'.'.join(
1331 testedwith = b'.'.join(
1322 [stringutil.forcebytestr(c) for c in testedwith]
1332 [stringutil.forcebytestr(c) for c in testedwith]
1323 )
1333 )
1324 extver = version or _(b"(version N/A)")
1334 extver = version or _(b"(version N/A)")
1325 warning = _(
1335 warning = _(
1326 b'** Unknown exception encountered with '
1336 b'** Unknown exception encountered with '
1327 b'possibly-broken third-party extension "%s" %s\n'
1337 b'possibly-broken third-party extension "%s" %s\n'
1328 b'** which supports versions %s of Mercurial.\n'
1338 b'** which supports versions %s of Mercurial.\n'
1329 b'** Please disable "%s" and try your action again.\n'
1339 b'** Please disable "%s" and try your action again.\n'
1330 b'** If that fixes the bug please report it to %s\n'
1340 b'** If that fixes the bug please report it to %s\n'
1331 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1341 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1332 else:
1342 else:
1333 bugtracker = ui.config(b'ui', b'supportcontact')
1343 bugtracker = ui.config(b'ui', b'supportcontact')
1334 if bugtracker is None:
1344 if bugtracker is None:
1335 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1345 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1336 warning = (
1346 warning = (
1337 _(
1347 _(
1338 b"** unknown exception encountered, "
1348 b"** unknown exception encountered, "
1339 b"please report by visiting\n** "
1349 b"please report by visiting\n** "
1340 )
1350 )
1341 + bugtracker
1351 + bugtracker
1342 + b'\n'
1352 + b'\n'
1343 )
1353 )
1344 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1354 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1345
1355
1346 def ext_with_ver(x):
1356 def ext_with_ver(x):
1347 ext = x[0]
1357 ext = x[0]
1348 ver = extensions.moduleversion(x[1])
1358 ver = extensions.moduleversion(x[1])
1349 if ver:
1359 if ver:
1350 ext += b' ' + ver
1360 ext += b' ' + ver
1351 return ext
1361 return ext
1352
1362
1353 warning += (
1363 warning += (
1354 (_(b"** Python %s\n") % sysversion)
1364 (_(b"** Python %s\n") % sysversion)
1355 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1365 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1356 + (
1366 + (
1357 _(b"** Extensions loaded: %s\n")
1367 _(b"** Extensions loaded: %s\n")
1358 % b", ".join(
1368 % b", ".join(
1359 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1369 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1360 )
1370 )
1361 )
1371 )
1362 )
1372 )
1363 return warning
1373 return warning
1364
1374
1365
1375
1366 def handlecommandexception(ui):
1376 def handlecommandexception(ui):
1367 """Produce a warning message for broken commands
1377 """Produce a warning message for broken commands
1368
1378
1369 Called when handling an exception; the exception is reraised if
1379 Called when handling an exception; the exception is reraised if
1370 this function returns False, ignored otherwise.
1380 this function returns False, ignored otherwise.
1371 """
1381 """
1372 warning = _exceptionwarning(ui)
1382 warning = _exceptionwarning(ui)
1373 ui.log(
1383 ui.log(
1374 b"commandexception",
1384 b"commandexception",
1375 b"%s\n%s\n",
1385 b"%s\n%s\n",
1376 warning,
1386 warning,
1377 pycompat.sysbytes(traceback.format_exc()),
1387 pycompat.sysbytes(traceback.format_exc()),
1378 )
1388 )
1379 ui.warn(warning)
1389 ui.warn(warning)
1380 return False # re-raise the exception
1390 return False # re-raise the exception
@@ -1,470 +1,539 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 [30]
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 *cannot change locale* (glob) (?)
461 *cannot change locale* (glob) (?)
462 LC_CTYPE=unsupported_value
462 LC_CTYPE=unsupported_value
463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 LC_CTYPE=
465 LC_CTYPE=
466 $ LANG= LC_ALL= LC_CTYPE= chg \
466 $ LANG= LC_ALL= LC_CTYPE= chg \
467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 LC_ALL=
468 LC_ALL=
469 LC_CTYPE=
469 LC_CTYPE=
470 LANG=
470 LANG=
471
472 Profiling isn't permanently enabled or carried over between chg invocations that
473 share the same server
474 $ cp $HGRCPATH.orig $HGRCPATH
475 $ hg init $TESTTMP/profiling
476 $ cd $TESTTMP/profiling
477 $ filteredchg() {
478 > CHGDEBUG=1 chg "$@" 2>&1 | egrep 'Sample count|start cmdserver' || true
479 > }
480 $ newchg() {
481 > chg --kill-chg-daemon
482 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 > }
484 (--profile isn't permanently on just because it was specified when chg was
485 started)
486 $ newchg log -r . --profile
487 Sample count: * (glob)
488 $ filteredchg log -r .
489 (enabling profiling via config works, even on the first chg command that starts
490 a cmdserver)
491 $ cat >> $HGRCPATH <<EOF
492 > [profiling]
493 > type=stat
494 > enabled=1
495 > EOF
496 $ newchg log -r .
497 Sample count: * (glob)
498 $ filteredchg log -r .
499 Sample count: * (glob)
500 (test that we aren't accumulating more and more samples each run)
501 $ cat > $TESTTMP/debugsleep.py <<EOF
502 > import time
503 > from mercurial import registrar
504 > cmdtable = {}
505 > command = registrar.command(cmdtable)
506 > @command(b'debugsleep', [], b'', norepo=True)
507 > def debugsleep(ui):
508 > start = time.time()
509 > x = 0
510 > while time.time() < start + 0.5:
511 > time.sleep(.1)
512 > x += 1
513 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 > EOF
515 $ cat >> $HGRCPATH <<EOF
516 > [extensions]
517 > debugsleep = $TESTTMP/debugsleep.py
518 > EOF
519 $ newchg debugsleep > run_1
520 $ filteredchg debugsleep > run_2
521 $ filteredchg debugsleep > run_3
522 $ filteredchg debugsleep > run_4
523 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 $ "$PYTHON" <<EOF
525 > r1 = int(open("run_1", "r").read().split()[-1])
526 > r4 = int(open("run_4", "r").read().split()[-1])
527 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 > (r1, r4, r4 > (r1 * 3)))
529 > EOF
530 Run 1: * samples (glob)
531 Run 4: * samples (glob)
532 Run 4 > 3 * Run 1: True
533 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 $ newchg log -r . --no-profile
535 $ filteredchg log -r .
536 Sample count: * (glob)
537 $ filteredchg log -r . --no-profile
538 $ filteredchg log -r .
539 Sample count: * (glob)
General Comments 0
You need to be logged in to leave comments. Login now