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