##// END OF EJS Templates
chg: make `chg --cwd $anything -R $relativepath` behave better...
Valentin Gatien-Baron -
r50036:22fc6945 stable draft
parent child Browse files
Show More
@@ -1,1390 +1,1392 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 and not status:
256 if ret and not status:
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 formattedargs = _formatargs(req.args)
290 formattedargs = _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 formattedargs,
329 formattedargs,
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 if wd is not None:
995 rpath = os.path.join(wd, rpath)
994 path = urlutil.get_clone_path(lui, rpath)[0]
996 path = urlutil.get_clone_path(lui, rpath)[0]
995 lui = ui.copy()
997 lui = ui.copy()
996 if rcutil.use_repo_hgrc():
998 if rcutil.use_repo_hgrc():
997 _readsharedsourceconfig(lui, path)
999 _readsharedsourceconfig(lui, path)
998 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
1000 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)
1001 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
1000
1002
1001 return path, lui
1003 return path, lui
1002
1004
1003
1005
1004 def _checkshellalias(lui, ui, args):
1006 def _checkshellalias(lui, ui, args):
1005 """Return the function to run the shell alias, if it is required"""
1007 """Return the function to run the shell alias, if it is required"""
1006 options = {}
1008 options = {}
1007
1009
1008 try:
1010 try:
1009 args = fancyopts.fancyopts(args, commands.globalopts, options)
1011 args = fancyopts.fancyopts(args, commands.globalopts, options)
1010 except getopt.GetoptError:
1012 except getopt.GetoptError:
1011 return
1013 return
1012
1014
1013 if not args:
1015 if not args:
1014 return
1016 return
1015
1017
1016 cmdtable = commands.table
1018 cmdtable = commands.table
1017
1019
1018 cmd = args[0]
1020 cmd = args[0]
1019 try:
1021 try:
1020 strict = ui.configbool(b"ui", b"strict")
1022 strict = ui.configbool(b"ui", b"strict")
1021 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1023 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1022 except (error.AmbiguousCommand, error.UnknownCommand):
1024 except (error.AmbiguousCommand, error.UnknownCommand):
1023 return
1025 return
1024
1026
1025 cmd = aliases[0]
1027 cmd = aliases[0]
1026 fn = entry[0]
1028 fn = entry[0]
1027
1029
1028 if cmd and util.safehasattr(fn, b'shell'):
1030 if cmd and util.safehasattr(fn, b'shell'):
1029 # shell alias shouldn't receive early options which are consumed by hg
1031 # shell alias shouldn't receive early options which are consumed by hg
1030 _earlyopts, args = _earlysplitopts(args)
1032 _earlyopts, args = _earlysplitopts(args)
1031 d = lambda: fn(ui, *args[1:])
1033 d = lambda: fn(ui, *args[1:])
1032 return lambda: runcommand(
1034 return lambda: runcommand(
1033 lui, None, cmd, args[:1], ui, options, d, [], {}
1035 lui, None, cmd, args[:1], ui, options, d, [], {}
1034 )
1036 )
1035
1037
1036
1038
1037 def _dispatch(req):
1039 def _dispatch(req):
1038 args = req.args
1040 args = req.args
1039 ui = req.ui
1041 ui = req.ui
1040
1042
1041 # check for cwd
1043 # check for cwd
1042 cwd = req.earlyoptions[b'cwd']
1044 cwd = req.earlyoptions[b'cwd']
1043 if cwd:
1045 if cwd:
1044 os.chdir(cwd)
1046 os.chdir(cwd)
1045
1047
1046 rpath = req.earlyoptions[b'repository']
1048 rpath = req.earlyoptions[b'repository']
1047 path, lui = _getlocal(ui, rpath)
1049 path, lui = _getlocal(ui, rpath)
1048
1050
1049 uis = {ui, lui}
1051 uis = {ui, lui}
1050
1052
1051 if req.repo:
1053 if req.repo:
1052 uis.add(req.repo.ui)
1054 uis.add(req.repo.ui)
1053
1055
1054 if (
1056 if (
1055 req.earlyoptions[b'verbose']
1057 req.earlyoptions[b'verbose']
1056 or req.earlyoptions[b'debug']
1058 or req.earlyoptions[b'debug']
1057 or req.earlyoptions[b'quiet']
1059 or req.earlyoptions[b'quiet']
1058 ):
1060 ):
1059 for opt in (b'verbose', b'debug', b'quiet'):
1061 for opt in (b'verbose', b'debug', b'quiet'):
1060 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1062 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1061 for ui_ in uis:
1063 for ui_ in uis:
1062 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1064 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1063
1065
1064 if req.earlyoptions[b'profile']:
1066 if req.earlyoptions[b'profile']:
1065 for ui_ in uis:
1067 for ui_ in uis:
1066 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1068 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1067 elif req.earlyoptions[b'profile'] is False:
1069 elif req.earlyoptions[b'profile'] is False:
1068 # Check for it being set already, so that we don't pollute the config
1070 # 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
1071 # with this when using chg in the very common case that it's not
1070 # enabled.
1072 # enabled.
1071 if lui.configbool(b'profiling', b'enabled'):
1073 if lui.configbool(b'profiling', b'enabled'):
1072 # Only do this on lui so that `chg foo` with a user config setting
1074 # Only do this on lui so that `chg foo` with a user config setting
1073 # profiling.enabled=1 still shows profiling information (chg will
1075 # profiling.enabled=1 still shows profiling information (chg will
1074 # specify `--no-profile` when `hg serve` is starting up, we don't
1076 # specify `--no-profile` when `hg serve` is starting up, we don't
1075 # want that to propagate to every later invocation).
1077 # want that to propagate to every later invocation).
1076 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1078 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1077
1079
1078 profile = lui.configbool(b'profiling', b'enabled')
1080 profile = lui.configbool(b'profiling', b'enabled')
1079 with profiling.profile(lui, enabled=profile) as profiler:
1081 with profiling.profile(lui, enabled=profile) as profiler:
1080 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1082 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1081 # reposetup
1083 # reposetup
1082 extensions.loadall(lui)
1084 extensions.loadall(lui)
1083 # Propagate any changes to lui.__class__ by extensions
1085 # Propagate any changes to lui.__class__ by extensions
1084 ui.__class__ = lui.__class__
1086 ui.__class__ = lui.__class__
1085
1087
1086 # (uisetup and extsetup are handled in extensions.loadall)
1088 # (uisetup and extsetup are handled in extensions.loadall)
1087
1089
1088 # (reposetup is handled in hg.repository)
1090 # (reposetup is handled in hg.repository)
1089
1091
1090 addaliases(lui, commands.table)
1092 addaliases(lui, commands.table)
1091
1093
1092 # All aliases and commands are completely defined, now.
1094 # All aliases and commands are completely defined, now.
1093 # Check abbreviation/ambiguity of shell alias.
1095 # Check abbreviation/ambiguity of shell alias.
1094 shellaliasfn = _checkshellalias(lui, ui, args)
1096 shellaliasfn = _checkshellalias(lui, ui, args)
1095 if shellaliasfn:
1097 if shellaliasfn:
1096 # no additional configs will be set, set up the ui instances
1098 # no additional configs will be set, set up the ui instances
1097 for ui_ in uis:
1099 for ui_ in uis:
1098 extensions.populateui(ui_)
1100 extensions.populateui(ui_)
1099 return shellaliasfn()
1101 return shellaliasfn()
1100
1102
1101 # check for fallback encoding
1103 # check for fallback encoding
1102 fallback = lui.config(b'ui', b'fallbackencoding')
1104 fallback = lui.config(b'ui', b'fallbackencoding')
1103 if fallback:
1105 if fallback:
1104 encoding.fallbackencoding = fallback
1106 encoding.fallbackencoding = fallback
1105
1107
1106 fullargs = args
1108 fullargs = args
1107 cmd, func, args, options, cmdoptions = _parse(lui, args)
1109 cmd, func, args, options, cmdoptions = _parse(lui, args)
1108
1110
1109 # store the canonical command name in request object for later access
1111 # store the canonical command name in request object for later access
1110 req.canonical_command = cmd
1112 req.canonical_command = cmd
1111
1113
1112 if options[b"config"] != req.earlyoptions[b"config"]:
1114 if options[b"config"] != req.earlyoptions[b"config"]:
1113 raise error.InputError(_(b"option --config may not be abbreviated"))
1115 raise error.InputError(_(b"option --config may not be abbreviated"))
1114 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1116 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1115 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1117 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1116 if options[b"repository"] != req.earlyoptions[b"repository"]:
1118 if options[b"repository"] != req.earlyoptions[b"repository"]:
1117 raise error.InputError(
1119 raise error.InputError(
1118 _(
1120 _(
1119 b"option -R has to be separated from other options (e.g. not "
1121 b"option -R has to be separated from other options (e.g. not "
1120 b"-qR) and --repository may only be abbreviated as --repo"
1122 b"-qR) and --repository may only be abbreviated as --repo"
1121 )
1123 )
1122 )
1124 )
1123 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1125 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1124 raise error.InputError(
1126 raise error.InputError(
1125 _(b"option --debugger may not be abbreviated")
1127 _(b"option --debugger may not be abbreviated")
1126 )
1128 )
1127 # don't validate --profile/--traceback, which can be enabled from now
1129 # don't validate --profile/--traceback, which can be enabled from now
1128
1130
1129 if options[b"encoding"]:
1131 if options[b"encoding"]:
1130 encoding.encoding = options[b"encoding"]
1132 encoding.encoding = options[b"encoding"]
1131 if options[b"encodingmode"]:
1133 if options[b"encodingmode"]:
1132 encoding.encodingmode = options[b"encodingmode"]
1134 encoding.encodingmode = options[b"encodingmode"]
1133 if options[b"time"]:
1135 if options[b"time"]:
1134
1136
1135 def get_times():
1137 def get_times():
1136 t = os.times()
1138 t = os.times()
1137 if t[4] == 0.0:
1139 if t[4] == 0.0:
1138 # Windows leaves this as zero, so use time.perf_counter()
1140 # Windows leaves this as zero, so use time.perf_counter()
1139 t = (t[0], t[1], t[2], t[3], util.timer())
1141 t = (t[0], t[1], t[2], t[3], util.timer())
1140 return t
1142 return t
1141
1143
1142 s = get_times()
1144 s = get_times()
1143
1145
1144 def print_time():
1146 def print_time():
1145 t = get_times()
1147 t = get_times()
1146 ui.warn(
1148 ui.warn(
1147 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1149 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1148 % (
1150 % (
1149 t[4] - s[4],
1151 t[4] - s[4],
1150 t[0] - s[0],
1152 t[0] - s[0],
1151 t[2] - s[2],
1153 t[2] - s[2],
1152 t[1] - s[1],
1154 t[1] - s[1],
1153 t[3] - s[3],
1155 t[3] - s[3],
1154 )
1156 )
1155 )
1157 )
1156
1158
1157 ui.atexit(print_time)
1159 ui.atexit(print_time)
1158 if options[b"profile"]:
1160 if options[b"profile"]:
1159 profiler.start()
1161 profiler.start()
1160
1162
1161 # if abbreviated version of this were used, take them in account, now
1163 # if abbreviated version of this were used, take them in account, now
1162 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1164 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1163 for opt in (b'verbose', b'debug', b'quiet'):
1165 for opt in (b'verbose', b'debug', b'quiet'):
1164 if options[opt] == req.earlyoptions[opt]:
1166 if options[opt] == req.earlyoptions[opt]:
1165 continue
1167 continue
1166 val = pycompat.bytestr(bool(options[opt]))
1168 val = pycompat.bytestr(bool(options[opt]))
1167 for ui_ in uis:
1169 for ui_ in uis:
1168 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1170 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1169
1171
1170 if options[b'traceback']:
1172 if options[b'traceback']:
1171 for ui_ in uis:
1173 for ui_ in uis:
1172 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1174 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1173
1175
1174 if options[b'noninteractive']:
1176 if options[b'noninteractive']:
1175 for ui_ in uis:
1177 for ui_ in uis:
1176 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1178 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1177
1179
1178 if cmdoptions.get(b'insecure', False):
1180 if cmdoptions.get(b'insecure', False):
1179 for ui_ in uis:
1181 for ui_ in uis:
1180 ui_.insecureconnections = True
1182 ui_.insecureconnections = True
1181
1183
1182 # setup color handling before pager, because setting up pager
1184 # setup color handling before pager, because setting up pager
1183 # might cause incorrect console information
1185 # might cause incorrect console information
1184 coloropt = options[b'color']
1186 coloropt = options[b'color']
1185 for ui_ in uis:
1187 for ui_ in uis:
1186 if coloropt:
1188 if coloropt:
1187 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1189 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1188 color.setup(ui_)
1190 color.setup(ui_)
1189
1191
1190 if stringutil.parsebool(options[b'pager']):
1192 if stringutil.parsebool(options[b'pager']):
1191 # ui.pager() expects 'internal-always-' prefix in this case
1193 # ui.pager() expects 'internal-always-' prefix in this case
1192 ui.pager(b'internal-always-' + cmd)
1194 ui.pager(b'internal-always-' + cmd)
1193 elif options[b'pager'] != b'auto':
1195 elif options[b'pager'] != b'auto':
1194 for ui_ in uis:
1196 for ui_ in uis:
1195 ui_.disablepager()
1197 ui_.disablepager()
1196
1198
1197 # configs are fully loaded, set up the ui instances
1199 # configs are fully loaded, set up the ui instances
1198 for ui_ in uis:
1200 for ui_ in uis:
1199 extensions.populateui(ui_)
1201 extensions.populateui(ui_)
1200
1202
1201 if options[b'version']:
1203 if options[b'version']:
1202 return commands.version_(ui)
1204 return commands.version_(ui)
1203 if options[b'help']:
1205 if options[b'help']:
1204 return commands.help_(ui, cmd, command=cmd is not None)
1206 return commands.help_(ui, cmd, command=cmd is not None)
1205 elif not cmd:
1207 elif not cmd:
1206 return commands.help_(ui, b'shortlist')
1208 return commands.help_(ui, b'shortlist')
1207
1209
1208 repo = None
1210 repo = None
1209 cmdpats = args[:]
1211 cmdpats = args[:]
1210 assert func is not None # help out pytype
1212 assert func is not None # help out pytype
1211 if not func.norepo:
1213 if not func.norepo:
1212 # use the repo from the request only if we don't have -R
1214 # use the repo from the request only if we don't have -R
1213 if not rpath and not cwd:
1215 if not rpath and not cwd:
1214 repo = req.repo
1216 repo = req.repo
1215
1217
1216 if repo:
1218 if repo:
1217 # set the descriptors of the repo ui to those of ui
1219 # set the descriptors of the repo ui to those of ui
1218 repo.ui.fin = ui.fin
1220 repo.ui.fin = ui.fin
1219 repo.ui.fout = ui.fout
1221 repo.ui.fout = ui.fout
1220 repo.ui.ferr = ui.ferr
1222 repo.ui.ferr = ui.ferr
1221 repo.ui.fmsg = ui.fmsg
1223 repo.ui.fmsg = ui.fmsg
1222 else:
1224 else:
1223 try:
1225 try:
1224 repo = hg.repository(
1226 repo = hg.repository(
1225 ui,
1227 ui,
1226 path=path,
1228 path=path,
1227 presetupfuncs=req.prereposetups,
1229 presetupfuncs=req.prereposetups,
1228 intents=func.intents,
1230 intents=func.intents,
1229 )
1231 )
1230 if not repo.local():
1232 if not repo.local():
1231 raise error.InputError(
1233 raise error.InputError(
1232 _(b"repository '%s' is not local") % path
1234 _(b"repository '%s' is not local") % path
1233 )
1235 )
1234 repo.ui.setconfig(
1236 repo.ui.setconfig(
1235 b"bundle", b"mainreporoot", repo.root, b'repo'
1237 b"bundle", b"mainreporoot", repo.root, b'repo'
1236 )
1238 )
1237 except error.RequirementError:
1239 except error.RequirementError:
1238 raise
1240 raise
1239 except error.RepoError:
1241 except error.RepoError:
1240 if rpath: # invalid -R path
1242 if rpath: # invalid -R path
1241 raise
1243 raise
1242 if not func.optionalrepo:
1244 if not func.optionalrepo:
1243 if func.inferrepo and args and not path:
1245 if func.inferrepo and args and not path:
1244 # try to infer -R from command args
1246 # try to infer -R from command args
1245 repos = pycompat.maplist(cmdutil.findrepo, args)
1247 repos = pycompat.maplist(cmdutil.findrepo, args)
1246 guess = repos[0]
1248 guess = repos[0]
1247 if guess and repos.count(guess) == len(repos):
1249 if guess and repos.count(guess) == len(repos):
1248 req.args = [b'--repository', guess] + fullargs
1250 req.args = [b'--repository', guess] + fullargs
1249 req.earlyoptions[b'repository'] = guess
1251 req.earlyoptions[b'repository'] = guess
1250 return _dispatch(req)
1252 return _dispatch(req)
1251 if not path:
1253 if not path:
1252 raise error.InputError(
1254 raise error.InputError(
1253 _(
1255 _(
1254 b"no repository found in"
1256 b"no repository found in"
1255 b" '%s' (.hg not found)"
1257 b" '%s' (.hg not found)"
1256 )
1258 )
1257 % encoding.getcwd()
1259 % encoding.getcwd()
1258 )
1260 )
1259 raise
1261 raise
1260 if repo:
1262 if repo:
1261 ui = repo.ui
1263 ui = repo.ui
1262 if options[b'hidden']:
1264 if options[b'hidden']:
1263 repo = repo.unfiltered()
1265 repo = repo.unfiltered()
1264 args.insert(0, repo)
1266 args.insert(0, repo)
1265 elif rpath:
1267 elif rpath:
1266 ui.warn(_(b"warning: --repository ignored\n"))
1268 ui.warn(_(b"warning: --repository ignored\n"))
1267
1269
1268 msg = _formatargs(fullargs)
1270 msg = _formatargs(fullargs)
1269 ui.log(b"command", b'%s\n', msg)
1271 ui.log(b"command", b'%s\n', msg)
1270 strcmdopt = pycompat.strkwargs(cmdoptions)
1272 strcmdopt = pycompat.strkwargs(cmdoptions)
1271 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1273 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1272 try:
1274 try:
1273 return runcommand(
1275 return runcommand(
1274 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1276 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1275 )
1277 )
1276 finally:
1278 finally:
1277 if repo and repo != req.repo:
1279 if repo and repo != req.repo:
1278 repo.close()
1280 repo.close()
1279
1281
1280
1282
1281 def _runcommand(ui, options, cmd, cmdfunc):
1283 def _runcommand(ui, options, cmd, cmdfunc):
1282 """Run a command function, possibly with profiling enabled."""
1284 """Run a command function, possibly with profiling enabled."""
1283 try:
1285 try:
1284 with tracing.log("Running %s command" % cmd):
1286 with tracing.log("Running %s command" % cmd):
1285 return cmdfunc()
1287 return cmdfunc()
1286 except error.SignatureError:
1288 except error.SignatureError:
1287 raise error.CommandError(cmd, _(b'invalid arguments'))
1289 raise error.CommandError(cmd, _(b'invalid arguments'))
1288
1290
1289
1291
1290 def _exceptionwarning(ui):
1292 def _exceptionwarning(ui):
1291 """Produce a warning message for the current active exception"""
1293 """Produce a warning message for the current active exception"""
1292
1294
1293 # For compatibility checking, we discard the portion of the hg
1295 # For compatibility checking, we discard the portion of the hg
1294 # version after the + on the assumption that if a "normal
1296 # version after the + on the assumption that if a "normal
1295 # user" is running a build with a + in it the packager
1297 # user" is running a build with a + in it the packager
1296 # probably built from fairly close to a tag and anyone with a
1298 # probably built from fairly close to a tag and anyone with a
1297 # 'make local' copy of hg (where the version number can be out
1299 # 'make local' copy of hg (where the version number can be out
1298 # of date) will be clueful enough to notice the implausible
1300 # of date) will be clueful enough to notice the implausible
1299 # version number and try updating.
1301 # version number and try updating.
1300 ct = util.versiontuple(n=2)
1302 ct = util.versiontuple(n=2)
1301 worst = None, ct, b'', b''
1303 worst = None, ct, b'', b''
1302 if ui.config(b'ui', b'supportcontact') is None:
1304 if ui.config(b'ui', b'supportcontact') is None:
1303 for name, mod in extensions.extensions():
1305 for name, mod in extensions.extensions():
1304 # 'testedwith' should be bytes, but not all extensions are ported
1306 # 'testedwith' should be bytes, but not all extensions are ported
1305 # to py3 and we don't want UnicodeException because of that.
1307 # to py3 and we don't want UnicodeException because of that.
1306 testedwith = stringutil.forcebytestr(
1308 testedwith = stringutil.forcebytestr(
1307 getattr(mod, 'testedwith', b'')
1309 getattr(mod, 'testedwith', b'')
1308 )
1310 )
1309 version = extensions.moduleversion(mod)
1311 version = extensions.moduleversion(mod)
1310 report = getattr(mod, 'buglink', _(b'the extension author.'))
1312 report = getattr(mod, 'buglink', _(b'the extension author.'))
1311 if not testedwith.strip():
1313 if not testedwith.strip():
1312 # We found an untested extension. It's likely the culprit.
1314 # We found an untested extension. It's likely the culprit.
1313 worst = name, b'unknown', report, version
1315 worst = name, b'unknown', report, version
1314 break
1316 break
1315
1317
1316 # Never blame on extensions bundled with Mercurial.
1318 # Never blame on extensions bundled with Mercurial.
1317 if extensions.ismoduleinternal(mod):
1319 if extensions.ismoduleinternal(mod):
1318 continue
1320 continue
1319
1321
1320 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1322 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1321 if ct in tested:
1323 if ct in tested:
1322 continue
1324 continue
1323
1325
1324 lower = [t for t in tested if t < ct]
1326 lower = [t for t in tested if t < ct]
1325 nearest = max(lower or tested)
1327 nearest = max(lower or tested)
1326 if worst[0] is None or nearest < worst[1]:
1328 if worst[0] is None or nearest < worst[1]:
1327 worst = name, nearest, report, version
1329 worst = name, nearest, report, version
1328 if worst[0] is not None:
1330 if worst[0] is not None:
1329 name, testedwith, report, version = worst
1331 name, testedwith, report, version = worst
1330 if not isinstance(testedwith, (bytes, str)):
1332 if not isinstance(testedwith, (bytes, str)):
1331 testedwith = b'.'.join(
1333 testedwith = b'.'.join(
1332 [stringutil.forcebytestr(c) for c in testedwith]
1334 [stringutil.forcebytestr(c) for c in testedwith]
1333 )
1335 )
1334 extver = version or _(b"(version N/A)")
1336 extver = version or _(b"(version N/A)")
1335 warning = _(
1337 warning = _(
1336 b'** Unknown exception encountered with '
1338 b'** Unknown exception encountered with '
1337 b'possibly-broken third-party extension "%s" %s\n'
1339 b'possibly-broken third-party extension "%s" %s\n'
1338 b'** which supports versions %s of Mercurial.\n'
1340 b'** which supports versions %s of Mercurial.\n'
1339 b'** Please disable "%s" and try your action again.\n'
1341 b'** Please disable "%s" and try your action again.\n'
1340 b'** If that fixes the bug please report it to %s\n'
1342 b'** If that fixes the bug please report it to %s\n'
1341 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1343 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1342 else:
1344 else:
1343 bugtracker = ui.config(b'ui', b'supportcontact')
1345 bugtracker = ui.config(b'ui', b'supportcontact')
1344 if bugtracker is None:
1346 if bugtracker is None:
1345 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1347 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1346 warning = (
1348 warning = (
1347 _(
1349 _(
1348 b"** unknown exception encountered, "
1350 b"** unknown exception encountered, "
1349 b"please report by visiting\n** "
1351 b"please report by visiting\n** "
1350 )
1352 )
1351 + bugtracker
1353 + bugtracker
1352 + b'\n'
1354 + b'\n'
1353 )
1355 )
1354 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1356 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1355
1357
1356 def ext_with_ver(x):
1358 def ext_with_ver(x):
1357 ext = x[0]
1359 ext = x[0]
1358 ver = extensions.moduleversion(x[1])
1360 ver = extensions.moduleversion(x[1])
1359 if ver:
1361 if ver:
1360 ext += b' ' + ver
1362 ext += b' ' + ver
1361 return ext
1363 return ext
1362
1364
1363 warning += (
1365 warning += (
1364 (_(b"** Python %s\n") % sysversion)
1366 (_(b"** Python %s\n") % sysversion)
1365 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1367 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1366 + (
1368 + (
1367 _(b"** Extensions loaded: %s\n")
1369 _(b"** Extensions loaded: %s\n")
1368 % b", ".join(
1370 % b", ".join(
1369 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1371 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1370 )
1372 )
1371 )
1373 )
1372 )
1374 )
1373 return warning
1375 return warning
1374
1376
1375
1377
1376 def handlecommandexception(ui):
1378 def handlecommandexception(ui):
1377 """Produce a warning message for broken commands
1379 """Produce a warning message for broken commands
1378
1380
1379 Called when handling an exception; the exception is reraised if
1381 Called when handling an exception; the exception is reraised if
1380 this function returns False, ignored otherwise.
1382 this function returns False, ignored otherwise.
1381 """
1383 """
1382 warning = _exceptionwarning(ui)
1384 warning = _exceptionwarning(ui)
1383 ui.log(
1385 ui.log(
1384 b"commandexception",
1386 b"commandexception",
1385 b"%s\n%s\n",
1387 b"%s\n%s\n",
1386 warning,
1388 warning,
1387 pycompat.sysbytes(traceback.format_exc()),
1389 pycompat.sysbytes(traceback.format_exc()),
1388 )
1390 )
1389 ui.warn(warning)
1391 ui.warn(warning)
1390 return False # re-raise the exception
1392 return False # re-raise the exception
@@ -1,539 +1,549 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 -R is interpreted relative to --cwd
437
438 $ hg init repo1
439 $ hg init repo2
440 $ printf "[alias]\nx=y\n" >> repo1/.hg/hgrc
441 $ printf "[alias]\nx=z\n" >> repo2/.hg/hgrc
442 $ (cd repo1; chg --cwd ../repo2 -R . show alias.x)
443 z
444
436 Test that chg works (sets to the user's actual LC_CTYPE) even when python
445 Test that chg works (sets to the user's actual LC_CTYPE) even when python
437 "coerces" the locale (py3.7+)
446 "coerces" the locale (py3.7+)
438
447
439 $ cat > $TESTTMP/debugenv.py <<EOF
448 $ cat > $TESTTMP/debugenv.py <<EOF
440 > from mercurial import encoding
449 > from mercurial import encoding
441 > from mercurial import registrar
450 > from mercurial import registrar
442 > cmdtable = {}
451 > cmdtable = {}
443 > command = registrar.command(cmdtable)
452 > command = registrar.command(cmdtable)
444 > @command(b'debugenv', [], b'', norepo=True)
453 > @command(b'debugenv', [], b'', norepo=True)
445 > def debugenv(ui):
454 > def debugenv(ui):
446 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
455 > for k in [b'LC_ALL', b'LC_CTYPE', b'LANG']:
447 > v = encoding.environ.get(k)
456 > v = encoding.environ.get(k)
448 > if v is not None:
457 > if v is not None:
449 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
458 > ui.write(b'%s=%s\n' % (k, encoding.environ[k]))
450 > EOF
459 > EOF
451 (hg keeps python's modified LC_CTYPE, chg doesn't)
460 (hg keeps python's modified LC_CTYPE, chg doesn't)
452 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
461 $ (unset LC_ALL; unset LANG; LC_CTYPE= "$CHGHG" \
453 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
462 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
454 LC_CTYPE=C.UTF-8 (py37 !)
463 LC_CTYPE=C.UTF-8 (py37 !)
455 LC_CTYPE= (no-py37 !)
464 LC_CTYPE= (no-py37 !)
456 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
465 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
457 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
466 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
458 LC_CTYPE=
467 LC_CTYPE=
459 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
468 $ (unset LC_ALL; unset LANG; LC_CTYPE=unsupported_value chg \
460 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
469 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
461 *cannot change locale* (glob) (?)
470 *cannot change locale* (glob) (?)
462 LC_CTYPE=unsupported_value
471 LC_CTYPE=unsupported_value
463 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
472 $ (unset LC_ALL; unset LANG; LC_CTYPE= chg \
464 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
473 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv)
465 LC_CTYPE=
474 LC_CTYPE=
466 $ LANG= LC_ALL= LC_CTYPE= chg \
475 $ LANG= LC_ALL= LC_CTYPE= chg \
467 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
476 > --config extensions.debugenv=$TESTTMP/debugenv.py debugenv
468 LC_ALL=
477 LC_ALL=
469 LC_CTYPE=
478 LC_CTYPE=
470 LANG=
479 LANG=
471
480
472 Profiling isn't permanently enabled or carried over between chg invocations that
481 Profiling isn't permanently enabled or carried over between chg invocations that
473 share the same server
482 share the same server
474 $ cp $HGRCPATH.orig $HGRCPATH
483 $ cp $HGRCPATH.orig $HGRCPATH
475 $ hg init $TESTTMP/profiling
484 $ hg init $TESTTMP/profiling
476 $ cd $TESTTMP/profiling
485 $ cd $TESTTMP/profiling
477 $ filteredchg() {
486 $ filteredchg() {
478 > CHGDEBUG=1 chg "$@" 2>&1 | sed -rn 's_^No samples recorded.*$_Sample count: 0_; /Sample count/p; /start cmdserver/p'
487 > CHGDEBUG=1 chg "$@" 2>&1 | sed -rn 's_^No samples recorded.*$_Sample count: 0_; /Sample count/p; /start cmdserver/p'
479 > }
488 > }
480 $ newchg() {
489 $ newchg() {
481 > chg --kill-chg-daemon
490 > chg --kill-chg-daemon
482 > filteredchg "$@" | egrep -v 'start cmdserver' || true
491 > filteredchg "$@" | egrep -v 'start cmdserver' || true
483 > }
492 > }
484 (--profile isn't permanently on just because it was specified when chg was
493 (--profile isn't permanently on just because it was specified when chg was
485 started)
494 started)
486 $ newchg log -r . --profile
495 $ newchg log -r . --profile
487 Sample count: * (glob)
496 Sample count: * (glob)
488 $ filteredchg log -r .
497 $ filteredchg log -r .
489 (enabling profiling via config works, even on the first chg command that starts
498 (enabling profiling via config works, even on the first chg command that starts
490 a cmdserver)
499 a cmdserver)
491 $ cat >> $HGRCPATH <<EOF
500 $ cat >> $HGRCPATH <<EOF
492 > [profiling]
501 > [profiling]
493 > type=stat
502 > type=stat
494 > enabled=1
503 > enabled=1
495 > EOF
504 > EOF
496 $ newchg log -r .
505 $ newchg log -r .
497 Sample count: * (glob)
506 Sample count: * (glob)
498 $ filteredchg log -r .
507 $ filteredchg log -r .
499 Sample count: * (glob)
508 Sample count: * (glob)
500 (test that we aren't accumulating more and more samples each run)
509 (test that we aren't accumulating more and more samples each run)
501 $ cat > $TESTTMP/debugsleep.py <<EOF
510 $ cat > $TESTTMP/debugsleep.py <<EOF
502 > import time
511 > import time
503 > from mercurial import registrar
512 > from mercurial import registrar
504 > cmdtable = {}
513 > cmdtable = {}
505 > command = registrar.command(cmdtable)
514 > command = registrar.command(cmdtable)
506 > @command(b'debugsleep', [], b'', norepo=True)
515 > @command(b'debugsleep', [], b'', norepo=True)
507 > def debugsleep(ui):
516 > def debugsleep(ui):
508 > start = time.time()
517 > start = time.time()
509 > x = 0
518 > x = 0
510 > while time.time() < start + 0.5:
519 > while time.time() < start + 0.5:
511 > time.sleep(.1)
520 > time.sleep(.1)
512 > x += 1
521 > x += 1
513 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
522 > ui.status(b'%d debugsleep iterations in %.03fs\n' % (x, time.time() - start))
514 > EOF
523 > EOF
515 $ cat >> $HGRCPATH <<EOF
524 $ cat >> $HGRCPATH <<EOF
516 > [extensions]
525 > [extensions]
517 > debugsleep = $TESTTMP/debugsleep.py
526 > debugsleep = $TESTTMP/debugsleep.py
518 > EOF
527 > EOF
519 $ newchg debugsleep > run_1
528 $ newchg debugsleep > run_1
520 $ filteredchg debugsleep > run_2
529 $ filteredchg debugsleep > run_2
521 $ filteredchg debugsleep > run_3
530 $ filteredchg debugsleep > run_3
522 $ filteredchg debugsleep > run_4
531 $ filteredchg debugsleep > run_4
523 FIXME: Run 4 should not be >3x Run 1's number of samples.
532 FIXME: Run 4 should not be >3x Run 1's number of samples.
524 $ "$PYTHON" <<EOF
533 $ "$PYTHON" <<EOF
525 > r1 = int(open("run_1", "r").read().split()[-1])
534 > r1 = int(open("run_1", "r").read().split()[-1])
526 > r4 = int(open("run_4", "r").read().split()[-1])
535 > 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" %
536 > print("Run 1: %d samples\nRun 4: %d samples\nRun 4 > 3 * Run 1: %s" %
528 > (r1, r4, r4 > (r1 * 3)))
537 > (r1, r4, r4 > (r1 * 3)))
529 > EOF
538 > EOF
530 Run 1: * samples (glob)
539 Run 1: * samples (glob)
531 Run 4: * samples (glob)
540 Run 4: * samples (glob)
532 Run 4 > 3 * Run 1: False
541 Run 4 > 3 * Run 1: False
533 (Disabling with --no-profile on the commandline still works, but isn't permanent)
542 (Disabling with --no-profile on the commandline still works, but isn't permanent)
534 $ newchg log -r . --no-profile
543 $ newchg log -r . --no-profile
535 $ filteredchg log -r .
544 $ filteredchg log -r .
536 Sample count: * (glob)
545 Sample count: * (glob)
537 $ filteredchg log -r . --no-profile
546 $ filteredchg log -r . --no-profile
538 $ filteredchg log -r .
547 $ filteredchg log -r .
539 Sample count: * (glob)
548 Sample count: * (glob)
549 $ cd ../
General Comments 0
You need to be logged in to leave comments. Login now