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