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