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