##// END OF EJS Templates
dispatch: pass around ui.fmsg channel...
Yuya Nishihara -
r40623:5542bc91 default
parent child Browse files
Show More
@@ -1,1084 +1,1089 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from hgdemandimport import tracing
24 from hgdemandimport import tracing
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
28 color,
28 color,
29 commands,
29 commands,
30 demandimport,
30 demandimport,
31 encoding,
31 encoding,
32 error,
32 error,
33 extensions,
33 extensions,
34 fancyopts,
34 fancyopts,
35 help,
35 help,
36 hg,
36 hg,
37 hook,
37 hook,
38 profiling,
38 profiling,
39 pycompat,
39 pycompat,
40 registrar,
40 registrar,
41 scmutil,
41 scmutil,
42 ui as uimod,
42 ui as uimod,
43 util,
43 util,
44 )
44 )
45
45
46 from .utils import (
46 from .utils import (
47 procutil,
47 procutil,
48 stringutil,
48 stringutil,
49 )
49 )
50
50
51 class request(object):
51 class request(object):
52 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
53 ferr=None, prereposetups=None):
53 ferr=None, fmsg=None, prereposetups=None):
54 self.args = args
54 self.args = args
55 self.ui = ui
55 self.ui = ui
56 self.repo = repo
56 self.repo = repo
57
57
58 # input/output/error streams
58 # input/output/error streams
59 self.fin = fin
59 self.fin = fin
60 self.fout = fout
60 self.fout = fout
61 self.ferr = ferr
61 self.ferr = ferr
62 # separate stream for status/error messages
63 self.fmsg = fmsg
62
64
63 # remember options pre-parsed by _earlyparseopts()
65 # remember options pre-parsed by _earlyparseopts()
64 self.earlyoptions = {}
66 self.earlyoptions = {}
65
67
66 # reposetups which run before extensions, useful for chg to pre-fill
68 # reposetups which run before extensions, useful for chg to pre-fill
67 # low-level repo state (for example, changelog) before extensions.
69 # low-level repo state (for example, changelog) before extensions.
68 self.prereposetups = prereposetups or []
70 self.prereposetups = prereposetups or []
69
71
70 # store the parsed and canonical command
72 # store the parsed and canonical command
71 self.canonical_command = None
73 self.canonical_command = None
72
74
73 def _runexithandlers(self):
75 def _runexithandlers(self):
74 exc = None
76 exc = None
75 handlers = self.ui._exithandlers
77 handlers = self.ui._exithandlers
76 try:
78 try:
77 while handlers:
79 while handlers:
78 func, args, kwargs = handlers.pop()
80 func, args, kwargs = handlers.pop()
79 try:
81 try:
80 func(*args, **kwargs)
82 func(*args, **kwargs)
81 except: # re-raises below
83 except: # re-raises below
82 if exc is None:
84 if exc is None:
83 exc = sys.exc_info()[1]
85 exc = sys.exc_info()[1]
84 self.ui.warn(('error in exit handlers:\n'))
86 self.ui.warn(('error in exit handlers:\n'))
85 self.ui.traceback(force=True)
87 self.ui.traceback(force=True)
86 finally:
88 finally:
87 if exc is not None:
89 if exc is not None:
88 raise exc
90 raise exc
89
91
90 def run():
92 def run():
91 "run the command in sys.argv"
93 "run the command in sys.argv"
92 initstdio()
94 initstdio()
93 with tracing.log('parse args into request'):
95 with tracing.log('parse args into request'):
94 req = request(pycompat.sysargv[1:])
96 req = request(pycompat.sysargv[1:])
95 err = None
97 err = None
96 try:
98 try:
97 status = dispatch(req)
99 status = dispatch(req)
98 except error.StdioError as e:
100 except error.StdioError as e:
99 err = e
101 err = e
100 status = -1
102 status = -1
101
103
102 # In all cases we try to flush stdio streams.
104 # In all cases we try to flush stdio streams.
103 if util.safehasattr(req.ui, 'fout'):
105 if util.safehasattr(req.ui, 'fout'):
104 try:
106 try:
105 req.ui.fout.flush()
107 req.ui.fout.flush()
106 except IOError as e:
108 except IOError as e:
107 err = e
109 err = e
108 status = -1
110 status = -1
109
111
110 if util.safehasattr(req.ui, 'ferr'):
112 if util.safehasattr(req.ui, 'ferr'):
111 try:
113 try:
112 if err is not None and err.errno != errno.EPIPE:
114 if err is not None and err.errno != errno.EPIPE:
113 req.ui.ferr.write('abort: %s\n' %
115 req.ui.ferr.write('abort: %s\n' %
114 encoding.strtolocal(err.strerror))
116 encoding.strtolocal(err.strerror))
115 req.ui.ferr.flush()
117 req.ui.ferr.flush()
116 # There's not much we can do about an I/O error here. So (possibly)
118 # There's not much we can do about an I/O error here. So (possibly)
117 # change the status code and move on.
119 # change the status code and move on.
118 except IOError:
120 except IOError:
119 status = -1
121 status = -1
120
122
121 _silencestdio()
123 _silencestdio()
122 sys.exit(status & 255)
124 sys.exit(status & 255)
123
125
124 if pycompat.ispy3:
126 if pycompat.ispy3:
125 def initstdio():
127 def initstdio():
126 pass
128 pass
127
129
128 def _silencestdio():
130 def _silencestdio():
129 for fp in (sys.stdout, sys.stderr):
131 for fp in (sys.stdout, sys.stderr):
130 # Check if the file is okay
132 # Check if the file is okay
131 try:
133 try:
132 fp.flush()
134 fp.flush()
133 continue
135 continue
134 except IOError:
136 except IOError:
135 pass
137 pass
136 # Otherwise mark it as closed to silence "Exception ignored in"
138 # Otherwise mark it as closed to silence "Exception ignored in"
137 # message emitted by the interpreter finalizer. Be careful to
139 # message emitted by the interpreter finalizer. Be careful to
138 # not close procutil.stdout, which may be a fdopen-ed file object
140 # not close procutil.stdout, which may be a fdopen-ed file object
139 # and its close() actually closes the underlying file descriptor.
141 # and its close() actually closes the underlying file descriptor.
140 try:
142 try:
141 fp.close()
143 fp.close()
142 except IOError:
144 except IOError:
143 pass
145 pass
144 else:
146 else:
145 def initstdio():
147 def initstdio():
146 for fp in (sys.stdin, sys.stdout, sys.stderr):
148 for fp in (sys.stdin, sys.stdout, sys.stderr):
147 procutil.setbinary(fp)
149 procutil.setbinary(fp)
148
150
149 def _silencestdio():
151 def _silencestdio():
150 pass
152 pass
151
153
152 def _getsimilar(symbols, value):
154 def _getsimilar(symbols, value):
153 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
155 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
154 # The cutoff for similarity here is pretty arbitrary. It should
156 # The cutoff for similarity here is pretty arbitrary. It should
155 # probably be investigated and tweaked.
157 # probably be investigated and tweaked.
156 return [s for s in symbols if sim(s) > 0.6]
158 return [s for s in symbols if sim(s) > 0.6]
157
159
158 def _reportsimilar(write, similar):
160 def _reportsimilar(write, similar):
159 if len(similar) == 1:
161 if len(similar) == 1:
160 write(_("(did you mean %s?)\n") % similar[0])
162 write(_("(did you mean %s?)\n") % similar[0])
161 elif similar:
163 elif similar:
162 ss = ", ".join(sorted(similar))
164 ss = ", ".join(sorted(similar))
163 write(_("(did you mean one of %s?)\n") % ss)
165 write(_("(did you mean one of %s?)\n") % ss)
164
166
165 def _formatparse(write, inst):
167 def _formatparse(write, inst):
166 similar = []
168 similar = []
167 if isinstance(inst, error.UnknownIdentifier):
169 if isinstance(inst, error.UnknownIdentifier):
168 # make sure to check fileset first, as revset can invoke fileset
170 # make sure to check fileset first, as revset can invoke fileset
169 similar = _getsimilar(inst.symbols, inst.function)
171 similar = _getsimilar(inst.symbols, inst.function)
170 if len(inst.args) > 1:
172 if len(inst.args) > 1:
171 write(_("hg: parse error at %s: %s\n") %
173 write(_("hg: parse error at %s: %s\n") %
172 (pycompat.bytestr(inst.args[1]), inst.args[0]))
174 (pycompat.bytestr(inst.args[1]), inst.args[0]))
173 if inst.args[0].startswith(' '):
175 if inst.args[0].startswith(' '):
174 write(_("unexpected leading whitespace\n"))
176 write(_("unexpected leading whitespace\n"))
175 else:
177 else:
176 write(_("hg: parse error: %s\n") % inst.args[0])
178 write(_("hg: parse error: %s\n") % inst.args[0])
177 _reportsimilar(write, similar)
179 _reportsimilar(write, similar)
178 if inst.hint:
180 if inst.hint:
179 write(_("(%s)\n") % inst.hint)
181 write(_("(%s)\n") % inst.hint)
180
182
181 def _formatargs(args):
183 def _formatargs(args):
182 return ' '.join(procutil.shellquote(a) for a in args)
184 return ' '.join(procutil.shellquote(a) for a in args)
183
185
184 def dispatch(req):
186 def dispatch(req):
185 """run the command specified in req.args; returns an integer status code"""
187 """run the command specified in req.args; returns an integer status code"""
186 with tracing.log('dispatch.dispatch'):
188 with tracing.log('dispatch.dispatch'):
187 if req.ferr:
189 if req.ferr:
188 ferr = req.ferr
190 ferr = req.ferr
189 elif req.ui:
191 elif req.ui:
190 ferr = req.ui.ferr
192 ferr = req.ui.ferr
191 else:
193 else:
192 ferr = procutil.stderr
194 ferr = procutil.stderr
193
195
194 try:
196 try:
195 if not req.ui:
197 if not req.ui:
196 req.ui = uimod.ui.load()
198 req.ui = uimod.ui.load()
197 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
199 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
198 if req.earlyoptions['traceback']:
200 if req.earlyoptions['traceback']:
199 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
201 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
200
202
201 # set ui streams from the request
203 # set ui streams from the request
202 if req.fin:
204 if req.fin:
203 req.ui.fin = req.fin
205 req.ui.fin = req.fin
204 if req.fout:
206 if req.fout:
205 req.ui.fout = req.fout
207 req.ui.fout = req.fout
206 if req.ferr:
208 if req.ferr:
207 req.ui.ferr = req.ferr
209 req.ui.ferr = req.ferr
210 if req.fmsg:
211 req.ui.fmsg = req.fmsg
208 except error.Abort as inst:
212 except error.Abort as inst:
209 ferr.write(_("abort: %s\n") % inst)
213 ferr.write(_("abort: %s\n") % inst)
210 if inst.hint:
214 if inst.hint:
211 ferr.write(_("(%s)\n") % inst.hint)
215 ferr.write(_("(%s)\n") % inst.hint)
212 return -1
216 return -1
213 except error.ParseError as inst:
217 except error.ParseError as inst:
214 _formatparse(ferr.write, inst)
218 _formatparse(ferr.write, inst)
215 return -1
219 return -1
216
220
217 msg = _formatargs(req.args)
221 msg = _formatargs(req.args)
218 starttime = util.timer()
222 starttime = util.timer()
219 ret = 1 # default of Python exit code on unhandled exception
223 ret = 1 # default of Python exit code on unhandled exception
220 try:
224 try:
221 ret = _runcatch(req) or 0
225 ret = _runcatch(req) or 0
222 except error.ProgrammingError as inst:
226 except error.ProgrammingError as inst:
223 req.ui.error(_('** ProgrammingError: %s\n') % inst)
227 req.ui.error(_('** ProgrammingError: %s\n') % inst)
224 if inst.hint:
228 if inst.hint:
225 req.ui.error(_('** (%s)\n') % inst.hint)
229 req.ui.error(_('** (%s)\n') % inst.hint)
226 raise
230 raise
227 except KeyboardInterrupt as inst:
231 except KeyboardInterrupt as inst:
228 try:
232 try:
229 if isinstance(inst, error.SignalInterrupt):
233 if isinstance(inst, error.SignalInterrupt):
230 msg = _("killed!\n")
234 msg = _("killed!\n")
231 else:
235 else:
232 msg = _("interrupted!\n")
236 msg = _("interrupted!\n")
233 req.ui.error(msg)
237 req.ui.error(msg)
234 except error.SignalInterrupt:
238 except error.SignalInterrupt:
235 # maybe pager would quit without consuming all the output, and
239 # maybe pager would quit without consuming all the output, and
236 # SIGPIPE was raised. we cannot print anything in this case.
240 # SIGPIPE was raised. we cannot print anything in this case.
237 pass
241 pass
238 except IOError as inst:
242 except IOError as inst:
239 if inst.errno != errno.EPIPE:
243 if inst.errno != errno.EPIPE:
240 raise
244 raise
241 ret = -1
245 ret = -1
242 finally:
246 finally:
243 duration = util.timer() - starttime
247 duration = util.timer() - starttime
244 req.ui.flush()
248 req.ui.flush()
245 if req.ui.logblockedtimes:
249 if req.ui.logblockedtimes:
246 req.ui._blockedtimes['command_duration'] = duration * 1000
250 req.ui._blockedtimes['command_duration'] = duration * 1000
247 req.ui.log('uiblocked', 'ui blocked ms',
251 req.ui.log('uiblocked', 'ui blocked ms',
248 **pycompat.strkwargs(req.ui._blockedtimes))
252 **pycompat.strkwargs(req.ui._blockedtimes))
249 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
253 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
250 msg, ret & 255, duration,
254 msg, ret & 255, duration,
251 canonical_command=req.canonical_command)
255 canonical_command=req.canonical_command)
252 try:
256 try:
253 req._runexithandlers()
257 req._runexithandlers()
254 except: # exiting, so no re-raises
258 except: # exiting, so no re-raises
255 ret = ret or -1
259 ret = ret or -1
256 return ret
260 return ret
257
261
258 def _runcatch(req):
262 def _runcatch(req):
259 with tracing.log('dispatch._runcatch'):
263 with tracing.log('dispatch._runcatch'):
260 def catchterm(*args):
264 def catchterm(*args):
261 raise error.SignalInterrupt
265 raise error.SignalInterrupt
262
266
263 ui = req.ui
267 ui = req.ui
264 try:
268 try:
265 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
269 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
266 num = getattr(signal, name, None)
270 num = getattr(signal, name, None)
267 if num:
271 if num:
268 signal.signal(num, catchterm)
272 signal.signal(num, catchterm)
269 except ValueError:
273 except ValueError:
270 pass # happens if called in a thread
274 pass # happens if called in a thread
271
275
272 def _runcatchfunc():
276 def _runcatchfunc():
273 realcmd = None
277 realcmd = None
274 try:
278 try:
275 cmdargs = fancyopts.fancyopts(
279 cmdargs = fancyopts.fancyopts(
276 req.args[:], commands.globalopts, {})
280 req.args[:], commands.globalopts, {})
277 cmd = cmdargs[0]
281 cmd = cmdargs[0]
278 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
282 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
279 realcmd = aliases[0]
283 realcmd = aliases[0]
280 except (error.UnknownCommand, error.AmbiguousCommand,
284 except (error.UnknownCommand, error.AmbiguousCommand,
281 IndexError, getopt.GetoptError):
285 IndexError, getopt.GetoptError):
282 # Don't handle this here. We know the command is
286 # Don't handle this here. We know the command is
283 # invalid, but all we're worried about for now is that
287 # invalid, but all we're worried about for now is that
284 # it's not a command that server operators expect to
288 # it's not a command that server operators expect to
285 # be safe to offer to users in a sandbox.
289 # be safe to offer to users in a sandbox.
286 pass
290 pass
287 if realcmd == 'serve' and '--stdio' in cmdargs:
291 if realcmd == 'serve' and '--stdio' in cmdargs:
288 # We want to constrain 'hg serve --stdio' instances pretty
292 # We want to constrain 'hg serve --stdio' instances pretty
289 # closely, as many shared-ssh access tools want to grant
293 # closely, as many shared-ssh access tools want to grant
290 # access to run *only* 'hg -R $repo serve --stdio'. We
294 # access to run *only* 'hg -R $repo serve --stdio'. We
291 # restrict to exactly that set of arguments, and prohibit
295 # restrict to exactly that set of arguments, and prohibit
292 # any repo name that starts with '--' to prevent
296 # any repo name that starts with '--' to prevent
293 # shenanigans wherein a user does something like pass
297 # shenanigans wherein a user does something like pass
294 # --debugger or --config=ui.debugger=1 as a repo
298 # --debugger or --config=ui.debugger=1 as a repo
295 # name. This used to actually run the debugger.
299 # name. This used to actually run the debugger.
296 if (len(req.args) != 4 or
300 if (len(req.args) != 4 or
297 req.args[0] != '-R' or
301 req.args[0] != '-R' or
298 req.args[1].startswith('--') or
302 req.args[1].startswith('--') or
299 req.args[2] != 'serve' or
303 req.args[2] != 'serve' or
300 req.args[3] != '--stdio'):
304 req.args[3] != '--stdio'):
301 raise error.Abort(
305 raise error.Abort(
302 _('potentially unsafe serve --stdio invocation: %s') %
306 _('potentially unsafe serve --stdio invocation: %s') %
303 (stringutil.pprint(req.args),))
307 (stringutil.pprint(req.args),))
304
308
305 try:
309 try:
306 debugger = 'pdb'
310 debugger = 'pdb'
307 debugtrace = {
311 debugtrace = {
308 'pdb': pdb.set_trace
312 'pdb': pdb.set_trace
309 }
313 }
310 debugmortem = {
314 debugmortem = {
311 'pdb': pdb.post_mortem
315 'pdb': pdb.post_mortem
312 }
316 }
313
317
314 # read --config before doing anything else
318 # read --config before doing anything else
315 # (e.g. to change trust settings for reading .hg/hgrc)
319 # (e.g. to change trust settings for reading .hg/hgrc)
316 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
320 cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
317
321
318 if req.repo:
322 if req.repo:
319 # copy configs that were passed on the cmdline (--config) to
323 # copy configs that were passed on the cmdline (--config) to
320 # the repo ui
324 # the repo ui
321 for sec, name, val in cfgs:
325 for sec, name, val in cfgs:
322 req.repo.ui.setconfig(sec, name, val, source='--config')
326 req.repo.ui.setconfig(sec, name, val, source='--config')
323
327
324 # developer config: ui.debugger
328 # developer config: ui.debugger
325 debugger = ui.config("ui", "debugger")
329 debugger = ui.config("ui", "debugger")
326 debugmod = pdb
330 debugmod = pdb
327 if not debugger or ui.plain():
331 if not debugger or ui.plain():
328 # if we are in HGPLAIN mode, then disable custom debugging
332 # if we are in HGPLAIN mode, then disable custom debugging
329 debugger = 'pdb'
333 debugger = 'pdb'
330 elif req.earlyoptions['debugger']:
334 elif req.earlyoptions['debugger']:
331 # This import can be slow for fancy debuggers, so only
335 # This import can be slow for fancy debuggers, so only
332 # do it when absolutely necessary, i.e. when actual
336 # do it when absolutely necessary, i.e. when actual
333 # debugging has been requested
337 # debugging has been requested
334 with demandimport.deactivated():
338 with demandimport.deactivated():
335 try:
339 try:
336 debugmod = __import__(debugger)
340 debugmod = __import__(debugger)
337 except ImportError:
341 except ImportError:
338 pass # Leave debugmod = pdb
342 pass # Leave debugmod = pdb
339
343
340 debugtrace[debugger] = debugmod.set_trace
344 debugtrace[debugger] = debugmod.set_trace
341 debugmortem[debugger] = debugmod.post_mortem
345 debugmortem[debugger] = debugmod.post_mortem
342
346
343 # enter the debugger before command execution
347 # enter the debugger before command execution
344 if req.earlyoptions['debugger']:
348 if req.earlyoptions['debugger']:
345 ui.warn(_("entering debugger - "
349 ui.warn(_("entering debugger - "
346 "type c to continue starting hg or h for help\n"))
350 "type c to continue starting hg or h for help\n"))
347
351
348 if (debugger != 'pdb' and
352 if (debugger != 'pdb' and
349 debugtrace[debugger] == debugtrace['pdb']):
353 debugtrace[debugger] == debugtrace['pdb']):
350 ui.warn(_("%s debugger specified "
354 ui.warn(_("%s debugger specified "
351 "but its module was not found\n") % debugger)
355 "but its module was not found\n") % debugger)
352 with demandimport.deactivated():
356 with demandimport.deactivated():
353 debugtrace[debugger]()
357 debugtrace[debugger]()
354 try:
358 try:
355 return _dispatch(req)
359 return _dispatch(req)
356 finally:
360 finally:
357 ui.flush()
361 ui.flush()
358 except: # re-raises
362 except: # re-raises
359 # enter the debugger when we hit an exception
363 # enter the debugger when we hit an exception
360 if req.earlyoptions['debugger']:
364 if req.earlyoptions['debugger']:
361 traceback.print_exc()
365 traceback.print_exc()
362 debugmortem[debugger](sys.exc_info()[2])
366 debugmortem[debugger](sys.exc_info()[2])
363 raise
367 raise
364 return _callcatch(ui, _runcatchfunc)
368 return _callcatch(ui, _runcatchfunc)
365
369
366 def _callcatch(ui, func):
370 def _callcatch(ui, func):
367 """like scmutil.callcatch but handles more high-level exceptions about
371 """like scmutil.callcatch but handles more high-level exceptions about
368 config parsing and commands. besides, use handlecommandexception to handle
372 config parsing and commands. besides, use handlecommandexception to handle
369 uncaught exceptions.
373 uncaught exceptions.
370 """
374 """
371 try:
375 try:
372 return scmutil.callcatch(ui, func)
376 return scmutil.callcatch(ui, func)
373 except error.AmbiguousCommand as inst:
377 except error.AmbiguousCommand as inst:
374 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
378 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
375 (inst.args[0], " ".join(inst.args[1])))
379 (inst.args[0], " ".join(inst.args[1])))
376 except error.CommandError as inst:
380 except error.CommandError as inst:
377 if inst.args[0]:
381 if inst.args[0]:
378 ui.pager('help')
382 ui.pager('help')
379 msgbytes = pycompat.bytestr(inst.args[1])
383 msgbytes = pycompat.bytestr(inst.args[1])
380 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
384 ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
381 commands.help_(ui, inst.args[0], full=False, command=True)
385 commands.help_(ui, inst.args[0], full=False, command=True)
382 else:
386 else:
383 ui.warn(_("hg: %s\n") % inst.args[1])
387 ui.warn(_("hg: %s\n") % inst.args[1])
384 ui.warn(_("(use 'hg help -v' for a list of global options)\n"))
388 ui.warn(_("(use 'hg help -v' for a list of global options)\n"))
385 except error.ParseError as inst:
389 except error.ParseError as inst:
386 _formatparse(ui.warn, inst)
390 _formatparse(ui.warn, inst)
387 return -1
391 return -1
388 except error.UnknownCommand as inst:
392 except error.UnknownCommand as inst:
389 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
393 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
390 try:
394 try:
391 # check if the command is in a disabled extension
395 # check if the command is in a disabled extension
392 # (but don't check for extensions themselves)
396 # (but don't check for extensions themselves)
393 formatted = help.formattedhelp(ui, commands, inst.args[0],
397 formatted = help.formattedhelp(ui, commands, inst.args[0],
394 unknowncmd=True)
398 unknowncmd=True)
395 ui.warn(nocmdmsg)
399 ui.warn(nocmdmsg)
396 ui.write(formatted)
400 ui.write(formatted)
397 except (error.UnknownCommand, error.Abort):
401 except (error.UnknownCommand, error.Abort):
398 suggested = False
402 suggested = False
399 if len(inst.args) == 2:
403 if len(inst.args) == 2:
400 sim = _getsimilar(inst.args[1], inst.args[0])
404 sim = _getsimilar(inst.args[1], inst.args[0])
401 if sim:
405 if sim:
402 ui.warn(nocmdmsg)
406 ui.warn(nocmdmsg)
403 _reportsimilar(ui.warn, sim)
407 _reportsimilar(ui.warn, sim)
404 suggested = True
408 suggested = True
405 if not suggested:
409 if not suggested:
406 ui.warn(nocmdmsg)
410 ui.warn(nocmdmsg)
407 ui.warn(_("(use 'hg help' for a list of commands)\n"))
411 ui.warn(_("(use 'hg help' for a list of commands)\n"))
408 except IOError:
412 except IOError:
409 raise
413 raise
410 except KeyboardInterrupt:
414 except KeyboardInterrupt:
411 raise
415 raise
412 except: # probably re-raises
416 except: # probably re-raises
413 if not handlecommandexception(ui):
417 if not handlecommandexception(ui):
414 raise
418 raise
415
419
416 return -1
420 return -1
417
421
418 def aliasargs(fn, givenargs):
422 def aliasargs(fn, givenargs):
419 args = []
423 args = []
420 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
424 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
421 if not util.safehasattr(fn, '_origfunc'):
425 if not util.safehasattr(fn, '_origfunc'):
422 args = getattr(fn, 'args', args)
426 args = getattr(fn, 'args', args)
423 if args:
427 if args:
424 cmd = ' '.join(map(procutil.shellquote, args))
428 cmd = ' '.join(map(procutil.shellquote, args))
425
429
426 nums = []
430 nums = []
427 def replacer(m):
431 def replacer(m):
428 num = int(m.group(1)) - 1
432 num = int(m.group(1)) - 1
429 nums.append(num)
433 nums.append(num)
430 if num < len(givenargs):
434 if num < len(givenargs):
431 return givenargs[num]
435 return givenargs[num]
432 raise error.Abort(_('too few arguments for command alias'))
436 raise error.Abort(_('too few arguments for command alias'))
433 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
437 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
434 givenargs = [x for i, x in enumerate(givenargs)
438 givenargs = [x for i, x in enumerate(givenargs)
435 if i not in nums]
439 if i not in nums]
436 args = pycompat.shlexsplit(cmd)
440 args = pycompat.shlexsplit(cmd)
437 return args + givenargs
441 return args + givenargs
438
442
439 def aliasinterpolate(name, args, cmd):
443 def aliasinterpolate(name, args, cmd):
440 '''interpolate args into cmd for shell aliases
444 '''interpolate args into cmd for shell aliases
441
445
442 This also handles $0, $@ and "$@".
446 This also handles $0, $@ and "$@".
443 '''
447 '''
444 # util.interpolate can't deal with "$@" (with quotes) because it's only
448 # util.interpolate can't deal with "$@" (with quotes) because it's only
445 # built to match prefix + patterns.
449 # built to match prefix + patterns.
446 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
450 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
447 replacemap['$0'] = name
451 replacemap['$0'] = name
448 replacemap['$$'] = '$'
452 replacemap['$$'] = '$'
449 replacemap['$@'] = ' '.join(args)
453 replacemap['$@'] = ' '.join(args)
450 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
454 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
451 # parameters, separated out into words. Emulate the same behavior here by
455 # parameters, separated out into words. Emulate the same behavior here by
452 # quoting the arguments individually. POSIX shells will then typically
456 # quoting the arguments individually. POSIX shells will then typically
453 # tokenize each argument into exactly one word.
457 # tokenize each argument into exactly one word.
454 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
458 replacemap['"$@"'] = ' '.join(procutil.shellquote(arg) for arg in args)
455 # escape '\$' for regex
459 # escape '\$' for regex
456 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
460 regex = '|'.join(replacemap.keys()).replace('$', br'\$')
457 r = re.compile(regex)
461 r = re.compile(regex)
458 return r.sub(lambda x: replacemap[x.group()], cmd)
462 return r.sub(lambda x: replacemap[x.group()], cmd)
459
463
460 class cmdalias(object):
464 class cmdalias(object):
461 def __init__(self, ui, name, definition, cmdtable, source):
465 def __init__(self, ui, name, definition, cmdtable, source):
462 self.name = self.cmd = name
466 self.name = self.cmd = name
463 self.cmdname = ''
467 self.cmdname = ''
464 self.definition = definition
468 self.definition = definition
465 self.fn = None
469 self.fn = None
466 self.givenargs = []
470 self.givenargs = []
467 self.opts = []
471 self.opts = []
468 self.help = ''
472 self.help = ''
469 self.badalias = None
473 self.badalias = None
470 self.unknowncmd = False
474 self.unknowncmd = False
471 self.source = source
475 self.source = source
472
476
473 try:
477 try:
474 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
478 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
475 for alias, e in cmdtable.iteritems():
479 for alias, e in cmdtable.iteritems():
476 if e is entry:
480 if e is entry:
477 self.cmd = alias
481 self.cmd = alias
478 break
482 break
479 self.shadows = True
483 self.shadows = True
480 except error.UnknownCommand:
484 except error.UnknownCommand:
481 self.shadows = False
485 self.shadows = False
482
486
483 if not self.definition:
487 if not self.definition:
484 self.badalias = _("no definition for alias '%s'") % self.name
488 self.badalias = _("no definition for alias '%s'") % self.name
485 return
489 return
486
490
487 if self.definition.startswith('!'):
491 if self.definition.startswith('!'):
488 shdef = self.definition[1:]
492 shdef = self.definition[1:]
489 self.shell = True
493 self.shell = True
490 def fn(ui, *args):
494 def fn(ui, *args):
491 env = {'HG_ARGS': ' '.join((self.name,) + args)}
495 env = {'HG_ARGS': ' '.join((self.name,) + args)}
492 def _checkvar(m):
496 def _checkvar(m):
493 if m.groups()[0] == '$':
497 if m.groups()[0] == '$':
494 return m.group()
498 return m.group()
495 elif int(m.groups()[0]) <= len(args):
499 elif int(m.groups()[0]) <= len(args):
496 return m.group()
500 return m.group()
497 else:
501 else:
498 ui.debug("No argument found for substitution "
502 ui.debug("No argument found for substitution "
499 "of %i variable in alias '%s' definition.\n"
503 "of %i variable in alias '%s' definition.\n"
500 % (int(m.groups()[0]), self.name))
504 % (int(m.groups()[0]), self.name))
501 return ''
505 return ''
502 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
506 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
503 cmd = aliasinterpolate(self.name, args, cmd)
507 cmd = aliasinterpolate(self.name, args, cmd)
504 return ui.system(cmd, environ=env,
508 return ui.system(cmd, environ=env,
505 blockedtag='alias_%s' % self.name)
509 blockedtag='alias_%s' % self.name)
506 self.fn = fn
510 self.fn = fn
507 self.alias = True
511 self.alias = True
508 self._populatehelp(ui, name, shdef, self.fn)
512 self._populatehelp(ui, name, shdef, self.fn)
509 return
513 return
510
514
511 try:
515 try:
512 args = pycompat.shlexsplit(self.definition)
516 args = pycompat.shlexsplit(self.definition)
513 except ValueError as inst:
517 except ValueError as inst:
514 self.badalias = (_("error in definition for alias '%s': %s")
518 self.badalias = (_("error in definition for alias '%s': %s")
515 % (self.name, stringutil.forcebytestr(inst)))
519 % (self.name, stringutil.forcebytestr(inst)))
516 return
520 return
517 earlyopts, args = _earlysplitopts(args)
521 earlyopts, args = _earlysplitopts(args)
518 if earlyopts:
522 if earlyopts:
519 self.badalias = (_("error in definition for alias '%s': %s may "
523 self.badalias = (_("error in definition for alias '%s': %s may "
520 "only be given on the command line")
524 "only be given on the command line")
521 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
525 % (self.name, '/'.join(pycompat.ziplist(*earlyopts)
522 [0])))
526 [0])))
523 return
527 return
524 self.cmdname = cmd = args.pop(0)
528 self.cmdname = cmd = args.pop(0)
525 self.givenargs = args
529 self.givenargs = args
526
530
527 try:
531 try:
528 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
532 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
529 if len(tableentry) > 2:
533 if len(tableentry) > 2:
530 self.fn, self.opts, cmdhelp = tableentry
534 self.fn, self.opts, cmdhelp = tableentry
531 else:
535 else:
532 self.fn, self.opts = tableentry
536 self.fn, self.opts = tableentry
533 cmdhelp = None
537 cmdhelp = None
534
538
535 self.alias = True
539 self.alias = True
536 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
540 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
537
541
538 except error.UnknownCommand:
542 except error.UnknownCommand:
539 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
543 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
540 % (self.name, cmd))
544 % (self.name, cmd))
541 self.unknowncmd = True
545 self.unknowncmd = True
542 except error.AmbiguousCommand:
546 except error.AmbiguousCommand:
543 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
547 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
544 % (self.name, cmd))
548 % (self.name, cmd))
545
549
546 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
550 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
547 # confine strings to be passed to i18n.gettext()
551 # confine strings to be passed to i18n.gettext()
548 cfg = {}
552 cfg = {}
549 for k in ('doc', 'help', 'category'):
553 for k in ('doc', 'help', 'category'):
550 v = ui.config('alias', '%s:%s' % (name, k), None)
554 v = ui.config('alias', '%s:%s' % (name, k), None)
551 if v is None:
555 if v is None:
552 continue
556 continue
553 if not encoding.isasciistr(v):
557 if not encoding.isasciistr(v):
554 self.badalias = (_("non-ASCII character in alias definition "
558 self.badalias = (_("non-ASCII character in alias definition "
555 "'%s:%s'") % (name, k))
559 "'%s:%s'") % (name, k))
556 return
560 return
557 cfg[k] = v
561 cfg[k] = v
558
562
559 self.help = cfg.get('help', defaulthelp or '')
563 self.help = cfg.get('help', defaulthelp or '')
560 if self.help and self.help.startswith("hg " + cmd):
564 if self.help and self.help.startswith("hg " + cmd):
561 # drop prefix in old-style help lines so hg shows the alias
565 # drop prefix in old-style help lines so hg shows the alias
562 self.help = self.help[4 + len(cmd):]
566 self.help = self.help[4 + len(cmd):]
563
567
564 self.owndoc = 'doc' in cfg
568 self.owndoc = 'doc' in cfg
565 doc = cfg.get('doc', pycompat.getdoc(fn))
569 doc = cfg.get('doc', pycompat.getdoc(fn))
566 if doc is not None:
570 if doc is not None:
567 doc = pycompat.sysstr(doc)
571 doc = pycompat.sysstr(doc)
568 self.__doc__ = doc
572 self.__doc__ = doc
569
573
570 self.helpcategory = cfg.get('category', registrar.command.CATEGORY_NONE)
574 self.helpcategory = cfg.get('category', registrar.command.CATEGORY_NONE)
571
575
572 @property
576 @property
573 def args(self):
577 def args(self):
574 args = pycompat.maplist(util.expandpath, self.givenargs)
578 args = pycompat.maplist(util.expandpath, self.givenargs)
575 return aliasargs(self.fn, args)
579 return aliasargs(self.fn, args)
576
580
577 def __getattr__(self, name):
581 def __getattr__(self, name):
578 adefaults = {r'norepo': True, r'intents': set(),
582 adefaults = {r'norepo': True, r'intents': set(),
579 r'optionalrepo': False, r'inferrepo': False}
583 r'optionalrepo': False, r'inferrepo': False}
580 if name not in adefaults:
584 if name not in adefaults:
581 raise AttributeError(name)
585 raise AttributeError(name)
582 if self.badalias or util.safehasattr(self, 'shell'):
586 if self.badalias or util.safehasattr(self, 'shell'):
583 return adefaults[name]
587 return adefaults[name]
584 return getattr(self.fn, name)
588 return getattr(self.fn, name)
585
589
586 def __call__(self, ui, *args, **opts):
590 def __call__(self, ui, *args, **opts):
587 if self.badalias:
591 if self.badalias:
588 hint = None
592 hint = None
589 if self.unknowncmd:
593 if self.unknowncmd:
590 try:
594 try:
591 # check if the command is in a disabled extension
595 # check if the command is in a disabled extension
592 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
596 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
593 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
597 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
594 except error.UnknownCommand:
598 except error.UnknownCommand:
595 pass
599 pass
596 raise error.Abort(self.badalias, hint=hint)
600 raise error.Abort(self.badalias, hint=hint)
597 if self.shadows:
601 if self.shadows:
598 ui.debug("alias '%s' shadows command '%s'\n" %
602 ui.debug("alias '%s' shadows command '%s'\n" %
599 (self.name, self.cmdname))
603 (self.name, self.cmdname))
600
604
601 ui.log('commandalias', "alias '%s' expands to '%s'\n",
605 ui.log('commandalias', "alias '%s' expands to '%s'\n",
602 self.name, self.definition)
606 self.name, self.definition)
603 if util.safehasattr(self, 'shell'):
607 if util.safehasattr(self, 'shell'):
604 return self.fn(ui, *args, **opts)
608 return self.fn(ui, *args, **opts)
605 else:
609 else:
606 try:
610 try:
607 return util.checksignature(self.fn)(ui, *args, **opts)
611 return util.checksignature(self.fn)(ui, *args, **opts)
608 except error.SignatureError:
612 except error.SignatureError:
609 args = ' '.join([self.cmdname] + self.args)
613 args = ' '.join([self.cmdname] + self.args)
610 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
614 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
611 raise
615 raise
612
616
613 class lazyaliasentry(object):
617 class lazyaliasentry(object):
614 """like a typical command entry (func, opts, help), but is lazy"""
618 """like a typical command entry (func, opts, help), but is lazy"""
615
619
616 def __init__(self, ui, name, definition, cmdtable, source):
620 def __init__(self, ui, name, definition, cmdtable, source):
617 self.ui = ui
621 self.ui = ui
618 self.name = name
622 self.name = name
619 self.definition = definition
623 self.definition = definition
620 self.cmdtable = cmdtable.copy()
624 self.cmdtable = cmdtable.copy()
621 self.source = source
625 self.source = source
622 self.alias = True
626 self.alias = True
623
627
624 @util.propertycache
628 @util.propertycache
625 def _aliasdef(self):
629 def _aliasdef(self):
626 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
630 return cmdalias(self.ui, self.name, self.definition, self.cmdtable,
627 self.source)
631 self.source)
628
632
629 def __getitem__(self, n):
633 def __getitem__(self, n):
630 aliasdef = self._aliasdef
634 aliasdef = self._aliasdef
631 if n == 0:
635 if n == 0:
632 return aliasdef
636 return aliasdef
633 elif n == 1:
637 elif n == 1:
634 return aliasdef.opts
638 return aliasdef.opts
635 elif n == 2:
639 elif n == 2:
636 return aliasdef.help
640 return aliasdef.help
637 else:
641 else:
638 raise IndexError
642 raise IndexError
639
643
640 def __iter__(self):
644 def __iter__(self):
641 for i in range(3):
645 for i in range(3):
642 yield self[i]
646 yield self[i]
643
647
644 def __len__(self):
648 def __len__(self):
645 return 3
649 return 3
646
650
647 def addaliases(ui, cmdtable):
651 def addaliases(ui, cmdtable):
648 # aliases are processed after extensions have been loaded, so they
652 # aliases are processed after extensions have been loaded, so they
649 # may use extension commands. Aliases can also use other alias definitions,
653 # may use extension commands. Aliases can also use other alias definitions,
650 # but only if they have been defined prior to the current definition.
654 # but only if they have been defined prior to the current definition.
651 for alias, definition in ui.configitems('alias', ignoresub=True):
655 for alias, definition in ui.configitems('alias', ignoresub=True):
652 try:
656 try:
653 if cmdtable[alias].definition == definition:
657 if cmdtable[alias].definition == definition:
654 continue
658 continue
655 except (KeyError, AttributeError):
659 except (KeyError, AttributeError):
656 # definition might not exist or it might not be a cmdalias
660 # definition might not exist or it might not be a cmdalias
657 pass
661 pass
658
662
659 source = ui.configsource('alias', alias)
663 source = ui.configsource('alias', alias)
660 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
664 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
661 cmdtable[alias] = entry
665 cmdtable[alias] = entry
662
666
663 def _parse(ui, args):
667 def _parse(ui, args):
664 options = {}
668 options = {}
665 cmdoptions = {}
669 cmdoptions = {}
666
670
667 try:
671 try:
668 args = fancyopts.fancyopts(args, commands.globalopts, options)
672 args = fancyopts.fancyopts(args, commands.globalopts, options)
669 except getopt.GetoptError as inst:
673 except getopt.GetoptError as inst:
670 raise error.CommandError(None, stringutil.forcebytestr(inst))
674 raise error.CommandError(None, stringutil.forcebytestr(inst))
671
675
672 if args:
676 if args:
673 cmd, args = args[0], args[1:]
677 cmd, args = args[0], args[1:]
674 aliases, entry = cmdutil.findcmd(cmd, commands.table,
678 aliases, entry = cmdutil.findcmd(cmd, commands.table,
675 ui.configbool("ui", "strict"))
679 ui.configbool("ui", "strict"))
676 cmd = aliases[0]
680 cmd = aliases[0]
677 args = aliasargs(entry[0], args)
681 args = aliasargs(entry[0], args)
678 defaults = ui.config("defaults", cmd)
682 defaults = ui.config("defaults", cmd)
679 if defaults:
683 if defaults:
680 args = pycompat.maplist(
684 args = pycompat.maplist(
681 util.expandpath, pycompat.shlexsplit(defaults)) + args
685 util.expandpath, pycompat.shlexsplit(defaults)) + args
682 c = list(entry[1])
686 c = list(entry[1])
683 else:
687 else:
684 cmd = None
688 cmd = None
685 c = []
689 c = []
686
690
687 # combine global options into local
691 # combine global options into local
688 for o in commands.globalopts:
692 for o in commands.globalopts:
689 c.append((o[0], o[1], options[o[1]], o[3]))
693 c.append((o[0], o[1], options[o[1]], o[3]))
690
694
691 try:
695 try:
692 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
696 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
693 except getopt.GetoptError as inst:
697 except getopt.GetoptError as inst:
694 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
698 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
695
699
696 # separate global options back out
700 # separate global options back out
697 for o in commands.globalopts:
701 for o in commands.globalopts:
698 n = o[1]
702 n = o[1]
699 options[n] = cmdoptions[n]
703 options[n] = cmdoptions[n]
700 del cmdoptions[n]
704 del cmdoptions[n]
701
705
702 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
706 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
703
707
704 def _parseconfig(ui, config):
708 def _parseconfig(ui, config):
705 """parse the --config options from the command line"""
709 """parse the --config options from the command line"""
706 configs = []
710 configs = []
707
711
708 for cfg in config:
712 for cfg in config:
709 try:
713 try:
710 name, value = [cfgelem.strip()
714 name, value = [cfgelem.strip()
711 for cfgelem in cfg.split('=', 1)]
715 for cfgelem in cfg.split('=', 1)]
712 section, name = name.split('.', 1)
716 section, name = name.split('.', 1)
713 if not section or not name:
717 if not section or not name:
714 raise IndexError
718 raise IndexError
715 ui.setconfig(section, name, value, '--config')
719 ui.setconfig(section, name, value, '--config')
716 configs.append((section, name, value))
720 configs.append((section, name, value))
717 except (IndexError, ValueError):
721 except (IndexError, ValueError):
718 raise error.Abort(_('malformed --config option: %r '
722 raise error.Abort(_('malformed --config option: %r '
719 '(use --config section.name=value)')
723 '(use --config section.name=value)')
720 % pycompat.bytestr(cfg))
724 % pycompat.bytestr(cfg))
721
725
722 return configs
726 return configs
723
727
724 def _earlyparseopts(ui, args):
728 def _earlyparseopts(ui, args):
725 options = {}
729 options = {}
726 fancyopts.fancyopts(args, commands.globalopts, options,
730 fancyopts.fancyopts(args, commands.globalopts, options,
727 gnu=not ui.plain('strictflags'), early=True,
731 gnu=not ui.plain('strictflags'), early=True,
728 optaliases={'repository': ['repo']})
732 optaliases={'repository': ['repo']})
729 return options
733 return options
730
734
731 def _earlysplitopts(args):
735 def _earlysplitopts(args):
732 """Split args into a list of possible early options and remainder args"""
736 """Split args into a list of possible early options and remainder args"""
733 shortoptions = 'R:'
737 shortoptions = 'R:'
734 # TODO: perhaps 'debugger' should be included
738 # TODO: perhaps 'debugger' should be included
735 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
739 longoptions = ['cwd=', 'repository=', 'repo=', 'config=']
736 return fancyopts.earlygetopt(args, shortoptions, longoptions,
740 return fancyopts.earlygetopt(args, shortoptions, longoptions,
737 gnu=True, keepsep=True)
741 gnu=True, keepsep=True)
738
742
739 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
743 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
740 # run pre-hook, and abort if it fails
744 # run pre-hook, and abort if it fails
741 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
745 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
742 pats=cmdpats, opts=cmdoptions)
746 pats=cmdpats, opts=cmdoptions)
743 try:
747 try:
744 ret = _runcommand(ui, options, cmd, d)
748 ret = _runcommand(ui, options, cmd, d)
745 # run post-hook, passing command result
749 # run post-hook, passing command result
746 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
750 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
747 result=ret, pats=cmdpats, opts=cmdoptions)
751 result=ret, pats=cmdpats, opts=cmdoptions)
748 except Exception:
752 except Exception:
749 # run failure hook and re-raise
753 # run failure hook and re-raise
750 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
754 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
751 pats=cmdpats, opts=cmdoptions)
755 pats=cmdpats, opts=cmdoptions)
752 raise
756 raise
753 return ret
757 return ret
754
758
755 def _getlocal(ui, rpath, wd=None):
759 def _getlocal(ui, rpath, wd=None):
756 """Return (path, local ui object) for the given target path.
760 """Return (path, local ui object) for the given target path.
757
761
758 Takes paths in [cwd]/.hg/hgrc into account."
762 Takes paths in [cwd]/.hg/hgrc into account."
759 """
763 """
760 if wd is None:
764 if wd is None:
761 try:
765 try:
762 wd = encoding.getcwd()
766 wd = encoding.getcwd()
763 except OSError as e:
767 except OSError as e:
764 raise error.Abort(_("error getting current working directory: %s") %
768 raise error.Abort(_("error getting current working directory: %s") %
765 encoding.strtolocal(e.strerror))
769 encoding.strtolocal(e.strerror))
766 path = cmdutil.findrepo(wd) or ""
770 path = cmdutil.findrepo(wd) or ""
767 if not path:
771 if not path:
768 lui = ui
772 lui = ui
769 else:
773 else:
770 lui = ui.copy()
774 lui = ui.copy()
771 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
775 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
772
776
773 if rpath:
777 if rpath:
774 path = lui.expandpath(rpath)
778 path = lui.expandpath(rpath)
775 lui = ui.copy()
779 lui = ui.copy()
776 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
780 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
777
781
778 return path, lui
782 return path, lui
779
783
780 def _checkshellalias(lui, ui, args):
784 def _checkshellalias(lui, ui, args):
781 """Return the function to run the shell alias, if it is required"""
785 """Return the function to run the shell alias, if it is required"""
782 options = {}
786 options = {}
783
787
784 try:
788 try:
785 args = fancyopts.fancyopts(args, commands.globalopts, options)
789 args = fancyopts.fancyopts(args, commands.globalopts, options)
786 except getopt.GetoptError:
790 except getopt.GetoptError:
787 return
791 return
788
792
789 if not args:
793 if not args:
790 return
794 return
791
795
792 cmdtable = commands.table
796 cmdtable = commands.table
793
797
794 cmd = args[0]
798 cmd = args[0]
795 try:
799 try:
796 strict = ui.configbool("ui", "strict")
800 strict = ui.configbool("ui", "strict")
797 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
801 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
798 except (error.AmbiguousCommand, error.UnknownCommand):
802 except (error.AmbiguousCommand, error.UnknownCommand):
799 return
803 return
800
804
801 cmd = aliases[0]
805 cmd = aliases[0]
802 fn = entry[0]
806 fn = entry[0]
803
807
804 if cmd and util.safehasattr(fn, 'shell'):
808 if cmd and util.safehasattr(fn, 'shell'):
805 # shell alias shouldn't receive early options which are consumed by hg
809 # shell alias shouldn't receive early options which are consumed by hg
806 _earlyopts, args = _earlysplitopts(args)
810 _earlyopts, args = _earlysplitopts(args)
807 d = lambda: fn(ui, *args[1:])
811 d = lambda: fn(ui, *args[1:])
808 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
812 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
809 [], {})
813 [], {})
810
814
811 def _dispatch(req):
815 def _dispatch(req):
812 args = req.args
816 args = req.args
813 ui = req.ui
817 ui = req.ui
814
818
815 # check for cwd
819 # check for cwd
816 cwd = req.earlyoptions['cwd']
820 cwd = req.earlyoptions['cwd']
817 if cwd:
821 if cwd:
818 os.chdir(cwd)
822 os.chdir(cwd)
819
823
820 rpath = req.earlyoptions['repository']
824 rpath = req.earlyoptions['repository']
821 path, lui = _getlocal(ui, rpath)
825 path, lui = _getlocal(ui, rpath)
822
826
823 uis = {ui, lui}
827 uis = {ui, lui}
824
828
825 if req.repo:
829 if req.repo:
826 uis.add(req.repo.ui)
830 uis.add(req.repo.ui)
827
831
828 if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
832 if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
829 or req.earlyoptions['quiet']):
833 or req.earlyoptions['quiet']):
830 for opt in ('verbose', 'debug', 'quiet'):
834 for opt in ('verbose', 'debug', 'quiet'):
831 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
835 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
832 for ui_ in uis:
836 for ui_ in uis:
833 ui_.setconfig('ui', opt, val, '--' + opt)
837 ui_.setconfig('ui', opt, val, '--' + opt)
834
838
835 if req.earlyoptions['profile']:
839 if req.earlyoptions['profile']:
836 for ui_ in uis:
840 for ui_ in uis:
837 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
841 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
838
842
839 profile = lui.configbool('profiling', 'enabled')
843 profile = lui.configbool('profiling', 'enabled')
840 with profiling.profile(lui, enabled=profile) as profiler:
844 with profiling.profile(lui, enabled=profile) as profiler:
841 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
845 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
842 # reposetup
846 # reposetup
843 extensions.loadall(lui)
847 extensions.loadall(lui)
844 # Propagate any changes to lui.__class__ by extensions
848 # Propagate any changes to lui.__class__ by extensions
845 ui.__class__ = lui.__class__
849 ui.__class__ = lui.__class__
846
850
847 # (uisetup and extsetup are handled in extensions.loadall)
851 # (uisetup and extsetup are handled in extensions.loadall)
848
852
849 # (reposetup is handled in hg.repository)
853 # (reposetup is handled in hg.repository)
850
854
851 addaliases(lui, commands.table)
855 addaliases(lui, commands.table)
852
856
853 # All aliases and commands are completely defined, now.
857 # All aliases and commands are completely defined, now.
854 # Check abbreviation/ambiguity of shell alias.
858 # Check abbreviation/ambiguity of shell alias.
855 shellaliasfn = _checkshellalias(lui, ui, args)
859 shellaliasfn = _checkshellalias(lui, ui, args)
856 if shellaliasfn:
860 if shellaliasfn:
857 return shellaliasfn()
861 return shellaliasfn()
858
862
859 # check for fallback encoding
863 # check for fallback encoding
860 fallback = lui.config('ui', 'fallbackencoding')
864 fallback = lui.config('ui', 'fallbackencoding')
861 if fallback:
865 if fallback:
862 encoding.fallbackencoding = fallback
866 encoding.fallbackencoding = fallback
863
867
864 fullargs = args
868 fullargs = args
865 cmd, func, args, options, cmdoptions = _parse(lui, args)
869 cmd, func, args, options, cmdoptions = _parse(lui, args)
866
870
867 # store the canonical command name in request object for later access
871 # store the canonical command name in request object for later access
868 req.canonical_command = cmd
872 req.canonical_command = cmd
869
873
870 if options["config"] != req.earlyoptions["config"]:
874 if options["config"] != req.earlyoptions["config"]:
871 raise error.Abort(_("option --config may not be abbreviated!"))
875 raise error.Abort(_("option --config may not be abbreviated!"))
872 if options["cwd"] != req.earlyoptions["cwd"]:
876 if options["cwd"] != req.earlyoptions["cwd"]:
873 raise error.Abort(_("option --cwd may not be abbreviated!"))
877 raise error.Abort(_("option --cwd may not be abbreviated!"))
874 if options["repository"] != req.earlyoptions["repository"]:
878 if options["repository"] != req.earlyoptions["repository"]:
875 raise error.Abort(_(
879 raise error.Abort(_(
876 "option -R has to be separated from other options (e.g. not "
880 "option -R has to be separated from other options (e.g. not "
877 "-qR) and --repository may only be abbreviated as --repo!"))
881 "-qR) and --repository may only be abbreviated as --repo!"))
878 if options["debugger"] != req.earlyoptions["debugger"]:
882 if options["debugger"] != req.earlyoptions["debugger"]:
879 raise error.Abort(_("option --debugger may not be abbreviated!"))
883 raise error.Abort(_("option --debugger may not be abbreviated!"))
880 # don't validate --profile/--traceback, which can be enabled from now
884 # don't validate --profile/--traceback, which can be enabled from now
881
885
882 if options["encoding"]:
886 if options["encoding"]:
883 encoding.encoding = options["encoding"]
887 encoding.encoding = options["encoding"]
884 if options["encodingmode"]:
888 if options["encodingmode"]:
885 encoding.encodingmode = options["encodingmode"]
889 encoding.encodingmode = options["encodingmode"]
886 if options["time"]:
890 if options["time"]:
887 def get_times():
891 def get_times():
888 t = os.times()
892 t = os.times()
889 if t[4] == 0.0:
893 if t[4] == 0.0:
890 # Windows leaves this as zero, so use time.clock()
894 # Windows leaves this as zero, so use time.clock()
891 t = (t[0], t[1], t[2], t[3], time.clock())
895 t = (t[0], t[1], t[2], t[3], time.clock())
892 return t
896 return t
893 s = get_times()
897 s = get_times()
894 def print_time():
898 def print_time():
895 t = get_times()
899 t = get_times()
896 ui.warn(
900 ui.warn(
897 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
901 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
898 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
902 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
899 ui.atexit(print_time)
903 ui.atexit(print_time)
900 if options["profile"]:
904 if options["profile"]:
901 profiler.start()
905 profiler.start()
902
906
903 # if abbreviated version of this were used, take them in account, now
907 # if abbreviated version of this were used, take them in account, now
904 if options['verbose'] or options['debug'] or options['quiet']:
908 if options['verbose'] or options['debug'] or options['quiet']:
905 for opt in ('verbose', 'debug', 'quiet'):
909 for opt in ('verbose', 'debug', 'quiet'):
906 if options[opt] == req.earlyoptions[opt]:
910 if options[opt] == req.earlyoptions[opt]:
907 continue
911 continue
908 val = pycompat.bytestr(bool(options[opt]))
912 val = pycompat.bytestr(bool(options[opt]))
909 for ui_ in uis:
913 for ui_ in uis:
910 ui_.setconfig('ui', opt, val, '--' + opt)
914 ui_.setconfig('ui', opt, val, '--' + opt)
911
915
912 if options['traceback']:
916 if options['traceback']:
913 for ui_ in uis:
917 for ui_ in uis:
914 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
918 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
915
919
916 if options['noninteractive']:
920 if options['noninteractive']:
917 for ui_ in uis:
921 for ui_ in uis:
918 ui_.setconfig('ui', 'interactive', 'off', '-y')
922 ui_.setconfig('ui', 'interactive', 'off', '-y')
919
923
920 if cmdoptions.get('insecure', False):
924 if cmdoptions.get('insecure', False):
921 for ui_ in uis:
925 for ui_ in uis:
922 ui_.insecureconnections = True
926 ui_.insecureconnections = True
923
927
924 # setup color handling before pager, because setting up pager
928 # setup color handling before pager, because setting up pager
925 # might cause incorrect console information
929 # might cause incorrect console information
926 coloropt = options['color']
930 coloropt = options['color']
927 for ui_ in uis:
931 for ui_ in uis:
928 if coloropt:
932 if coloropt:
929 ui_.setconfig('ui', 'color', coloropt, '--color')
933 ui_.setconfig('ui', 'color', coloropt, '--color')
930 color.setup(ui_)
934 color.setup(ui_)
931
935
932 if stringutil.parsebool(options['pager']):
936 if stringutil.parsebool(options['pager']):
933 # ui.pager() expects 'internal-always-' prefix in this case
937 # ui.pager() expects 'internal-always-' prefix in this case
934 ui.pager('internal-always-' + cmd)
938 ui.pager('internal-always-' + cmd)
935 elif options['pager'] != 'auto':
939 elif options['pager'] != 'auto':
936 for ui_ in uis:
940 for ui_ in uis:
937 ui_.disablepager()
941 ui_.disablepager()
938
942
939 if options['version']:
943 if options['version']:
940 return commands.version_(ui)
944 return commands.version_(ui)
941 if options['help']:
945 if options['help']:
942 return commands.help_(ui, cmd, command=cmd is not None)
946 return commands.help_(ui, cmd, command=cmd is not None)
943 elif not cmd:
947 elif not cmd:
944 return commands.help_(ui, 'shortlist')
948 return commands.help_(ui, 'shortlist')
945
949
946 repo = None
950 repo = None
947 cmdpats = args[:]
951 cmdpats = args[:]
948 if not func.norepo:
952 if not func.norepo:
949 # use the repo from the request only if we don't have -R
953 # use the repo from the request only if we don't have -R
950 if not rpath and not cwd:
954 if not rpath and not cwd:
951 repo = req.repo
955 repo = req.repo
952
956
953 if repo:
957 if repo:
954 # set the descriptors of the repo ui to those of ui
958 # set the descriptors of the repo ui to those of ui
955 repo.ui.fin = ui.fin
959 repo.ui.fin = ui.fin
956 repo.ui.fout = ui.fout
960 repo.ui.fout = ui.fout
957 repo.ui.ferr = ui.ferr
961 repo.ui.ferr = ui.ferr
962 repo.ui.fmsg = ui.fmsg
958 else:
963 else:
959 try:
964 try:
960 repo = hg.repository(ui, path=path,
965 repo = hg.repository(ui, path=path,
961 presetupfuncs=req.prereposetups,
966 presetupfuncs=req.prereposetups,
962 intents=func.intents)
967 intents=func.intents)
963 if not repo.local():
968 if not repo.local():
964 raise error.Abort(_("repository '%s' is not local")
969 raise error.Abort(_("repository '%s' is not local")
965 % path)
970 % path)
966 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
971 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
967 'repo')
972 'repo')
968 except error.RequirementError:
973 except error.RequirementError:
969 raise
974 raise
970 except error.RepoError:
975 except error.RepoError:
971 if rpath: # invalid -R path
976 if rpath: # invalid -R path
972 raise
977 raise
973 if not func.optionalrepo:
978 if not func.optionalrepo:
974 if func.inferrepo and args and not path:
979 if func.inferrepo and args and not path:
975 # try to infer -R from command args
980 # try to infer -R from command args
976 repos = pycompat.maplist(cmdutil.findrepo, args)
981 repos = pycompat.maplist(cmdutil.findrepo, args)
977 guess = repos[0]
982 guess = repos[0]
978 if guess and repos.count(guess) == len(repos):
983 if guess and repos.count(guess) == len(repos):
979 req.args = ['--repository', guess] + fullargs
984 req.args = ['--repository', guess] + fullargs
980 req.earlyoptions['repository'] = guess
985 req.earlyoptions['repository'] = guess
981 return _dispatch(req)
986 return _dispatch(req)
982 if not path:
987 if not path:
983 raise error.RepoError(_("no repository found in"
988 raise error.RepoError(_("no repository found in"
984 " '%s' (.hg not found)")
989 " '%s' (.hg not found)")
985 % encoding.getcwd())
990 % encoding.getcwd())
986 raise
991 raise
987 if repo:
992 if repo:
988 ui = repo.ui
993 ui = repo.ui
989 if options['hidden']:
994 if options['hidden']:
990 repo = repo.unfiltered()
995 repo = repo.unfiltered()
991 args.insert(0, repo)
996 args.insert(0, repo)
992 elif rpath:
997 elif rpath:
993 ui.warn(_("warning: --repository ignored\n"))
998 ui.warn(_("warning: --repository ignored\n"))
994
999
995 msg = _formatargs(fullargs)
1000 msg = _formatargs(fullargs)
996 ui.log("command", '%s\n', msg)
1001 ui.log("command", '%s\n', msg)
997 strcmdopt = pycompat.strkwargs(cmdoptions)
1002 strcmdopt = pycompat.strkwargs(cmdoptions)
998 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1003 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
999 try:
1004 try:
1000 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1005 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
1001 cmdpats, cmdoptions)
1006 cmdpats, cmdoptions)
1002 finally:
1007 finally:
1003 if repo and repo != req.repo:
1008 if repo and repo != req.repo:
1004 repo.close()
1009 repo.close()
1005
1010
1006 def _runcommand(ui, options, cmd, cmdfunc):
1011 def _runcommand(ui, options, cmd, cmdfunc):
1007 """Run a command function, possibly with profiling enabled."""
1012 """Run a command function, possibly with profiling enabled."""
1008 try:
1013 try:
1009 with tracing.log("Running %s command" % cmd):
1014 with tracing.log("Running %s command" % cmd):
1010 return cmdfunc()
1015 return cmdfunc()
1011 except error.SignatureError:
1016 except error.SignatureError:
1012 raise error.CommandError(cmd, _('invalid arguments'))
1017 raise error.CommandError(cmd, _('invalid arguments'))
1013
1018
1014 def _exceptionwarning(ui):
1019 def _exceptionwarning(ui):
1015 """Produce a warning message for the current active exception"""
1020 """Produce a warning message for the current active exception"""
1016
1021
1017 # For compatibility checking, we discard the portion of the hg
1022 # For compatibility checking, we discard the portion of the hg
1018 # version after the + on the assumption that if a "normal
1023 # version after the + on the assumption that if a "normal
1019 # user" is running a build with a + in it the packager
1024 # user" is running a build with a + in it the packager
1020 # probably built from fairly close to a tag and anyone with a
1025 # probably built from fairly close to a tag and anyone with a
1021 # 'make local' copy of hg (where the version number can be out
1026 # 'make local' copy of hg (where the version number can be out
1022 # of date) will be clueful enough to notice the implausible
1027 # of date) will be clueful enough to notice the implausible
1023 # version number and try updating.
1028 # version number and try updating.
1024 ct = util.versiontuple(n=2)
1029 ct = util.versiontuple(n=2)
1025 worst = None, ct, ''
1030 worst = None, ct, ''
1026 if ui.config('ui', 'supportcontact') is None:
1031 if ui.config('ui', 'supportcontact') is None:
1027 for name, mod in extensions.extensions():
1032 for name, mod in extensions.extensions():
1028 # 'testedwith' should be bytes, but not all extensions are ported
1033 # 'testedwith' should be bytes, but not all extensions are ported
1029 # to py3 and we don't want UnicodeException because of that.
1034 # to py3 and we don't want UnicodeException because of that.
1030 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1035 testedwith = stringutil.forcebytestr(getattr(mod, 'testedwith', ''))
1031 report = getattr(mod, 'buglink', _('the extension author.'))
1036 report = getattr(mod, 'buglink', _('the extension author.'))
1032 if not testedwith.strip():
1037 if not testedwith.strip():
1033 # We found an untested extension. It's likely the culprit.
1038 # We found an untested extension. It's likely the culprit.
1034 worst = name, 'unknown', report
1039 worst = name, 'unknown', report
1035 break
1040 break
1036
1041
1037 # Never blame on extensions bundled with Mercurial.
1042 # Never blame on extensions bundled with Mercurial.
1038 if extensions.ismoduleinternal(mod):
1043 if extensions.ismoduleinternal(mod):
1039 continue
1044 continue
1040
1045
1041 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1046 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1042 if ct in tested:
1047 if ct in tested:
1043 continue
1048 continue
1044
1049
1045 lower = [t for t in tested if t < ct]
1050 lower = [t for t in tested if t < ct]
1046 nearest = max(lower or tested)
1051 nearest = max(lower or tested)
1047 if worst[0] is None or nearest < worst[1]:
1052 if worst[0] is None or nearest < worst[1]:
1048 worst = name, nearest, report
1053 worst = name, nearest, report
1049 if worst[0] is not None:
1054 if worst[0] is not None:
1050 name, testedwith, report = worst
1055 name, testedwith, report = worst
1051 if not isinstance(testedwith, (bytes, str)):
1056 if not isinstance(testedwith, (bytes, str)):
1052 testedwith = '.'.join([stringutil.forcebytestr(c)
1057 testedwith = '.'.join([stringutil.forcebytestr(c)
1053 for c in testedwith])
1058 for c in testedwith])
1054 warning = (_('** Unknown exception encountered with '
1059 warning = (_('** Unknown exception encountered with '
1055 'possibly-broken third-party extension %s\n'
1060 'possibly-broken third-party extension %s\n'
1056 '** which supports versions %s of Mercurial.\n'
1061 '** which supports versions %s of Mercurial.\n'
1057 '** Please disable %s and try your action again.\n'
1062 '** Please disable %s and try your action again.\n'
1058 '** If that fixes the bug please report it to %s\n')
1063 '** If that fixes the bug please report it to %s\n')
1059 % (name, testedwith, name, stringutil.forcebytestr(report)))
1064 % (name, testedwith, name, stringutil.forcebytestr(report)))
1060 else:
1065 else:
1061 bugtracker = ui.config('ui', 'supportcontact')
1066 bugtracker = ui.config('ui', 'supportcontact')
1062 if bugtracker is None:
1067 if bugtracker is None:
1063 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1068 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1064 warning = (_("** unknown exception encountered, "
1069 warning = (_("** unknown exception encountered, "
1065 "please report by visiting\n** ") + bugtracker + '\n')
1070 "please report by visiting\n** ") + bugtracker + '\n')
1066 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1071 sysversion = pycompat.sysbytes(sys.version).replace('\n', '')
1067 warning += ((_("** Python %s\n") % sysversion) +
1072 warning += ((_("** Python %s\n") % sysversion) +
1068 (_("** Mercurial Distributed SCM (version %s)\n") %
1073 (_("** Mercurial Distributed SCM (version %s)\n") %
1069 util.version()) +
1074 util.version()) +
1070 (_("** Extensions loaded: %s\n") %
1075 (_("** Extensions loaded: %s\n") %
1071 ", ".join([x[0] for x in extensions.extensions()])))
1076 ", ".join([x[0] for x in extensions.extensions()])))
1072 return warning
1077 return warning
1073
1078
1074 def handlecommandexception(ui):
1079 def handlecommandexception(ui):
1075 """Produce a warning message for broken commands
1080 """Produce a warning message for broken commands
1076
1081
1077 Called when handling an exception; the exception is reraised if
1082 Called when handling an exception; the exception is reraised if
1078 this function returns False, ignored otherwise.
1083 this function returns False, ignored otherwise.
1079 """
1084 """
1080 warning = _exceptionwarning(ui)
1085 warning = _exceptionwarning(ui)
1081 ui.log("commandexception", "%s\n%s\n", warning,
1086 ui.log("commandexception", "%s\n%s\n", warning,
1082 pycompat.sysbytes(traceback.format_exc()))
1087 pycompat.sysbytes(traceback.format_exc()))
1083 ui.warn(warning)
1088 ui.warn(warning)
1084 return False # re-raise the exception
1089 return False # re-raise the exception
@@ -1,1956 +1,1969 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 progress,
33 progress,
34 pycompat,
34 pycompat,
35 rcutil,
35 rcutil,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 procutil,
41 procutil,
42 stringutil,
42 stringutil,
43 )
43 )
44
44
45 urlreq = util.urlreq
45 urlreq = util.urlreq
46
46
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
47 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
48 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 if not c.isalnum())
49 if not c.isalnum())
50
50
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
51 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 tweakrc = b"""
52 tweakrc = b"""
53 [ui]
53 [ui]
54 # The rollback command is dangerous. As a rule, don't use it.
54 # The rollback command is dangerous. As a rule, don't use it.
55 rollback = False
55 rollback = False
56 # Make `hg status` report copy information
56 # Make `hg status` report copy information
57 statuscopies = yes
57 statuscopies = yes
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
58 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 interface = curses
59 interface = curses
60
60
61 [commands]
61 [commands]
62 # Grep working directory by default.
62 # Grep working directory by default.
63 grep.all-files = True
63 grep.all-files = True
64 # Make `hg status` emit cwd-relative paths by default.
64 # Make `hg status` emit cwd-relative paths by default.
65 status.relative = yes
65 status.relative = yes
66 # Refuse to perform an `hg update` that would cause a file content merge
66 # Refuse to perform an `hg update` that would cause a file content merge
67 update.check = noconflict
67 update.check = noconflict
68 # Show conflicts information in `hg status`
68 # Show conflicts information in `hg status`
69 status.verbose = True
69 status.verbose = True
70
70
71 [diff]
71 [diff]
72 git = 1
72 git = 1
73 showfunc = 1
73 showfunc = 1
74 word-diff = 1
74 word-diff = 1
75 """
75 """
76
76
77 samplehgrcs = {
77 samplehgrcs = {
78 'user':
78 'user':
79 b"""# example user config (see 'hg help config' for more info)
79 b"""# example user config (see 'hg help config' for more info)
80 [ui]
80 [ui]
81 # name and email, e.g.
81 # name and email, e.g.
82 # username = Jane Doe <jdoe@example.com>
82 # username = Jane Doe <jdoe@example.com>
83 username =
83 username =
84
84
85 # We recommend enabling tweakdefaults to get slight improvements to
85 # We recommend enabling tweakdefaults to get slight improvements to
86 # the UI over time. Make sure to set HGPLAIN in the environment when
86 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # writing scripts!
87 # writing scripts!
88 # tweakdefaults = True
88 # tweakdefaults = True
89
89
90 # uncomment to disable color in command output
90 # uncomment to disable color in command output
91 # (see 'hg help color' for details)
91 # (see 'hg help color' for details)
92 # color = never
92 # color = never
93
93
94 # uncomment to disable command output pagination
94 # uncomment to disable command output pagination
95 # (see 'hg help pager' for details)
95 # (see 'hg help pager' for details)
96 # paginate = never
96 # paginate = never
97
97
98 [extensions]
98 [extensions]
99 # uncomment these lines to enable some popular extensions
99 # uncomment these lines to enable some popular extensions
100 # (see 'hg help extensions' for more info)
100 # (see 'hg help extensions' for more info)
101 #
101 #
102 # churn =
102 # churn =
103 """,
103 """,
104
104
105 'cloned':
105 'cloned':
106 b"""# example repository config (see 'hg help config' for more info)
106 b"""# example repository config (see 'hg help config' for more info)
107 [paths]
107 [paths]
108 default = %s
108 default = %s
109
109
110 # path aliases to other clones of this repo in URLs or filesystem paths
110 # path aliases to other clones of this repo in URLs or filesystem paths
111 # (see 'hg help config.paths' for more info)
111 # (see 'hg help config.paths' for more info)
112 #
112 #
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
113 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
114 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-clone = /home/jdoe/jdoes-clone
115 # my-clone = /home/jdoe/jdoes-clone
116
116
117 [ui]
117 [ui]
118 # name and email (local to this repository, optional), e.g.
118 # name and email (local to this repository, optional), e.g.
119 # username = Jane Doe <jdoe@example.com>
119 # username = Jane Doe <jdoe@example.com>
120 """,
120 """,
121
121
122 'local':
122 'local':
123 b"""# example repository config (see 'hg help config' for more info)
123 b"""# example repository config (see 'hg help config' for more info)
124 [paths]
124 [paths]
125 # path aliases to other clones of this repo in URLs or filesystem paths
125 # path aliases to other clones of this repo in URLs or filesystem paths
126 # (see 'hg help config.paths' for more info)
126 # (see 'hg help config.paths' for more info)
127 #
127 #
128 # default = http://example.com/hg/example-repo
128 # default = http://example.com/hg/example-repo
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
129 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
130 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-clone = /home/jdoe/jdoes-clone
131 # my-clone = /home/jdoe/jdoes-clone
132
132
133 [ui]
133 [ui]
134 # name and email (local to this repository, optional), e.g.
134 # name and email (local to this repository, optional), e.g.
135 # username = Jane Doe <jdoe@example.com>
135 # username = Jane Doe <jdoe@example.com>
136 """,
136 """,
137
137
138 'global':
138 'global':
139 b"""# example system-wide hg config (see 'hg help config' for more info)
139 b"""# example system-wide hg config (see 'hg help config' for more info)
140
140
141 [ui]
141 [ui]
142 # uncomment to disable color in command output
142 # uncomment to disable color in command output
143 # (see 'hg help color' for details)
143 # (see 'hg help color' for details)
144 # color = never
144 # color = never
145
145
146 # uncomment to disable command output pagination
146 # uncomment to disable command output pagination
147 # (see 'hg help pager' for details)
147 # (see 'hg help pager' for details)
148 # paginate = never
148 # paginate = never
149
149
150 [extensions]
150 [extensions]
151 # uncomment these lines to enable some popular extensions
151 # uncomment these lines to enable some popular extensions
152 # (see 'hg help extensions' for more info)
152 # (see 'hg help extensions' for more info)
153 #
153 #
154 # blackbox =
154 # blackbox =
155 # churn =
155 # churn =
156 """,
156 """,
157 }
157 }
158
158
159 def _maybestrurl(maybebytes):
159 def _maybestrurl(maybebytes):
160 return pycompat.rapply(pycompat.strurl, maybebytes)
160 return pycompat.rapply(pycompat.strurl, maybebytes)
161
161
162 def _maybebytesurl(maybestr):
162 def _maybebytesurl(maybestr):
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
163 return pycompat.rapply(pycompat.bytesurl, maybestr)
164
164
165 class httppasswordmgrdbproxy(object):
165 class httppasswordmgrdbproxy(object):
166 """Delays loading urllib2 until it's needed."""
166 """Delays loading urllib2 until it's needed."""
167 def __init__(self):
167 def __init__(self):
168 self._mgr = None
168 self._mgr = None
169
169
170 def _get_mgr(self):
170 def _get_mgr(self):
171 if self._mgr is None:
171 if self._mgr is None:
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
172 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 return self._mgr
173 return self._mgr
174
174
175 def add_password(self, realm, uris, user, passwd):
175 def add_password(self, realm, uris, user, passwd):
176 return self._get_mgr().add_password(
176 return self._get_mgr().add_password(
177 _maybestrurl(realm), _maybestrurl(uris),
177 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(user), _maybestrurl(passwd))
178 _maybestrurl(user), _maybestrurl(passwd))
179
179
180 def find_user_password(self, realm, uri):
180 def find_user_password(self, realm, uri):
181 mgr = self._get_mgr()
181 mgr = self._get_mgr()
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
182 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 _maybestrurl(uri)))
183 _maybestrurl(uri)))
184
184
185 def _catchterm(*args):
185 def _catchterm(*args):
186 raise error.SignalInterrupt
186 raise error.SignalInterrupt
187
187
188 # unique object used to detect no default value has been provided when
188 # unique object used to detect no default value has been provided when
189 # retrieving configuration value.
189 # retrieving configuration value.
190 _unset = object()
190 _unset = object()
191
191
192 # _reqexithandlers: callbacks run at the end of a request
192 # _reqexithandlers: callbacks run at the end of a request
193 _reqexithandlers = []
193 _reqexithandlers = []
194
194
195 class ui(object):
195 class ui(object):
196 def __init__(self, src=None):
196 def __init__(self, src=None):
197 """Create a fresh new ui object if no src given
197 """Create a fresh new ui object if no src given
198
198
199 Use uimod.ui.load() to create a ui which knows global and user configs.
199 Use uimod.ui.load() to create a ui which knows global and user configs.
200 In most cases, you should use ui.copy() to create a copy of an existing
200 In most cases, you should use ui.copy() to create a copy of an existing
201 ui object.
201 ui object.
202 """
202 """
203 # _buffers: used for temporary capture of output
203 # _buffers: used for temporary capture of output
204 self._buffers = []
204 self._buffers = []
205 # 3-tuple describing how each buffer in the stack behaves.
205 # 3-tuple describing how each buffer in the stack behaves.
206 # Values are (capture stderr, capture subprocesses, apply labels).
206 # Values are (capture stderr, capture subprocesses, apply labels).
207 self._bufferstates = []
207 self._bufferstates = []
208 # When a buffer is active, defines whether we are expanding labels.
208 # When a buffer is active, defines whether we are expanding labels.
209 # This exists to prevent an extra list lookup.
209 # This exists to prevent an extra list lookup.
210 self._bufferapplylabels = None
210 self._bufferapplylabels = None
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
211 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self._reportuntrusted = True
212 self._reportuntrusted = True
213 self._knownconfig = configitems.coreitems
213 self._knownconfig = configitems.coreitems
214 self._ocfg = config.config() # overlay
214 self._ocfg = config.config() # overlay
215 self._tcfg = config.config() # trusted
215 self._tcfg = config.config() # trusted
216 self._ucfg = config.config() # untrusted
216 self._ucfg = config.config() # untrusted
217 self._trustusers = set()
217 self._trustusers = set()
218 self._trustgroups = set()
218 self._trustgroups = set()
219 self.callhooks = True
219 self.callhooks = True
220 # Insecure server connections requested.
220 # Insecure server connections requested.
221 self.insecureconnections = False
221 self.insecureconnections = False
222 # Blocked time
222 # Blocked time
223 self.logblockedtimes = False
223 self.logblockedtimes = False
224 # color mode: see mercurial/color.py for possible value
224 # color mode: see mercurial/color.py for possible value
225 self._colormode = None
225 self._colormode = None
226 self._terminfoparams = {}
226 self._terminfoparams = {}
227 self._styles = {}
227 self._styles = {}
228 self._uninterruptible = False
228 self._uninterruptible = False
229
229
230 if src:
230 if src:
231 self._fout = src._fout
231 self._fout = src._fout
232 self._ferr = src._ferr
232 self._ferr = src._ferr
233 self._fin = src._fin
233 self._fin = src._fin
234 self._fmsg = src._fmsg
234 self._fmsgout = src._fmsgout
235 self._fmsgout = src._fmsgout
235 self._fmsgerr = src._fmsgerr
236 self._fmsgerr = src._fmsgerr
236 self._finoutredirected = src._finoutredirected
237 self._finoutredirected = src._finoutredirected
237 self.pageractive = src.pageractive
238 self.pageractive = src.pageractive
238 self._disablepager = src._disablepager
239 self._disablepager = src._disablepager
239 self._tweaked = src._tweaked
240 self._tweaked = src._tweaked
240
241
241 self._tcfg = src._tcfg.copy()
242 self._tcfg = src._tcfg.copy()
242 self._ucfg = src._ucfg.copy()
243 self._ucfg = src._ucfg.copy()
243 self._ocfg = src._ocfg.copy()
244 self._ocfg = src._ocfg.copy()
244 self._trustusers = src._trustusers.copy()
245 self._trustusers = src._trustusers.copy()
245 self._trustgroups = src._trustgroups.copy()
246 self._trustgroups = src._trustgroups.copy()
246 self.environ = src.environ
247 self.environ = src.environ
247 self.callhooks = src.callhooks
248 self.callhooks = src.callhooks
248 self.insecureconnections = src.insecureconnections
249 self.insecureconnections = src.insecureconnections
249 self._colormode = src._colormode
250 self._colormode = src._colormode
250 self._terminfoparams = src._terminfoparams.copy()
251 self._terminfoparams = src._terminfoparams.copy()
251 self._styles = src._styles.copy()
252 self._styles = src._styles.copy()
252
253
253 self.fixconfig()
254 self.fixconfig()
254
255
255 self.httppasswordmgrdb = src.httppasswordmgrdb
256 self.httppasswordmgrdb = src.httppasswordmgrdb
256 self._blockedtimes = src._blockedtimes
257 self._blockedtimes = src._blockedtimes
257 else:
258 else:
258 self._fout = procutil.stdout
259 self._fout = procutil.stdout
259 self._ferr = procutil.stderr
260 self._ferr = procutil.stderr
260 self._fin = procutil.stdin
261 self._fin = procutil.stdin
262 self._fmsg = None
261 self._fmsgout = self.fout # configurable
263 self._fmsgout = self.fout # configurable
262 self._fmsgerr = self.ferr # configurable
264 self._fmsgerr = self.ferr # configurable
263 self._finoutredirected = False
265 self._finoutredirected = False
264 self.pageractive = False
266 self.pageractive = False
265 self._disablepager = False
267 self._disablepager = False
266 self._tweaked = False
268 self._tweaked = False
267
269
268 # shared read-only environment
270 # shared read-only environment
269 self.environ = encoding.environ
271 self.environ = encoding.environ
270
272
271 self.httppasswordmgrdb = httppasswordmgrdbproxy()
273 self.httppasswordmgrdb = httppasswordmgrdbproxy()
272 self._blockedtimes = collections.defaultdict(int)
274 self._blockedtimes = collections.defaultdict(int)
273
275
274 allowed = self.configlist('experimental', 'exportableenviron')
276 allowed = self.configlist('experimental', 'exportableenviron')
275 if '*' in allowed:
277 if '*' in allowed:
276 self._exportableenviron = self.environ
278 self._exportableenviron = self.environ
277 else:
279 else:
278 self._exportableenviron = {}
280 self._exportableenviron = {}
279 for k in allowed:
281 for k in allowed:
280 if k in self.environ:
282 if k in self.environ:
281 self._exportableenviron[k] = self.environ[k]
283 self._exportableenviron[k] = self.environ[k]
282
284
283 @classmethod
285 @classmethod
284 def load(cls):
286 def load(cls):
285 """Create a ui and load global and user configs"""
287 """Create a ui and load global and user configs"""
286 u = cls()
288 u = cls()
287 # we always trust global config files and environment variables
289 # we always trust global config files and environment variables
288 for t, f in rcutil.rccomponents():
290 for t, f in rcutil.rccomponents():
289 if t == 'path':
291 if t == 'path':
290 u.readconfig(f, trust=True)
292 u.readconfig(f, trust=True)
291 elif t == 'items':
293 elif t == 'items':
292 sections = set()
294 sections = set()
293 for section, name, value, source in f:
295 for section, name, value, source in f:
294 # do not set u._ocfg
296 # do not set u._ocfg
295 # XXX clean this up once immutable config object is a thing
297 # XXX clean this up once immutable config object is a thing
296 u._tcfg.set(section, name, value, source)
298 u._tcfg.set(section, name, value, source)
297 u._ucfg.set(section, name, value, source)
299 u._ucfg.set(section, name, value, source)
298 sections.add(section)
300 sections.add(section)
299 for section in sections:
301 for section in sections:
300 u.fixconfig(section=section)
302 u.fixconfig(section=section)
301 else:
303 else:
302 raise error.ProgrammingError('unknown rctype: %s' % t)
304 raise error.ProgrammingError('unknown rctype: %s' % t)
303 u._maybetweakdefaults()
305 u._maybetweakdefaults()
304 return u
306 return u
305
307
306 def _maybetweakdefaults(self):
308 def _maybetweakdefaults(self):
307 if not self.configbool('ui', 'tweakdefaults'):
309 if not self.configbool('ui', 'tweakdefaults'):
308 return
310 return
309 if self._tweaked or self.plain('tweakdefaults'):
311 if self._tweaked or self.plain('tweakdefaults'):
310 return
312 return
311
313
312 # Note: it is SUPER IMPORTANT that you set self._tweaked to
314 # Note: it is SUPER IMPORTANT that you set self._tweaked to
313 # True *before* any calls to setconfig(), otherwise you'll get
315 # True *before* any calls to setconfig(), otherwise you'll get
314 # infinite recursion between setconfig and this method.
316 # infinite recursion between setconfig and this method.
315 #
317 #
316 # TODO: We should extract an inner method in setconfig() to
318 # TODO: We should extract an inner method in setconfig() to
317 # avoid this weirdness.
319 # avoid this weirdness.
318 self._tweaked = True
320 self._tweaked = True
319 tmpcfg = config.config()
321 tmpcfg = config.config()
320 tmpcfg.parse('<tweakdefaults>', tweakrc)
322 tmpcfg.parse('<tweakdefaults>', tweakrc)
321 for section in tmpcfg:
323 for section in tmpcfg:
322 for name, value in tmpcfg.items(section):
324 for name, value in tmpcfg.items(section):
323 if not self.hasconfig(section, name):
325 if not self.hasconfig(section, name):
324 self.setconfig(section, name, value, "<tweakdefaults>")
326 self.setconfig(section, name, value, "<tweakdefaults>")
325
327
326 def copy(self):
328 def copy(self):
327 return self.__class__(self)
329 return self.__class__(self)
328
330
329 def resetstate(self):
331 def resetstate(self):
330 """Clear internal state that shouldn't persist across commands"""
332 """Clear internal state that shouldn't persist across commands"""
331 if self._progbar:
333 if self._progbar:
332 self._progbar.resetstate() # reset last-print time of progress bar
334 self._progbar.resetstate() # reset last-print time of progress bar
333 self.httppasswordmgrdb = httppasswordmgrdbproxy()
335 self.httppasswordmgrdb = httppasswordmgrdbproxy()
334
336
335 @contextlib.contextmanager
337 @contextlib.contextmanager
336 def timeblockedsection(self, key):
338 def timeblockedsection(self, key):
337 # this is open-coded below - search for timeblockedsection to find them
339 # this is open-coded below - search for timeblockedsection to find them
338 starttime = util.timer()
340 starttime = util.timer()
339 try:
341 try:
340 yield
342 yield
341 finally:
343 finally:
342 self._blockedtimes[key + '_blocked'] += \
344 self._blockedtimes[key + '_blocked'] += \
343 (util.timer() - starttime) * 1000
345 (util.timer() - starttime) * 1000
344
346
345 @contextlib.contextmanager
347 @contextlib.contextmanager
346 def uninterruptable(self):
348 def uninterruptable(self):
347 """Mark an operation as unsafe.
349 """Mark an operation as unsafe.
348
350
349 Most operations on a repository are safe to interrupt, but a
351 Most operations on a repository are safe to interrupt, but a
350 few are risky (for example repair.strip). This context manager
352 few are risky (for example repair.strip). This context manager
351 lets you advise Mercurial that something risky is happening so
353 lets you advise Mercurial that something risky is happening so
352 that control-C etc can be blocked if desired.
354 that control-C etc can be blocked if desired.
353 """
355 """
354 enabled = self.configbool('experimental', 'nointerrupt')
356 enabled = self.configbool('experimental', 'nointerrupt')
355 if (enabled and
357 if (enabled and
356 self.configbool('experimental', 'nointerrupt-interactiveonly')):
358 self.configbool('experimental', 'nointerrupt-interactiveonly')):
357 enabled = self.interactive()
359 enabled = self.interactive()
358 if self._uninterruptible or not enabled:
360 if self._uninterruptible or not enabled:
359 # if nointerrupt support is turned off, the process isn't
361 # if nointerrupt support is turned off, the process isn't
360 # interactive, or we're already in an uninterruptable
362 # interactive, or we're already in an uninterruptable
361 # block, do nothing.
363 # block, do nothing.
362 yield
364 yield
363 return
365 return
364 def warn():
366 def warn():
365 self.warn(_("shutting down cleanly\n"))
367 self.warn(_("shutting down cleanly\n"))
366 self.warn(
368 self.warn(
367 _("press ^C again to terminate immediately (dangerous)\n"))
369 _("press ^C again to terminate immediately (dangerous)\n"))
368 return True
370 return True
369 with procutil.uninterruptable(warn):
371 with procutil.uninterruptable(warn):
370 try:
372 try:
371 self._uninterruptible = True
373 self._uninterruptible = True
372 yield
374 yield
373 finally:
375 finally:
374 self._uninterruptible = False
376 self._uninterruptible = False
375
377
376 def formatter(self, topic, opts):
378 def formatter(self, topic, opts):
377 return formatter.formatter(self, self, topic, opts)
379 return formatter.formatter(self, self, topic, opts)
378
380
379 def _trusted(self, fp, f):
381 def _trusted(self, fp, f):
380 st = util.fstat(fp)
382 st = util.fstat(fp)
381 if util.isowner(st):
383 if util.isowner(st):
382 return True
384 return True
383
385
384 tusers, tgroups = self._trustusers, self._trustgroups
386 tusers, tgroups = self._trustusers, self._trustgroups
385 if '*' in tusers or '*' in tgroups:
387 if '*' in tusers or '*' in tgroups:
386 return True
388 return True
387
389
388 user = util.username(st.st_uid)
390 user = util.username(st.st_uid)
389 group = util.groupname(st.st_gid)
391 group = util.groupname(st.st_gid)
390 if user in tusers or group in tgroups or user == util.username():
392 if user in tusers or group in tgroups or user == util.username():
391 return True
393 return True
392
394
393 if self._reportuntrusted:
395 if self._reportuntrusted:
394 self.warn(_('not trusting file %s from untrusted '
396 self.warn(_('not trusting file %s from untrusted '
395 'user %s, group %s\n') % (f, user, group))
397 'user %s, group %s\n') % (f, user, group))
396 return False
398 return False
397
399
398 def readconfig(self, filename, root=None, trust=False,
400 def readconfig(self, filename, root=None, trust=False,
399 sections=None, remap=None):
401 sections=None, remap=None):
400 try:
402 try:
401 fp = open(filename, r'rb')
403 fp = open(filename, r'rb')
402 except IOError:
404 except IOError:
403 if not sections: # ignore unless we were looking for something
405 if not sections: # ignore unless we were looking for something
404 return
406 return
405 raise
407 raise
406
408
407 cfg = config.config()
409 cfg = config.config()
408 trusted = sections or trust or self._trusted(fp, filename)
410 trusted = sections or trust or self._trusted(fp, filename)
409
411
410 try:
412 try:
411 cfg.read(filename, fp, sections=sections, remap=remap)
413 cfg.read(filename, fp, sections=sections, remap=remap)
412 fp.close()
414 fp.close()
413 except error.ConfigError as inst:
415 except error.ConfigError as inst:
414 if trusted:
416 if trusted:
415 raise
417 raise
416 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
418 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
417
419
418 if self.plain():
420 if self.plain():
419 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
421 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
420 'logtemplate', 'message-output', 'statuscopies', 'style',
422 'logtemplate', 'message-output', 'statuscopies', 'style',
421 'traceback', 'verbose'):
423 'traceback', 'verbose'):
422 if k in cfg['ui']:
424 if k in cfg['ui']:
423 del cfg['ui'][k]
425 del cfg['ui'][k]
424 for k, v in cfg.items('defaults'):
426 for k, v in cfg.items('defaults'):
425 del cfg['defaults'][k]
427 del cfg['defaults'][k]
426 for k, v in cfg.items('commands'):
428 for k, v in cfg.items('commands'):
427 del cfg['commands'][k]
429 del cfg['commands'][k]
428 # Don't remove aliases from the configuration if in the exceptionlist
430 # Don't remove aliases from the configuration if in the exceptionlist
429 if self.plain('alias'):
431 if self.plain('alias'):
430 for k, v in cfg.items('alias'):
432 for k, v in cfg.items('alias'):
431 del cfg['alias'][k]
433 del cfg['alias'][k]
432 if self.plain('revsetalias'):
434 if self.plain('revsetalias'):
433 for k, v in cfg.items('revsetalias'):
435 for k, v in cfg.items('revsetalias'):
434 del cfg['revsetalias'][k]
436 del cfg['revsetalias'][k]
435 if self.plain('templatealias'):
437 if self.plain('templatealias'):
436 for k, v in cfg.items('templatealias'):
438 for k, v in cfg.items('templatealias'):
437 del cfg['templatealias'][k]
439 del cfg['templatealias'][k]
438
440
439 if trusted:
441 if trusted:
440 self._tcfg.update(cfg)
442 self._tcfg.update(cfg)
441 self._tcfg.update(self._ocfg)
443 self._tcfg.update(self._ocfg)
442 self._ucfg.update(cfg)
444 self._ucfg.update(cfg)
443 self._ucfg.update(self._ocfg)
445 self._ucfg.update(self._ocfg)
444
446
445 if root is None:
447 if root is None:
446 root = os.path.expanduser('~')
448 root = os.path.expanduser('~')
447 self.fixconfig(root=root)
449 self.fixconfig(root=root)
448
450
449 def fixconfig(self, root=None, section=None):
451 def fixconfig(self, root=None, section=None):
450 if section in (None, 'paths'):
452 if section in (None, 'paths'):
451 # expand vars and ~
453 # expand vars and ~
452 # translate paths relative to root (or home) into absolute paths
454 # translate paths relative to root (or home) into absolute paths
453 root = root or encoding.getcwd()
455 root = root or encoding.getcwd()
454 for c in self._tcfg, self._ucfg, self._ocfg:
456 for c in self._tcfg, self._ucfg, self._ocfg:
455 for n, p in c.items('paths'):
457 for n, p in c.items('paths'):
456 # Ignore sub-options.
458 # Ignore sub-options.
457 if ':' in n:
459 if ':' in n:
458 continue
460 continue
459 if not p:
461 if not p:
460 continue
462 continue
461 if '%%' in p:
463 if '%%' in p:
462 s = self.configsource('paths', n) or 'none'
464 s = self.configsource('paths', n) or 'none'
463 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
465 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
464 % (n, p, s))
466 % (n, p, s))
465 p = p.replace('%%', '%')
467 p = p.replace('%%', '%')
466 p = util.expandpath(p)
468 p = util.expandpath(p)
467 if not util.hasscheme(p) and not os.path.isabs(p):
469 if not util.hasscheme(p) and not os.path.isabs(p):
468 p = os.path.normpath(os.path.join(root, p))
470 p = os.path.normpath(os.path.join(root, p))
469 c.set("paths", n, p)
471 c.set("paths", n, p)
470
472
471 if section in (None, 'ui'):
473 if section in (None, 'ui'):
472 # update ui options
474 # update ui options
473 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
475 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
474 self.debugflag = self.configbool('ui', 'debug')
476 self.debugflag = self.configbool('ui', 'debug')
475 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
477 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
476 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
478 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
477 if self.verbose and self.quiet:
479 if self.verbose and self.quiet:
478 self.quiet = self.verbose = False
480 self.quiet = self.verbose = False
479 self._reportuntrusted = self.debugflag or self.configbool("ui",
481 self._reportuntrusted = self.debugflag or self.configbool("ui",
480 "report_untrusted")
482 "report_untrusted")
481 self.tracebackflag = self.configbool('ui', 'traceback')
483 self.tracebackflag = self.configbool('ui', 'traceback')
482 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
484 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
483
485
484 if section in (None, 'trusted'):
486 if section in (None, 'trusted'):
485 # update trust information
487 # update trust information
486 self._trustusers.update(self.configlist('trusted', 'users'))
488 self._trustusers.update(self.configlist('trusted', 'users'))
487 self._trustgroups.update(self.configlist('trusted', 'groups'))
489 self._trustgroups.update(self.configlist('trusted', 'groups'))
488
490
489 def backupconfig(self, section, item):
491 def backupconfig(self, section, item):
490 return (self._ocfg.backup(section, item),
492 return (self._ocfg.backup(section, item),
491 self._tcfg.backup(section, item),
493 self._tcfg.backup(section, item),
492 self._ucfg.backup(section, item),)
494 self._ucfg.backup(section, item),)
493 def restoreconfig(self, data):
495 def restoreconfig(self, data):
494 self._ocfg.restore(data[0])
496 self._ocfg.restore(data[0])
495 self._tcfg.restore(data[1])
497 self._tcfg.restore(data[1])
496 self._ucfg.restore(data[2])
498 self._ucfg.restore(data[2])
497
499
498 def setconfig(self, section, name, value, source=''):
500 def setconfig(self, section, name, value, source=''):
499 for cfg in (self._ocfg, self._tcfg, self._ucfg):
501 for cfg in (self._ocfg, self._tcfg, self._ucfg):
500 cfg.set(section, name, value, source)
502 cfg.set(section, name, value, source)
501 self.fixconfig(section=section)
503 self.fixconfig(section=section)
502 self._maybetweakdefaults()
504 self._maybetweakdefaults()
503
505
504 def _data(self, untrusted):
506 def _data(self, untrusted):
505 return untrusted and self._ucfg or self._tcfg
507 return untrusted and self._ucfg or self._tcfg
506
508
507 def configsource(self, section, name, untrusted=False):
509 def configsource(self, section, name, untrusted=False):
508 return self._data(untrusted).source(section, name)
510 return self._data(untrusted).source(section, name)
509
511
510 def config(self, section, name, default=_unset, untrusted=False):
512 def config(self, section, name, default=_unset, untrusted=False):
511 """return the plain string version of a config"""
513 """return the plain string version of a config"""
512 value = self._config(section, name, default=default,
514 value = self._config(section, name, default=default,
513 untrusted=untrusted)
515 untrusted=untrusted)
514 if value is _unset:
516 if value is _unset:
515 return None
517 return None
516 return value
518 return value
517
519
518 def _config(self, section, name, default=_unset, untrusted=False):
520 def _config(self, section, name, default=_unset, untrusted=False):
519 value = itemdefault = default
521 value = itemdefault = default
520 item = self._knownconfig.get(section, {}).get(name)
522 item = self._knownconfig.get(section, {}).get(name)
521 alternates = [(section, name)]
523 alternates = [(section, name)]
522
524
523 if item is not None:
525 if item is not None:
524 alternates.extend(item.alias)
526 alternates.extend(item.alias)
525 if callable(item.default):
527 if callable(item.default):
526 itemdefault = item.default()
528 itemdefault = item.default()
527 else:
529 else:
528 itemdefault = item.default
530 itemdefault = item.default
529 else:
531 else:
530 msg = ("accessing unregistered config item: '%s.%s'")
532 msg = ("accessing unregistered config item: '%s.%s'")
531 msg %= (section, name)
533 msg %= (section, name)
532 self.develwarn(msg, 2, 'warn-config-unknown')
534 self.develwarn(msg, 2, 'warn-config-unknown')
533
535
534 if default is _unset:
536 if default is _unset:
535 if item is None:
537 if item is None:
536 value = default
538 value = default
537 elif item.default is configitems.dynamicdefault:
539 elif item.default is configitems.dynamicdefault:
538 value = None
540 value = None
539 msg = "config item requires an explicit default value: '%s.%s'"
541 msg = "config item requires an explicit default value: '%s.%s'"
540 msg %= (section, name)
542 msg %= (section, name)
541 self.develwarn(msg, 2, 'warn-config-default')
543 self.develwarn(msg, 2, 'warn-config-default')
542 else:
544 else:
543 value = itemdefault
545 value = itemdefault
544 elif (item is not None
546 elif (item is not None
545 and item.default is not configitems.dynamicdefault
547 and item.default is not configitems.dynamicdefault
546 and default != itemdefault):
548 and default != itemdefault):
547 msg = ("specifying a mismatched default value for a registered "
549 msg = ("specifying a mismatched default value for a registered "
548 "config item: '%s.%s' '%s'")
550 "config item: '%s.%s' '%s'")
549 msg %= (section, name, pycompat.bytestr(default))
551 msg %= (section, name, pycompat.bytestr(default))
550 self.develwarn(msg, 2, 'warn-config-default')
552 self.develwarn(msg, 2, 'warn-config-default')
551
553
552 for s, n in alternates:
554 for s, n in alternates:
553 candidate = self._data(untrusted).get(s, n, None)
555 candidate = self._data(untrusted).get(s, n, None)
554 if candidate is not None:
556 if candidate is not None:
555 value = candidate
557 value = candidate
556 section = s
558 section = s
557 name = n
559 name = n
558 break
560 break
559
561
560 if self.debugflag and not untrusted and self._reportuntrusted:
562 if self.debugflag and not untrusted and self._reportuntrusted:
561 for s, n in alternates:
563 for s, n in alternates:
562 uvalue = self._ucfg.get(s, n)
564 uvalue = self._ucfg.get(s, n)
563 if uvalue is not None and uvalue != value:
565 if uvalue is not None and uvalue != value:
564 self.debug("ignoring untrusted configuration option "
566 self.debug("ignoring untrusted configuration option "
565 "%s.%s = %s\n" % (s, n, uvalue))
567 "%s.%s = %s\n" % (s, n, uvalue))
566 return value
568 return value
567
569
568 def configsuboptions(self, section, name, default=_unset, untrusted=False):
570 def configsuboptions(self, section, name, default=_unset, untrusted=False):
569 """Get a config option and all sub-options.
571 """Get a config option and all sub-options.
570
572
571 Some config options have sub-options that are declared with the
573 Some config options have sub-options that are declared with the
572 format "key:opt = value". This method is used to return the main
574 format "key:opt = value". This method is used to return the main
573 option and all its declared sub-options.
575 option and all its declared sub-options.
574
576
575 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
577 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
576 is a dict of defined sub-options where keys and values are strings.
578 is a dict of defined sub-options where keys and values are strings.
577 """
579 """
578 main = self.config(section, name, default, untrusted=untrusted)
580 main = self.config(section, name, default, untrusted=untrusted)
579 data = self._data(untrusted)
581 data = self._data(untrusted)
580 sub = {}
582 sub = {}
581 prefix = '%s:' % name
583 prefix = '%s:' % name
582 for k, v in data.items(section):
584 for k, v in data.items(section):
583 if k.startswith(prefix):
585 if k.startswith(prefix):
584 sub[k[len(prefix):]] = v
586 sub[k[len(prefix):]] = v
585
587
586 if self.debugflag and not untrusted and self._reportuntrusted:
588 if self.debugflag and not untrusted and self._reportuntrusted:
587 for k, v in sub.items():
589 for k, v in sub.items():
588 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
590 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
589 if uvalue is not None and uvalue != v:
591 if uvalue is not None and uvalue != v:
590 self.debug('ignoring untrusted configuration option '
592 self.debug('ignoring untrusted configuration option '
591 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
593 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
592
594
593 return main, sub
595 return main, sub
594
596
595 def configpath(self, section, name, default=_unset, untrusted=False):
597 def configpath(self, section, name, default=_unset, untrusted=False):
596 'get a path config item, expanded relative to repo root or config file'
598 'get a path config item, expanded relative to repo root or config file'
597 v = self.config(section, name, default, untrusted)
599 v = self.config(section, name, default, untrusted)
598 if v is None:
600 if v is None:
599 return None
601 return None
600 if not os.path.isabs(v) or "://" not in v:
602 if not os.path.isabs(v) or "://" not in v:
601 src = self.configsource(section, name, untrusted)
603 src = self.configsource(section, name, untrusted)
602 if ':' in src:
604 if ':' in src:
603 base = os.path.dirname(src.rsplit(':')[0])
605 base = os.path.dirname(src.rsplit(':')[0])
604 v = os.path.join(base, os.path.expanduser(v))
606 v = os.path.join(base, os.path.expanduser(v))
605 return v
607 return v
606
608
607 def configbool(self, section, name, default=_unset, untrusted=False):
609 def configbool(self, section, name, default=_unset, untrusted=False):
608 """parse a configuration element as a boolean
610 """parse a configuration element as a boolean
609
611
610 >>> u = ui(); s = b'foo'
612 >>> u = ui(); s = b'foo'
611 >>> u.setconfig(s, b'true', b'yes')
613 >>> u.setconfig(s, b'true', b'yes')
612 >>> u.configbool(s, b'true')
614 >>> u.configbool(s, b'true')
613 True
615 True
614 >>> u.setconfig(s, b'false', b'no')
616 >>> u.setconfig(s, b'false', b'no')
615 >>> u.configbool(s, b'false')
617 >>> u.configbool(s, b'false')
616 False
618 False
617 >>> u.configbool(s, b'unknown')
619 >>> u.configbool(s, b'unknown')
618 False
620 False
619 >>> u.configbool(s, b'unknown', True)
621 >>> u.configbool(s, b'unknown', True)
620 True
622 True
621 >>> u.setconfig(s, b'invalid', b'somevalue')
623 >>> u.setconfig(s, b'invalid', b'somevalue')
622 >>> u.configbool(s, b'invalid')
624 >>> u.configbool(s, b'invalid')
623 Traceback (most recent call last):
625 Traceback (most recent call last):
624 ...
626 ...
625 ConfigError: foo.invalid is not a boolean ('somevalue')
627 ConfigError: foo.invalid is not a boolean ('somevalue')
626 """
628 """
627
629
628 v = self._config(section, name, default, untrusted=untrusted)
630 v = self._config(section, name, default, untrusted=untrusted)
629 if v is None:
631 if v is None:
630 return v
632 return v
631 if v is _unset:
633 if v is _unset:
632 if default is _unset:
634 if default is _unset:
633 return False
635 return False
634 return default
636 return default
635 if isinstance(v, bool):
637 if isinstance(v, bool):
636 return v
638 return v
637 b = stringutil.parsebool(v)
639 b = stringutil.parsebool(v)
638 if b is None:
640 if b is None:
639 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
641 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
640 % (section, name, v))
642 % (section, name, v))
641 return b
643 return b
642
644
643 def configwith(self, convert, section, name, default=_unset,
645 def configwith(self, convert, section, name, default=_unset,
644 desc=None, untrusted=False):
646 desc=None, untrusted=False):
645 """parse a configuration element with a conversion function
647 """parse a configuration element with a conversion function
646
648
647 >>> u = ui(); s = b'foo'
649 >>> u = ui(); s = b'foo'
648 >>> u.setconfig(s, b'float1', b'42')
650 >>> u.setconfig(s, b'float1', b'42')
649 >>> u.configwith(float, s, b'float1')
651 >>> u.configwith(float, s, b'float1')
650 42.0
652 42.0
651 >>> u.setconfig(s, b'float2', b'-4.25')
653 >>> u.setconfig(s, b'float2', b'-4.25')
652 >>> u.configwith(float, s, b'float2')
654 >>> u.configwith(float, s, b'float2')
653 -4.25
655 -4.25
654 >>> u.configwith(float, s, b'unknown', 7)
656 >>> u.configwith(float, s, b'unknown', 7)
655 7.0
657 7.0
656 >>> u.setconfig(s, b'invalid', b'somevalue')
658 >>> u.setconfig(s, b'invalid', b'somevalue')
657 >>> u.configwith(float, s, b'invalid')
659 >>> u.configwith(float, s, b'invalid')
658 Traceback (most recent call last):
660 Traceback (most recent call last):
659 ...
661 ...
660 ConfigError: foo.invalid is not a valid float ('somevalue')
662 ConfigError: foo.invalid is not a valid float ('somevalue')
661 >>> u.configwith(float, s, b'invalid', desc=b'womble')
663 >>> u.configwith(float, s, b'invalid', desc=b'womble')
662 Traceback (most recent call last):
664 Traceback (most recent call last):
663 ...
665 ...
664 ConfigError: foo.invalid is not a valid womble ('somevalue')
666 ConfigError: foo.invalid is not a valid womble ('somevalue')
665 """
667 """
666
668
667 v = self.config(section, name, default, untrusted)
669 v = self.config(section, name, default, untrusted)
668 if v is None:
670 if v is None:
669 return v # do not attempt to convert None
671 return v # do not attempt to convert None
670 try:
672 try:
671 return convert(v)
673 return convert(v)
672 except (ValueError, error.ParseError):
674 except (ValueError, error.ParseError):
673 if desc is None:
675 if desc is None:
674 desc = pycompat.sysbytes(convert.__name__)
676 desc = pycompat.sysbytes(convert.__name__)
675 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
677 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
676 % (section, name, desc, v))
678 % (section, name, desc, v))
677
679
678 def configint(self, section, name, default=_unset, untrusted=False):
680 def configint(self, section, name, default=_unset, untrusted=False):
679 """parse a configuration element as an integer
681 """parse a configuration element as an integer
680
682
681 >>> u = ui(); s = b'foo'
683 >>> u = ui(); s = b'foo'
682 >>> u.setconfig(s, b'int1', b'42')
684 >>> u.setconfig(s, b'int1', b'42')
683 >>> u.configint(s, b'int1')
685 >>> u.configint(s, b'int1')
684 42
686 42
685 >>> u.setconfig(s, b'int2', b'-42')
687 >>> u.setconfig(s, b'int2', b'-42')
686 >>> u.configint(s, b'int2')
688 >>> u.configint(s, b'int2')
687 -42
689 -42
688 >>> u.configint(s, b'unknown', 7)
690 >>> u.configint(s, b'unknown', 7)
689 7
691 7
690 >>> u.setconfig(s, b'invalid', b'somevalue')
692 >>> u.setconfig(s, b'invalid', b'somevalue')
691 >>> u.configint(s, b'invalid')
693 >>> u.configint(s, b'invalid')
692 Traceback (most recent call last):
694 Traceback (most recent call last):
693 ...
695 ...
694 ConfigError: foo.invalid is not a valid integer ('somevalue')
696 ConfigError: foo.invalid is not a valid integer ('somevalue')
695 """
697 """
696
698
697 return self.configwith(int, section, name, default, 'integer',
699 return self.configwith(int, section, name, default, 'integer',
698 untrusted)
700 untrusted)
699
701
700 def configbytes(self, section, name, default=_unset, untrusted=False):
702 def configbytes(self, section, name, default=_unset, untrusted=False):
701 """parse a configuration element as a quantity in bytes
703 """parse a configuration element as a quantity in bytes
702
704
703 Units can be specified as b (bytes), k or kb (kilobytes), m or
705 Units can be specified as b (bytes), k or kb (kilobytes), m or
704 mb (megabytes), g or gb (gigabytes).
706 mb (megabytes), g or gb (gigabytes).
705
707
706 >>> u = ui(); s = b'foo'
708 >>> u = ui(); s = b'foo'
707 >>> u.setconfig(s, b'val1', b'42')
709 >>> u.setconfig(s, b'val1', b'42')
708 >>> u.configbytes(s, b'val1')
710 >>> u.configbytes(s, b'val1')
709 42
711 42
710 >>> u.setconfig(s, b'val2', b'42.5 kb')
712 >>> u.setconfig(s, b'val2', b'42.5 kb')
711 >>> u.configbytes(s, b'val2')
713 >>> u.configbytes(s, b'val2')
712 43520
714 43520
713 >>> u.configbytes(s, b'unknown', b'7 MB')
715 >>> u.configbytes(s, b'unknown', b'7 MB')
714 7340032
716 7340032
715 >>> u.setconfig(s, b'invalid', b'somevalue')
717 >>> u.setconfig(s, b'invalid', b'somevalue')
716 >>> u.configbytes(s, b'invalid')
718 >>> u.configbytes(s, b'invalid')
717 Traceback (most recent call last):
719 Traceback (most recent call last):
718 ...
720 ...
719 ConfigError: foo.invalid is not a byte quantity ('somevalue')
721 ConfigError: foo.invalid is not a byte quantity ('somevalue')
720 """
722 """
721
723
722 value = self._config(section, name, default, untrusted)
724 value = self._config(section, name, default, untrusted)
723 if value is _unset:
725 if value is _unset:
724 if default is _unset:
726 if default is _unset:
725 default = 0
727 default = 0
726 value = default
728 value = default
727 if not isinstance(value, bytes):
729 if not isinstance(value, bytes):
728 return value
730 return value
729 try:
731 try:
730 return util.sizetoint(value)
732 return util.sizetoint(value)
731 except error.ParseError:
733 except error.ParseError:
732 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
734 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
733 % (section, name, value))
735 % (section, name, value))
734
736
735 def configlist(self, section, name, default=_unset, untrusted=False):
737 def configlist(self, section, name, default=_unset, untrusted=False):
736 """parse a configuration element as a list of comma/space separated
738 """parse a configuration element as a list of comma/space separated
737 strings
739 strings
738
740
739 >>> u = ui(); s = b'foo'
741 >>> u = ui(); s = b'foo'
740 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
742 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
741 >>> u.configlist(s, b'list1')
743 >>> u.configlist(s, b'list1')
742 ['this', 'is', 'a small', 'test']
744 ['this', 'is', 'a small', 'test']
743 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
745 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
744 >>> u.configlist(s, b'list2')
746 >>> u.configlist(s, b'list2')
745 ['this', 'is', 'a small', 'test']
747 ['this', 'is', 'a small', 'test']
746 """
748 """
747 # default is not always a list
749 # default is not always a list
748 v = self.configwith(config.parselist, section, name, default,
750 v = self.configwith(config.parselist, section, name, default,
749 'list', untrusted)
751 'list', untrusted)
750 if isinstance(v, bytes):
752 if isinstance(v, bytes):
751 return config.parselist(v)
753 return config.parselist(v)
752 elif v is None:
754 elif v is None:
753 return []
755 return []
754 return v
756 return v
755
757
756 def configdate(self, section, name, default=_unset, untrusted=False):
758 def configdate(self, section, name, default=_unset, untrusted=False):
757 """parse a configuration element as a tuple of ints
759 """parse a configuration element as a tuple of ints
758
760
759 >>> u = ui(); s = b'foo'
761 >>> u = ui(); s = b'foo'
760 >>> u.setconfig(s, b'date', b'0 0')
762 >>> u.setconfig(s, b'date', b'0 0')
761 >>> u.configdate(s, b'date')
763 >>> u.configdate(s, b'date')
762 (0, 0)
764 (0, 0)
763 """
765 """
764 if self.config(section, name, default, untrusted):
766 if self.config(section, name, default, untrusted):
765 return self.configwith(dateutil.parsedate, section, name, default,
767 return self.configwith(dateutil.parsedate, section, name, default,
766 'date', untrusted)
768 'date', untrusted)
767 if default is _unset:
769 if default is _unset:
768 return None
770 return None
769 return default
771 return default
770
772
771 def hasconfig(self, section, name, untrusted=False):
773 def hasconfig(self, section, name, untrusted=False):
772 return self._data(untrusted).hasitem(section, name)
774 return self._data(untrusted).hasitem(section, name)
773
775
774 def has_section(self, section, untrusted=False):
776 def has_section(self, section, untrusted=False):
775 '''tell whether section exists in config.'''
777 '''tell whether section exists in config.'''
776 return section in self._data(untrusted)
778 return section in self._data(untrusted)
777
779
778 def configitems(self, section, untrusted=False, ignoresub=False):
780 def configitems(self, section, untrusted=False, ignoresub=False):
779 items = self._data(untrusted).items(section)
781 items = self._data(untrusted).items(section)
780 if ignoresub:
782 if ignoresub:
781 items = [i for i in items if ':' not in i[0]]
783 items = [i for i in items if ':' not in i[0]]
782 if self.debugflag and not untrusted and self._reportuntrusted:
784 if self.debugflag and not untrusted and self._reportuntrusted:
783 for k, v in self._ucfg.items(section):
785 for k, v in self._ucfg.items(section):
784 if self._tcfg.get(section, k) != v:
786 if self._tcfg.get(section, k) != v:
785 self.debug("ignoring untrusted configuration option "
787 self.debug("ignoring untrusted configuration option "
786 "%s.%s = %s\n" % (section, k, v))
788 "%s.%s = %s\n" % (section, k, v))
787 return items
789 return items
788
790
789 def walkconfig(self, untrusted=False):
791 def walkconfig(self, untrusted=False):
790 cfg = self._data(untrusted)
792 cfg = self._data(untrusted)
791 for section in cfg.sections():
793 for section in cfg.sections():
792 for name, value in self.configitems(section, untrusted):
794 for name, value in self.configitems(section, untrusted):
793 yield section, name, value
795 yield section, name, value
794
796
795 def plain(self, feature=None):
797 def plain(self, feature=None):
796 '''is plain mode active?
798 '''is plain mode active?
797
799
798 Plain mode means that all configuration variables which affect
800 Plain mode means that all configuration variables which affect
799 the behavior and output of Mercurial should be
801 the behavior and output of Mercurial should be
800 ignored. Additionally, the output should be stable,
802 ignored. Additionally, the output should be stable,
801 reproducible and suitable for use in scripts or applications.
803 reproducible and suitable for use in scripts or applications.
802
804
803 The only way to trigger plain mode is by setting either the
805 The only way to trigger plain mode is by setting either the
804 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
806 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
805
807
806 The return value can either be
808 The return value can either be
807 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
809 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
808 - False if feature is disabled by default and not included in HGPLAIN
810 - False if feature is disabled by default and not included in HGPLAIN
809 - True otherwise
811 - True otherwise
810 '''
812 '''
811 if ('HGPLAIN' not in encoding.environ and
813 if ('HGPLAIN' not in encoding.environ and
812 'HGPLAINEXCEPT' not in encoding.environ):
814 'HGPLAINEXCEPT' not in encoding.environ):
813 return False
815 return False
814 exceptions = encoding.environ.get('HGPLAINEXCEPT',
816 exceptions = encoding.environ.get('HGPLAINEXCEPT',
815 '').strip().split(',')
817 '').strip().split(',')
816 # TODO: add support for HGPLAIN=+feature,-feature syntax
818 # TODO: add support for HGPLAIN=+feature,-feature syntax
817 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
819 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
818 exceptions.append('strictflags')
820 exceptions.append('strictflags')
819 if feature and exceptions:
821 if feature and exceptions:
820 return feature not in exceptions
822 return feature not in exceptions
821 return True
823 return True
822
824
823 def username(self, acceptempty=False):
825 def username(self, acceptempty=False):
824 """Return default username to be used in commits.
826 """Return default username to be used in commits.
825
827
826 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
828 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
827 and stop searching if one of these is set.
829 and stop searching if one of these is set.
828 If not found and acceptempty is True, returns None.
830 If not found and acceptempty is True, returns None.
829 If not found and ui.askusername is True, ask the user, else use
831 If not found and ui.askusername is True, ask the user, else use
830 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
832 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
831 If no username could be found, raise an Abort error.
833 If no username could be found, raise an Abort error.
832 """
834 """
833 user = encoding.environ.get("HGUSER")
835 user = encoding.environ.get("HGUSER")
834 if user is None:
836 if user is None:
835 user = self.config("ui", "username")
837 user = self.config("ui", "username")
836 if user is not None:
838 if user is not None:
837 user = os.path.expandvars(user)
839 user = os.path.expandvars(user)
838 if user is None:
840 if user is None:
839 user = encoding.environ.get("EMAIL")
841 user = encoding.environ.get("EMAIL")
840 if user is None and acceptempty:
842 if user is None and acceptempty:
841 return user
843 return user
842 if user is None and self.configbool("ui", "askusername"):
844 if user is None and self.configbool("ui", "askusername"):
843 user = self.prompt(_("enter a commit username:"), default=None)
845 user = self.prompt(_("enter a commit username:"), default=None)
844 if user is None and not self.interactive():
846 if user is None and not self.interactive():
845 try:
847 try:
846 user = '%s@%s' % (procutil.getuser(),
848 user = '%s@%s' % (procutil.getuser(),
847 encoding.strtolocal(socket.getfqdn()))
849 encoding.strtolocal(socket.getfqdn()))
848 self.warn(_("no username found, using '%s' instead\n") % user)
850 self.warn(_("no username found, using '%s' instead\n") % user)
849 except KeyError:
851 except KeyError:
850 pass
852 pass
851 if not user:
853 if not user:
852 raise error.Abort(_('no username supplied'),
854 raise error.Abort(_('no username supplied'),
853 hint=_("use 'hg config --edit' "
855 hint=_("use 'hg config --edit' "
854 'to set your username'))
856 'to set your username'))
855 if "\n" in user:
857 if "\n" in user:
856 raise error.Abort(_("username %r contains a newline\n")
858 raise error.Abort(_("username %r contains a newline\n")
857 % pycompat.bytestr(user))
859 % pycompat.bytestr(user))
858 return user
860 return user
859
861
860 def shortuser(self, user):
862 def shortuser(self, user):
861 """Return a short representation of a user name or email address."""
863 """Return a short representation of a user name or email address."""
862 if not self.verbose:
864 if not self.verbose:
863 user = stringutil.shortuser(user)
865 user = stringutil.shortuser(user)
864 return user
866 return user
865
867
866 def expandpath(self, loc, default=None):
868 def expandpath(self, loc, default=None):
867 """Return repository location relative to cwd or from [paths]"""
869 """Return repository location relative to cwd or from [paths]"""
868 try:
870 try:
869 p = self.paths.getpath(loc)
871 p = self.paths.getpath(loc)
870 if p:
872 if p:
871 return p.rawloc
873 return p.rawloc
872 except error.RepoError:
874 except error.RepoError:
873 pass
875 pass
874
876
875 if default:
877 if default:
876 try:
878 try:
877 p = self.paths.getpath(default)
879 p = self.paths.getpath(default)
878 if p:
880 if p:
879 return p.rawloc
881 return p.rawloc
880 except error.RepoError:
882 except error.RepoError:
881 pass
883 pass
882
884
883 return loc
885 return loc
884
886
885 @util.propertycache
887 @util.propertycache
886 def paths(self):
888 def paths(self):
887 return paths(self)
889 return paths(self)
888
890
889 @property
891 @property
890 def fout(self):
892 def fout(self):
891 return self._fout
893 return self._fout
892
894
893 @fout.setter
895 @fout.setter
894 def fout(self, f):
896 def fout(self, f):
895 self._fout = f
897 self._fout = f
896 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
898 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
897
899
898 @property
900 @property
899 def ferr(self):
901 def ferr(self):
900 return self._ferr
902 return self._ferr
901
903
902 @ferr.setter
904 @ferr.setter
903 def ferr(self, f):
905 def ferr(self, f):
904 self._ferr = f
906 self._ferr = f
905 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
907 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
906
908
907 @property
909 @property
908 def fin(self):
910 def fin(self):
909 return self._fin
911 return self._fin
910
912
911 @fin.setter
913 @fin.setter
912 def fin(self, f):
914 def fin(self, f):
913 self._fin = f
915 self._fin = f
914
916
917 @property
918 def fmsg(self):
919 """Stream dedicated for status/error messages; may be None if
920 fout/ferr are used"""
921 return self._fmsg
922
923 @fmsg.setter
924 def fmsg(self, f):
925 self._fmsg = f
926 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
927
915 def pushbuffer(self, error=False, subproc=False, labeled=False):
928 def pushbuffer(self, error=False, subproc=False, labeled=False):
916 """install a buffer to capture standard output of the ui object
929 """install a buffer to capture standard output of the ui object
917
930
918 If error is True, the error output will be captured too.
931 If error is True, the error output will be captured too.
919
932
920 If subproc is True, output from subprocesses (typically hooks) will be
933 If subproc is True, output from subprocesses (typically hooks) will be
921 captured too.
934 captured too.
922
935
923 If labeled is True, any labels associated with buffered
936 If labeled is True, any labels associated with buffered
924 output will be handled. By default, this has no effect
937 output will be handled. By default, this has no effect
925 on the output returned, but extensions and GUI tools may
938 on the output returned, but extensions and GUI tools may
926 handle this argument and returned styled output. If output
939 handle this argument and returned styled output. If output
927 is being buffered so it can be captured and parsed or
940 is being buffered so it can be captured and parsed or
928 processed, labeled should not be set to True.
941 processed, labeled should not be set to True.
929 """
942 """
930 self._buffers.append([])
943 self._buffers.append([])
931 self._bufferstates.append((error, subproc, labeled))
944 self._bufferstates.append((error, subproc, labeled))
932 self._bufferapplylabels = labeled
945 self._bufferapplylabels = labeled
933
946
934 def popbuffer(self):
947 def popbuffer(self):
935 '''pop the last buffer and return the buffered output'''
948 '''pop the last buffer and return the buffered output'''
936 self._bufferstates.pop()
949 self._bufferstates.pop()
937 if self._bufferstates:
950 if self._bufferstates:
938 self._bufferapplylabels = self._bufferstates[-1][2]
951 self._bufferapplylabels = self._bufferstates[-1][2]
939 else:
952 else:
940 self._bufferapplylabels = None
953 self._bufferapplylabels = None
941
954
942 return "".join(self._buffers.pop())
955 return "".join(self._buffers.pop())
943
956
944 def _isbuffered(self, dest):
957 def _isbuffered(self, dest):
945 if dest is self._fout:
958 if dest is self._fout:
946 return bool(self._buffers)
959 return bool(self._buffers)
947 if dest is self._ferr:
960 if dest is self._ferr:
948 return bool(self._bufferstates and self._bufferstates[-1][0])
961 return bool(self._bufferstates and self._bufferstates[-1][0])
949 return False
962 return False
950
963
951 def canwritewithoutlabels(self):
964 def canwritewithoutlabels(self):
952 '''check if write skips the label'''
965 '''check if write skips the label'''
953 if self._buffers and not self._bufferapplylabels:
966 if self._buffers and not self._bufferapplylabels:
954 return True
967 return True
955 return self._colormode is None
968 return self._colormode is None
956
969
957 def canbatchlabeledwrites(self):
970 def canbatchlabeledwrites(self):
958 '''check if write calls with labels are batchable'''
971 '''check if write calls with labels are batchable'''
959 # Windows color printing is special, see ``write``.
972 # Windows color printing is special, see ``write``.
960 return self._colormode != 'win32'
973 return self._colormode != 'win32'
961
974
962 def write(self, *args, **opts):
975 def write(self, *args, **opts):
963 '''write args to output
976 '''write args to output
964
977
965 By default, this method simply writes to the buffer or stdout.
978 By default, this method simply writes to the buffer or stdout.
966 Color mode can be set on the UI class to have the output decorated
979 Color mode can be set on the UI class to have the output decorated
967 with color modifier before being written to stdout.
980 with color modifier before being written to stdout.
968
981
969 The color used is controlled by an optional keyword argument, "label".
982 The color used is controlled by an optional keyword argument, "label".
970 This should be a string containing label names separated by space.
983 This should be a string containing label names separated by space.
971 Label names take the form of "topic.type". For example, ui.debug()
984 Label names take the form of "topic.type". For example, ui.debug()
972 issues a label of "ui.debug".
985 issues a label of "ui.debug".
973
986
974 When labeling output for a specific command, a label of
987 When labeling output for a specific command, a label of
975 "cmdname.type" is recommended. For example, status issues
988 "cmdname.type" is recommended. For example, status issues
976 a label of "status.modified" for modified files.
989 a label of "status.modified" for modified files.
977 '''
990 '''
978 self._write(self._fout, *args, **opts)
991 self._write(self._fout, *args, **opts)
979
992
980 def write_err(self, *args, **opts):
993 def write_err(self, *args, **opts):
981 self._write(self._ferr, *args, **opts)
994 self._write(self._ferr, *args, **opts)
982
995
983 def _write(self, dest, *args, **opts):
996 def _write(self, dest, *args, **opts):
984 if self._isbuffered(dest):
997 if self._isbuffered(dest):
985 if self._bufferapplylabels:
998 if self._bufferapplylabels:
986 label = opts.get(r'label', '')
999 label = opts.get(r'label', '')
987 self._buffers[-1].extend(self.label(a, label) for a in args)
1000 self._buffers[-1].extend(self.label(a, label) for a in args)
988 else:
1001 else:
989 self._buffers[-1].extend(args)
1002 self._buffers[-1].extend(args)
990 else:
1003 else:
991 self._writenobuf(dest, *args, **opts)
1004 self._writenobuf(dest, *args, **opts)
992
1005
993 def _writenobuf(self, dest, *args, **opts):
1006 def _writenobuf(self, dest, *args, **opts):
994 self._progclear()
1007 self._progclear()
995 msg = b''.join(args)
1008 msg = b''.join(args)
996
1009
997 # opencode timeblockedsection because this is a critical path
1010 # opencode timeblockedsection because this is a critical path
998 starttime = util.timer()
1011 starttime = util.timer()
999 try:
1012 try:
1000 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1013 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1001 self._fout.flush()
1014 self._fout.flush()
1002 if self._colormode == 'win32':
1015 if self._colormode == 'win32':
1003 # windows color printing is its own can of crab, defer to
1016 # windows color printing is its own can of crab, defer to
1004 # the color module and that is it.
1017 # the color module and that is it.
1005 color.win32print(self, dest.write, msg, **opts)
1018 color.win32print(self, dest.write, msg, **opts)
1006 else:
1019 else:
1007 if self._colormode is not None:
1020 if self._colormode is not None:
1008 label = opts.get(r'label', '')
1021 label = opts.get(r'label', '')
1009 msg = self.label(msg, label)
1022 msg = self.label(msg, label)
1010 dest.write(msg)
1023 dest.write(msg)
1011 # stderr may be buffered under win32 when redirected to files,
1024 # stderr may be buffered under win32 when redirected to files,
1012 # including stdout.
1025 # including stdout.
1013 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1026 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1014 dest.flush()
1027 dest.flush()
1015 except IOError as err:
1028 except IOError as err:
1016 if (dest is self._ferr
1029 if (dest is self._ferr
1017 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1030 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1018 # no way to report the error, so ignore it
1031 # no way to report the error, so ignore it
1019 return
1032 return
1020 raise error.StdioError(err)
1033 raise error.StdioError(err)
1021 finally:
1034 finally:
1022 self._blockedtimes['stdio_blocked'] += \
1035 self._blockedtimes['stdio_blocked'] += \
1023 (util.timer() - starttime) * 1000
1036 (util.timer() - starttime) * 1000
1024
1037
1025 def flush(self):
1038 def flush(self):
1026 # opencode timeblockedsection because this is a critical path
1039 # opencode timeblockedsection because this is a critical path
1027 starttime = util.timer()
1040 starttime = util.timer()
1028 try:
1041 try:
1029 try:
1042 try:
1030 self._fout.flush()
1043 self._fout.flush()
1031 except IOError as err:
1044 except IOError as err:
1032 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1045 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1033 raise error.StdioError(err)
1046 raise error.StdioError(err)
1034 finally:
1047 finally:
1035 try:
1048 try:
1036 self._ferr.flush()
1049 self._ferr.flush()
1037 except IOError as err:
1050 except IOError as err:
1038 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1051 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1039 raise error.StdioError(err)
1052 raise error.StdioError(err)
1040 finally:
1053 finally:
1041 self._blockedtimes['stdio_blocked'] += \
1054 self._blockedtimes['stdio_blocked'] += \
1042 (util.timer() - starttime) * 1000
1055 (util.timer() - starttime) * 1000
1043
1056
1044 def _isatty(self, fh):
1057 def _isatty(self, fh):
1045 if self.configbool('ui', 'nontty'):
1058 if self.configbool('ui', 'nontty'):
1046 return False
1059 return False
1047 return procutil.isatty(fh)
1060 return procutil.isatty(fh)
1048
1061
1049 def disablepager(self):
1062 def disablepager(self):
1050 self._disablepager = True
1063 self._disablepager = True
1051
1064
1052 def pager(self, command):
1065 def pager(self, command):
1053 """Start a pager for subsequent command output.
1066 """Start a pager for subsequent command output.
1054
1067
1055 Commands which produce a long stream of output should call
1068 Commands which produce a long stream of output should call
1056 this function to activate the user's preferred pagination
1069 this function to activate the user's preferred pagination
1057 mechanism (which may be no pager). Calling this function
1070 mechanism (which may be no pager). Calling this function
1058 precludes any future use of interactive functionality, such as
1071 precludes any future use of interactive functionality, such as
1059 prompting the user or activating curses.
1072 prompting the user or activating curses.
1060
1073
1061 Args:
1074 Args:
1062 command: The full, non-aliased name of the command. That is, "log"
1075 command: The full, non-aliased name of the command. That is, "log"
1063 not "history, "summary" not "summ", etc.
1076 not "history, "summary" not "summ", etc.
1064 """
1077 """
1065 if (self._disablepager
1078 if (self._disablepager
1066 or self.pageractive):
1079 or self.pageractive):
1067 # how pager should do is already determined
1080 # how pager should do is already determined
1068 return
1081 return
1069
1082
1070 if not command.startswith('internal-always-') and (
1083 if not command.startswith('internal-always-') and (
1071 # explicit --pager=on (= 'internal-always-' prefix) should
1084 # explicit --pager=on (= 'internal-always-' prefix) should
1072 # take precedence over disabling factors below
1085 # take precedence over disabling factors below
1073 command in self.configlist('pager', 'ignore')
1086 command in self.configlist('pager', 'ignore')
1074 or not self.configbool('ui', 'paginate')
1087 or not self.configbool('ui', 'paginate')
1075 or not self.configbool('pager', 'attend-' + command, True)
1088 or not self.configbool('pager', 'attend-' + command, True)
1076 or encoding.environ.get('TERM') == 'dumb'
1089 or encoding.environ.get('TERM') == 'dumb'
1077 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1090 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1078 # formatted() will need some adjustment.
1091 # formatted() will need some adjustment.
1079 or not self.formatted()
1092 or not self.formatted()
1080 or self.plain()
1093 or self.plain()
1081 or self._buffers
1094 or self._buffers
1082 # TODO: expose debugger-enabled on the UI object
1095 # TODO: expose debugger-enabled on the UI object
1083 or '--debugger' in pycompat.sysargv):
1096 or '--debugger' in pycompat.sysargv):
1084 # We only want to paginate if the ui appears to be
1097 # We only want to paginate if the ui appears to be
1085 # interactive, the user didn't say HGPLAIN or
1098 # interactive, the user didn't say HGPLAIN or
1086 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1099 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1087 return
1100 return
1088
1101
1089 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1102 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1090 if not pagercmd:
1103 if not pagercmd:
1091 return
1104 return
1092
1105
1093 pagerenv = {}
1106 pagerenv = {}
1094 for name, value in rcutil.defaultpagerenv().items():
1107 for name, value in rcutil.defaultpagerenv().items():
1095 if name not in encoding.environ:
1108 if name not in encoding.environ:
1096 pagerenv[name] = value
1109 pagerenv[name] = value
1097
1110
1098 self.debug('starting pager for command %s\n' %
1111 self.debug('starting pager for command %s\n' %
1099 stringutil.pprint(command))
1112 stringutil.pprint(command))
1100 self.flush()
1113 self.flush()
1101
1114
1102 wasformatted = self.formatted()
1115 wasformatted = self.formatted()
1103 if util.safehasattr(signal, "SIGPIPE"):
1116 if util.safehasattr(signal, "SIGPIPE"):
1104 signal.signal(signal.SIGPIPE, _catchterm)
1117 signal.signal(signal.SIGPIPE, _catchterm)
1105 if self._runpager(pagercmd, pagerenv):
1118 if self._runpager(pagercmd, pagerenv):
1106 self.pageractive = True
1119 self.pageractive = True
1107 # Preserve the formatted-ness of the UI. This is important
1120 # Preserve the formatted-ness of the UI. This is important
1108 # because we mess with stdout, which might confuse
1121 # because we mess with stdout, which might confuse
1109 # auto-detection of things being formatted.
1122 # auto-detection of things being formatted.
1110 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1123 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1111 self.setconfig('ui', 'interactive', False, 'pager')
1124 self.setconfig('ui', 'interactive', False, 'pager')
1112
1125
1113 # If pagermode differs from color.mode, reconfigure color now that
1126 # If pagermode differs from color.mode, reconfigure color now that
1114 # pageractive is set.
1127 # pageractive is set.
1115 cm = self._colormode
1128 cm = self._colormode
1116 if cm != self.config('color', 'pagermode', cm):
1129 if cm != self.config('color', 'pagermode', cm):
1117 color.setup(self)
1130 color.setup(self)
1118 else:
1131 else:
1119 # If the pager can't be spawned in dispatch when --pager=on is
1132 # If the pager can't be spawned in dispatch when --pager=on is
1120 # given, don't try again when the command runs, to avoid a duplicate
1133 # given, don't try again when the command runs, to avoid a duplicate
1121 # warning about a missing pager command.
1134 # warning about a missing pager command.
1122 self.disablepager()
1135 self.disablepager()
1123
1136
1124 def _runpager(self, command, env=None):
1137 def _runpager(self, command, env=None):
1125 """Actually start the pager and set up file descriptors.
1138 """Actually start the pager and set up file descriptors.
1126
1139
1127 This is separate in part so that extensions (like chg) can
1140 This is separate in part so that extensions (like chg) can
1128 override how a pager is invoked.
1141 override how a pager is invoked.
1129 """
1142 """
1130 if command == 'cat':
1143 if command == 'cat':
1131 # Save ourselves some work.
1144 # Save ourselves some work.
1132 return False
1145 return False
1133 # If the command doesn't contain any of these characters, we
1146 # If the command doesn't contain any of these characters, we
1134 # assume it's a binary and exec it directly. This means for
1147 # assume it's a binary and exec it directly. This means for
1135 # simple pager command configurations, we can degrade
1148 # simple pager command configurations, we can degrade
1136 # gracefully and tell the user about their broken pager.
1149 # gracefully and tell the user about their broken pager.
1137 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1150 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1138
1151
1139 if pycompat.iswindows and not shell:
1152 if pycompat.iswindows and not shell:
1140 # Window's built-in `more` cannot be invoked with shell=False, but
1153 # Window's built-in `more` cannot be invoked with shell=False, but
1141 # its `more.com` can. Hide this implementation detail from the
1154 # its `more.com` can. Hide this implementation detail from the
1142 # user so we can also get sane bad PAGER behavior. MSYS has
1155 # user so we can also get sane bad PAGER behavior. MSYS has
1143 # `more.exe`, so do a cmd.exe style resolution of the executable to
1156 # `more.exe`, so do a cmd.exe style resolution of the executable to
1144 # determine which one to use.
1157 # determine which one to use.
1145 fullcmd = procutil.findexe(command)
1158 fullcmd = procutil.findexe(command)
1146 if not fullcmd:
1159 if not fullcmd:
1147 self.warn(_("missing pager command '%s', skipping pager\n")
1160 self.warn(_("missing pager command '%s', skipping pager\n")
1148 % command)
1161 % command)
1149 return False
1162 return False
1150
1163
1151 command = fullcmd
1164 command = fullcmd
1152
1165
1153 try:
1166 try:
1154 pager = subprocess.Popen(
1167 pager = subprocess.Popen(
1155 procutil.tonativestr(command), shell=shell, bufsize=-1,
1168 procutil.tonativestr(command), shell=shell, bufsize=-1,
1156 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1169 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1157 stdout=procutil.stdout, stderr=procutil.stderr,
1170 stdout=procutil.stdout, stderr=procutil.stderr,
1158 env=procutil.tonativeenv(procutil.shellenviron(env)))
1171 env=procutil.tonativeenv(procutil.shellenviron(env)))
1159 except OSError as e:
1172 except OSError as e:
1160 if e.errno == errno.ENOENT and not shell:
1173 if e.errno == errno.ENOENT and not shell:
1161 self.warn(_("missing pager command '%s', skipping pager\n")
1174 self.warn(_("missing pager command '%s', skipping pager\n")
1162 % command)
1175 % command)
1163 return False
1176 return False
1164 raise
1177 raise
1165
1178
1166 # back up original file descriptors
1179 # back up original file descriptors
1167 stdoutfd = os.dup(procutil.stdout.fileno())
1180 stdoutfd = os.dup(procutil.stdout.fileno())
1168 stderrfd = os.dup(procutil.stderr.fileno())
1181 stderrfd = os.dup(procutil.stderr.fileno())
1169
1182
1170 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1183 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1171 if self._isatty(procutil.stderr):
1184 if self._isatty(procutil.stderr):
1172 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1185 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1173
1186
1174 @self.atexit
1187 @self.atexit
1175 def killpager():
1188 def killpager():
1176 if util.safehasattr(signal, "SIGINT"):
1189 if util.safehasattr(signal, "SIGINT"):
1177 signal.signal(signal.SIGINT, signal.SIG_IGN)
1190 signal.signal(signal.SIGINT, signal.SIG_IGN)
1178 # restore original fds, closing pager.stdin copies in the process
1191 # restore original fds, closing pager.stdin copies in the process
1179 os.dup2(stdoutfd, procutil.stdout.fileno())
1192 os.dup2(stdoutfd, procutil.stdout.fileno())
1180 os.dup2(stderrfd, procutil.stderr.fileno())
1193 os.dup2(stderrfd, procutil.stderr.fileno())
1181 pager.stdin.close()
1194 pager.stdin.close()
1182 pager.wait()
1195 pager.wait()
1183
1196
1184 return True
1197 return True
1185
1198
1186 @property
1199 @property
1187 def _exithandlers(self):
1200 def _exithandlers(self):
1188 return _reqexithandlers
1201 return _reqexithandlers
1189
1202
1190 def atexit(self, func, *args, **kwargs):
1203 def atexit(self, func, *args, **kwargs):
1191 '''register a function to run after dispatching a request
1204 '''register a function to run after dispatching a request
1192
1205
1193 Handlers do not stay registered across request boundaries.'''
1206 Handlers do not stay registered across request boundaries.'''
1194 self._exithandlers.append((func, args, kwargs))
1207 self._exithandlers.append((func, args, kwargs))
1195 return func
1208 return func
1196
1209
1197 def interface(self, feature):
1210 def interface(self, feature):
1198 """what interface to use for interactive console features?
1211 """what interface to use for interactive console features?
1199
1212
1200 The interface is controlled by the value of `ui.interface` but also by
1213 The interface is controlled by the value of `ui.interface` but also by
1201 the value of feature-specific configuration. For example:
1214 the value of feature-specific configuration. For example:
1202
1215
1203 ui.interface.histedit = text
1216 ui.interface.histedit = text
1204 ui.interface.chunkselector = curses
1217 ui.interface.chunkselector = curses
1205
1218
1206 Here the features are "histedit" and "chunkselector".
1219 Here the features are "histedit" and "chunkselector".
1207
1220
1208 The configuration above means that the default interfaces for commands
1221 The configuration above means that the default interfaces for commands
1209 is curses, the interface for histedit is text and the interface for
1222 is curses, the interface for histedit is text and the interface for
1210 selecting chunk is crecord (the best curses interface available).
1223 selecting chunk is crecord (the best curses interface available).
1211
1224
1212 Consider the following example:
1225 Consider the following example:
1213 ui.interface = curses
1226 ui.interface = curses
1214 ui.interface.histedit = text
1227 ui.interface.histedit = text
1215
1228
1216 Then histedit will use the text interface and chunkselector will use
1229 Then histedit will use the text interface and chunkselector will use
1217 the default curses interface (crecord at the moment).
1230 the default curses interface (crecord at the moment).
1218 """
1231 """
1219 alldefaults = frozenset(["text", "curses"])
1232 alldefaults = frozenset(["text", "curses"])
1220
1233
1221 featureinterfaces = {
1234 featureinterfaces = {
1222 "chunkselector": [
1235 "chunkselector": [
1223 "text",
1236 "text",
1224 "curses",
1237 "curses",
1225 ]
1238 ]
1226 }
1239 }
1227
1240
1228 # Feature-specific interface
1241 # Feature-specific interface
1229 if feature not in featureinterfaces.keys():
1242 if feature not in featureinterfaces.keys():
1230 # Programming error, not user error
1243 # Programming error, not user error
1231 raise ValueError("Unknown feature requested %s" % feature)
1244 raise ValueError("Unknown feature requested %s" % feature)
1232
1245
1233 availableinterfaces = frozenset(featureinterfaces[feature])
1246 availableinterfaces = frozenset(featureinterfaces[feature])
1234 if alldefaults > availableinterfaces:
1247 if alldefaults > availableinterfaces:
1235 # Programming error, not user error. We need a use case to
1248 # Programming error, not user error. We need a use case to
1236 # define the right thing to do here.
1249 # define the right thing to do here.
1237 raise ValueError(
1250 raise ValueError(
1238 "Feature %s does not handle all default interfaces" %
1251 "Feature %s does not handle all default interfaces" %
1239 feature)
1252 feature)
1240
1253
1241 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1254 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1242 return "text"
1255 return "text"
1243
1256
1244 # Default interface for all the features
1257 # Default interface for all the features
1245 defaultinterface = "text"
1258 defaultinterface = "text"
1246 i = self.config("ui", "interface")
1259 i = self.config("ui", "interface")
1247 if i in alldefaults:
1260 if i in alldefaults:
1248 defaultinterface = i
1261 defaultinterface = i
1249
1262
1250 choseninterface = defaultinterface
1263 choseninterface = defaultinterface
1251 f = self.config("ui", "interface.%s" % feature)
1264 f = self.config("ui", "interface.%s" % feature)
1252 if f in availableinterfaces:
1265 if f in availableinterfaces:
1253 choseninterface = f
1266 choseninterface = f
1254
1267
1255 if i is not None and defaultinterface != i:
1268 if i is not None and defaultinterface != i:
1256 if f is not None:
1269 if f is not None:
1257 self.warn(_("invalid value for ui.interface: %s\n") %
1270 self.warn(_("invalid value for ui.interface: %s\n") %
1258 (i,))
1271 (i,))
1259 else:
1272 else:
1260 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1273 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1261 (i, choseninterface))
1274 (i, choseninterface))
1262 if f is not None and choseninterface != f:
1275 if f is not None and choseninterface != f:
1263 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1276 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1264 (feature, f, choseninterface))
1277 (feature, f, choseninterface))
1265
1278
1266 return choseninterface
1279 return choseninterface
1267
1280
1268 def interactive(self):
1281 def interactive(self):
1269 '''is interactive input allowed?
1282 '''is interactive input allowed?
1270
1283
1271 An interactive session is a session where input can be reasonably read
1284 An interactive session is a session where input can be reasonably read
1272 from `sys.stdin'. If this function returns false, any attempt to read
1285 from `sys.stdin'. If this function returns false, any attempt to read
1273 from stdin should fail with an error, unless a sensible default has been
1286 from stdin should fail with an error, unless a sensible default has been
1274 specified.
1287 specified.
1275
1288
1276 Interactiveness is triggered by the value of the `ui.interactive'
1289 Interactiveness is triggered by the value of the `ui.interactive'
1277 configuration variable or - if it is unset - when `sys.stdin' points
1290 configuration variable or - if it is unset - when `sys.stdin' points
1278 to a terminal device.
1291 to a terminal device.
1279
1292
1280 This function refers to input only; for output, see `ui.formatted()'.
1293 This function refers to input only; for output, see `ui.formatted()'.
1281 '''
1294 '''
1282 i = self.configbool("ui", "interactive")
1295 i = self.configbool("ui", "interactive")
1283 if i is None:
1296 if i is None:
1284 # some environments replace stdin without implementing isatty
1297 # some environments replace stdin without implementing isatty
1285 # usually those are non-interactive
1298 # usually those are non-interactive
1286 return self._isatty(self._fin)
1299 return self._isatty(self._fin)
1287
1300
1288 return i
1301 return i
1289
1302
1290 def termwidth(self):
1303 def termwidth(self):
1291 '''how wide is the terminal in columns?
1304 '''how wide is the terminal in columns?
1292 '''
1305 '''
1293 if 'COLUMNS' in encoding.environ:
1306 if 'COLUMNS' in encoding.environ:
1294 try:
1307 try:
1295 return int(encoding.environ['COLUMNS'])
1308 return int(encoding.environ['COLUMNS'])
1296 except ValueError:
1309 except ValueError:
1297 pass
1310 pass
1298 return scmutil.termsize(self)[0]
1311 return scmutil.termsize(self)[0]
1299
1312
1300 def formatted(self):
1313 def formatted(self):
1301 '''should formatted output be used?
1314 '''should formatted output be used?
1302
1315
1303 It is often desirable to format the output to suite the output medium.
1316 It is often desirable to format the output to suite the output medium.
1304 Examples of this are truncating long lines or colorizing messages.
1317 Examples of this are truncating long lines or colorizing messages.
1305 However, this is not often not desirable when piping output into other
1318 However, this is not often not desirable when piping output into other
1306 utilities, e.g. `grep'.
1319 utilities, e.g. `grep'.
1307
1320
1308 Formatted output is triggered by the value of the `ui.formatted'
1321 Formatted output is triggered by the value of the `ui.formatted'
1309 configuration variable or - if it is unset - when `sys.stdout' points
1322 configuration variable or - if it is unset - when `sys.stdout' points
1310 to a terminal device. Please note that `ui.formatted' should be
1323 to a terminal device. Please note that `ui.formatted' should be
1311 considered an implementation detail; it is not intended for use outside
1324 considered an implementation detail; it is not intended for use outside
1312 Mercurial or its extensions.
1325 Mercurial or its extensions.
1313
1326
1314 This function refers to output only; for input, see `ui.interactive()'.
1327 This function refers to output only; for input, see `ui.interactive()'.
1315 This function always returns false when in plain mode, see `ui.plain()'.
1328 This function always returns false when in plain mode, see `ui.plain()'.
1316 '''
1329 '''
1317 if self.plain():
1330 if self.plain():
1318 return False
1331 return False
1319
1332
1320 i = self.configbool("ui", "formatted")
1333 i = self.configbool("ui", "formatted")
1321 if i is None:
1334 if i is None:
1322 # some environments replace stdout without implementing isatty
1335 # some environments replace stdout without implementing isatty
1323 # usually those are non-interactive
1336 # usually those are non-interactive
1324 return self._isatty(self._fout)
1337 return self._isatty(self._fout)
1325
1338
1326 return i
1339 return i
1327
1340
1328 def _readline(self):
1341 def _readline(self):
1329 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1342 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1330 # because they have to be text streams with *no buffering*. Instead,
1343 # because they have to be text streams with *no buffering*. Instead,
1331 # we use rawinput() only if call_readline() will be invoked by
1344 # we use rawinput() only if call_readline() will be invoked by
1332 # PyOS_Readline(), so no I/O will be made at Python layer.
1345 # PyOS_Readline(), so no I/O will be made at Python layer.
1333 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1346 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1334 and procutil.isstdin(self._fin)
1347 and procutil.isstdin(self._fin)
1335 and procutil.isstdout(self._fout))
1348 and procutil.isstdout(self._fout))
1336 if usereadline:
1349 if usereadline:
1337 try:
1350 try:
1338 # magically add command line editing support, where
1351 # magically add command line editing support, where
1339 # available
1352 # available
1340 import readline
1353 import readline
1341 # force demandimport to really load the module
1354 # force demandimport to really load the module
1342 readline.read_history_file
1355 readline.read_history_file
1343 # windows sometimes raises something other than ImportError
1356 # windows sometimes raises something other than ImportError
1344 except Exception:
1357 except Exception:
1345 usereadline = False
1358 usereadline = False
1346
1359
1347 # prompt ' ' must exist; otherwise readline may delete entire line
1360 # prompt ' ' must exist; otherwise readline may delete entire line
1348 # - http://bugs.python.org/issue12833
1361 # - http://bugs.python.org/issue12833
1349 with self.timeblockedsection('stdio'):
1362 with self.timeblockedsection('stdio'):
1350 if usereadline:
1363 if usereadline:
1351 line = encoding.strtolocal(pycompat.rawinput(r' '))
1364 line = encoding.strtolocal(pycompat.rawinput(r' '))
1352 # When stdin is in binary mode on Windows, it can cause
1365 # When stdin is in binary mode on Windows, it can cause
1353 # raw_input() to emit an extra trailing carriage return
1366 # raw_input() to emit an extra trailing carriage return
1354 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1367 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1355 line = line[:-1]
1368 line = line[:-1]
1356 else:
1369 else:
1357 self._fout.write(b' ')
1370 self._fout.write(b' ')
1358 self._fout.flush()
1371 self._fout.flush()
1359 line = self._fin.readline()
1372 line = self._fin.readline()
1360 if not line:
1373 if not line:
1361 raise EOFError
1374 raise EOFError
1362 line = line.rstrip(pycompat.oslinesep)
1375 line = line.rstrip(pycompat.oslinesep)
1363
1376
1364 return line
1377 return line
1365
1378
1366 def prompt(self, msg, default="y"):
1379 def prompt(self, msg, default="y"):
1367 """Prompt user with msg, read response.
1380 """Prompt user with msg, read response.
1368 If ui is not interactive, the default is returned.
1381 If ui is not interactive, the default is returned.
1369 """
1382 """
1370 if not self.interactive():
1383 if not self.interactive():
1371 self._write(self._fmsgout, msg, ' ', label='ui.prompt')
1384 self._write(self._fmsgout, msg, ' ', label='ui.prompt')
1372 self._write(self._fmsgout, default or '', "\n",
1385 self._write(self._fmsgout, default or '', "\n",
1373 label='ui.promptecho')
1386 label='ui.promptecho')
1374 return default
1387 return default
1375 self._writenobuf(self._fmsgout, msg, label='ui.prompt')
1388 self._writenobuf(self._fmsgout, msg, label='ui.prompt')
1376 self.flush()
1389 self.flush()
1377 try:
1390 try:
1378 r = self._readline()
1391 r = self._readline()
1379 if not r:
1392 if not r:
1380 r = default
1393 r = default
1381 if self.configbool('ui', 'promptecho'):
1394 if self.configbool('ui', 'promptecho'):
1382 self._write(self._fmsgout, r, "\n", label='ui.promptecho')
1395 self._write(self._fmsgout, r, "\n", label='ui.promptecho')
1383 return r
1396 return r
1384 except EOFError:
1397 except EOFError:
1385 raise error.ResponseExpected()
1398 raise error.ResponseExpected()
1386
1399
1387 @staticmethod
1400 @staticmethod
1388 def extractchoices(prompt):
1401 def extractchoices(prompt):
1389 """Extract prompt message and list of choices from specified prompt.
1402 """Extract prompt message and list of choices from specified prompt.
1390
1403
1391 This returns tuple "(message, choices)", and "choices" is the
1404 This returns tuple "(message, choices)", and "choices" is the
1392 list of tuple "(response character, text without &)".
1405 list of tuple "(response character, text without &)".
1393
1406
1394 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1407 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1395 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1408 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1396 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1409 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1397 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1410 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1398 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1411 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1399 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1412 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1400 """
1413 """
1401
1414
1402 # Sadly, the prompt string may have been built with a filename
1415 # Sadly, the prompt string may have been built with a filename
1403 # containing "$$" so let's try to find the first valid-looking
1416 # containing "$$" so let's try to find the first valid-looking
1404 # prompt to start parsing. Sadly, we also can't rely on
1417 # prompt to start parsing. Sadly, we also can't rely on
1405 # choices containing spaces, ASCII, or basically anything
1418 # choices containing spaces, ASCII, or basically anything
1406 # except an ampersand followed by a character.
1419 # except an ampersand followed by a character.
1407 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1420 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1408 msg = m.group(1)
1421 msg = m.group(1)
1409 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1422 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1410 def choicetuple(s):
1423 def choicetuple(s):
1411 ampidx = s.index('&')
1424 ampidx = s.index('&')
1412 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1425 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1413 return (msg, [choicetuple(s) for s in choices])
1426 return (msg, [choicetuple(s) for s in choices])
1414
1427
1415 def promptchoice(self, prompt, default=0):
1428 def promptchoice(self, prompt, default=0):
1416 """Prompt user with a message, read response, and ensure it matches
1429 """Prompt user with a message, read response, and ensure it matches
1417 one of the provided choices. The prompt is formatted as follows:
1430 one of the provided choices. The prompt is formatted as follows:
1418
1431
1419 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1432 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1420
1433
1421 The index of the choice is returned. Responses are case
1434 The index of the choice is returned. Responses are case
1422 insensitive. If ui is not interactive, the default is
1435 insensitive. If ui is not interactive, the default is
1423 returned.
1436 returned.
1424 """
1437 """
1425
1438
1426 msg, choices = self.extractchoices(prompt)
1439 msg, choices = self.extractchoices(prompt)
1427 resps = [r for r, t in choices]
1440 resps = [r for r, t in choices]
1428 while True:
1441 while True:
1429 r = self.prompt(msg, resps[default])
1442 r = self.prompt(msg, resps[default])
1430 if r.lower() in resps:
1443 if r.lower() in resps:
1431 return resps.index(r.lower())
1444 return resps.index(r.lower())
1432 # TODO: shouldn't it be a warning?
1445 # TODO: shouldn't it be a warning?
1433 self._write(self._fmsgout, _("unrecognized response\n"))
1446 self._write(self._fmsgout, _("unrecognized response\n"))
1434
1447
1435 def getpass(self, prompt=None, default=None):
1448 def getpass(self, prompt=None, default=None):
1436 if not self.interactive():
1449 if not self.interactive():
1437 return default
1450 return default
1438 try:
1451 try:
1439 self._write(self._fmsgerr, prompt or _('password: '),
1452 self._write(self._fmsgerr, prompt or _('password: '),
1440 label='ui.prompt')
1453 label='ui.prompt')
1441 # disable getpass() only if explicitly specified. it's still valid
1454 # disable getpass() only if explicitly specified. it's still valid
1442 # to interact with tty even if fin is not a tty.
1455 # to interact with tty even if fin is not a tty.
1443 with self.timeblockedsection('stdio'):
1456 with self.timeblockedsection('stdio'):
1444 if self.configbool('ui', 'nontty'):
1457 if self.configbool('ui', 'nontty'):
1445 l = self._fin.readline()
1458 l = self._fin.readline()
1446 if not l:
1459 if not l:
1447 raise EOFError
1460 raise EOFError
1448 return l.rstrip('\n')
1461 return l.rstrip('\n')
1449 else:
1462 else:
1450 return getpass.getpass('')
1463 return getpass.getpass('')
1451 except EOFError:
1464 except EOFError:
1452 raise error.ResponseExpected()
1465 raise error.ResponseExpected()
1453
1466
1454 def status(self, *msg, **opts):
1467 def status(self, *msg, **opts):
1455 '''write status message to output (if ui.quiet is False)
1468 '''write status message to output (if ui.quiet is False)
1456
1469
1457 This adds an output label of "ui.status".
1470 This adds an output label of "ui.status".
1458 '''
1471 '''
1459 if not self.quiet:
1472 if not self.quiet:
1460 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1473 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1461 self._write(self._fmsgout, *msg, **opts)
1474 self._write(self._fmsgout, *msg, **opts)
1462
1475
1463 def warn(self, *msg, **opts):
1476 def warn(self, *msg, **opts):
1464 '''write warning message to output (stderr)
1477 '''write warning message to output (stderr)
1465
1478
1466 This adds an output label of "ui.warning".
1479 This adds an output label of "ui.warning".
1467 '''
1480 '''
1468 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1481 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1469 self._write(self._fmsgerr, *msg, **opts)
1482 self._write(self._fmsgerr, *msg, **opts)
1470
1483
1471 def error(self, *msg, **opts):
1484 def error(self, *msg, **opts):
1472 '''write error message to output (stderr)
1485 '''write error message to output (stderr)
1473
1486
1474 This adds an output label of "ui.error".
1487 This adds an output label of "ui.error".
1475 '''
1488 '''
1476 opts[r'label'] = opts.get(r'label', '') + ' ui.error'
1489 opts[r'label'] = opts.get(r'label', '') + ' ui.error'
1477 self._write(self._fmsgerr, *msg, **opts)
1490 self._write(self._fmsgerr, *msg, **opts)
1478
1491
1479 def note(self, *msg, **opts):
1492 def note(self, *msg, **opts):
1480 '''write note to output (if ui.verbose is True)
1493 '''write note to output (if ui.verbose is True)
1481
1494
1482 This adds an output label of "ui.note".
1495 This adds an output label of "ui.note".
1483 '''
1496 '''
1484 if self.verbose:
1497 if self.verbose:
1485 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1498 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1486 self._write(self._fmsgout, *msg, **opts)
1499 self._write(self._fmsgout, *msg, **opts)
1487
1500
1488 def debug(self, *msg, **opts):
1501 def debug(self, *msg, **opts):
1489 '''write debug message to output (if ui.debugflag is True)
1502 '''write debug message to output (if ui.debugflag is True)
1490
1503
1491 This adds an output label of "ui.debug".
1504 This adds an output label of "ui.debug".
1492 '''
1505 '''
1493 if self.debugflag:
1506 if self.debugflag:
1494 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1507 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1495 self._write(self._fmsgout, *msg, **opts)
1508 self._write(self._fmsgout, *msg, **opts)
1496
1509
1497 def edit(self, text, user, extra=None, editform=None, pending=None,
1510 def edit(self, text, user, extra=None, editform=None, pending=None,
1498 repopath=None, action=None):
1511 repopath=None, action=None):
1499 if action is None:
1512 if action is None:
1500 self.develwarn('action is None but will soon be a required '
1513 self.develwarn('action is None but will soon be a required '
1501 'parameter to ui.edit()')
1514 'parameter to ui.edit()')
1502 extra_defaults = {
1515 extra_defaults = {
1503 'prefix': 'editor',
1516 'prefix': 'editor',
1504 'suffix': '.txt',
1517 'suffix': '.txt',
1505 }
1518 }
1506 if extra is not None:
1519 if extra is not None:
1507 if extra.get('suffix') is not None:
1520 if extra.get('suffix') is not None:
1508 self.develwarn('extra.suffix is not None but will soon be '
1521 self.develwarn('extra.suffix is not None but will soon be '
1509 'ignored by ui.edit()')
1522 'ignored by ui.edit()')
1510 extra_defaults.update(extra)
1523 extra_defaults.update(extra)
1511 extra = extra_defaults
1524 extra = extra_defaults
1512
1525
1513 if action == 'diff':
1526 if action == 'diff':
1514 suffix = '.diff'
1527 suffix = '.diff'
1515 elif action:
1528 elif action:
1516 suffix = '.%s.hg.txt' % action
1529 suffix = '.%s.hg.txt' % action
1517 else:
1530 else:
1518 suffix = extra['suffix']
1531 suffix = extra['suffix']
1519
1532
1520 rdir = None
1533 rdir = None
1521 if self.configbool('experimental', 'editortmpinhg'):
1534 if self.configbool('experimental', 'editortmpinhg'):
1522 rdir = repopath
1535 rdir = repopath
1523 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1536 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1524 suffix=suffix,
1537 suffix=suffix,
1525 dir=rdir)
1538 dir=rdir)
1526 try:
1539 try:
1527 f = os.fdopen(fd, r'wb')
1540 f = os.fdopen(fd, r'wb')
1528 f.write(util.tonativeeol(text))
1541 f.write(util.tonativeeol(text))
1529 f.close()
1542 f.close()
1530
1543
1531 environ = {'HGUSER': user}
1544 environ = {'HGUSER': user}
1532 if 'transplant_source' in extra:
1545 if 'transplant_source' in extra:
1533 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1546 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1534 for label in ('intermediate-source', 'source', 'rebase_source'):
1547 for label in ('intermediate-source', 'source', 'rebase_source'):
1535 if label in extra:
1548 if label in extra:
1536 environ.update({'HGREVISION': extra[label]})
1549 environ.update({'HGREVISION': extra[label]})
1537 break
1550 break
1538 if editform:
1551 if editform:
1539 environ.update({'HGEDITFORM': editform})
1552 environ.update({'HGEDITFORM': editform})
1540 if pending:
1553 if pending:
1541 environ.update({'HG_PENDING': pending})
1554 environ.update({'HG_PENDING': pending})
1542
1555
1543 editor = self.geteditor()
1556 editor = self.geteditor()
1544
1557
1545 self.system("%s \"%s\"" % (editor, name),
1558 self.system("%s \"%s\"" % (editor, name),
1546 environ=environ,
1559 environ=environ,
1547 onerr=error.Abort, errprefix=_("edit failed"),
1560 onerr=error.Abort, errprefix=_("edit failed"),
1548 blockedtag='editor')
1561 blockedtag='editor')
1549
1562
1550 f = open(name, r'rb')
1563 f = open(name, r'rb')
1551 t = util.fromnativeeol(f.read())
1564 t = util.fromnativeeol(f.read())
1552 f.close()
1565 f.close()
1553 finally:
1566 finally:
1554 os.unlink(name)
1567 os.unlink(name)
1555
1568
1556 return t
1569 return t
1557
1570
1558 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1571 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1559 blockedtag=None):
1572 blockedtag=None):
1560 '''execute shell command with appropriate output stream. command
1573 '''execute shell command with appropriate output stream. command
1561 output will be redirected if fout is not stdout.
1574 output will be redirected if fout is not stdout.
1562
1575
1563 if command fails and onerr is None, return status, else raise onerr
1576 if command fails and onerr is None, return status, else raise onerr
1564 object as exception.
1577 object as exception.
1565 '''
1578 '''
1566 if blockedtag is None:
1579 if blockedtag is None:
1567 # Long cmds tend to be because of an absolute path on cmd. Keep
1580 # Long cmds tend to be because of an absolute path on cmd. Keep
1568 # the tail end instead
1581 # the tail end instead
1569 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1582 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1570 blockedtag = 'unknown_system_' + cmdsuffix
1583 blockedtag = 'unknown_system_' + cmdsuffix
1571 out = self._fout
1584 out = self._fout
1572 if any(s[1] for s in self._bufferstates):
1585 if any(s[1] for s in self._bufferstates):
1573 out = self
1586 out = self
1574 with self.timeblockedsection(blockedtag):
1587 with self.timeblockedsection(blockedtag):
1575 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1588 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1576 if rc and onerr:
1589 if rc and onerr:
1577 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1590 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1578 procutil.explainexit(rc))
1591 procutil.explainexit(rc))
1579 if errprefix:
1592 if errprefix:
1580 errmsg = '%s: %s' % (errprefix, errmsg)
1593 errmsg = '%s: %s' % (errprefix, errmsg)
1581 raise onerr(errmsg)
1594 raise onerr(errmsg)
1582 return rc
1595 return rc
1583
1596
1584 def _runsystem(self, cmd, environ, cwd, out):
1597 def _runsystem(self, cmd, environ, cwd, out):
1585 """actually execute the given shell command (can be overridden by
1598 """actually execute the given shell command (can be overridden by
1586 extensions like chg)"""
1599 extensions like chg)"""
1587 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1600 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1588
1601
1589 def traceback(self, exc=None, force=False):
1602 def traceback(self, exc=None, force=False):
1590 '''print exception traceback if traceback printing enabled or forced.
1603 '''print exception traceback if traceback printing enabled or forced.
1591 only to call in exception handler. returns true if traceback
1604 only to call in exception handler. returns true if traceback
1592 printed.'''
1605 printed.'''
1593 if self.tracebackflag or force:
1606 if self.tracebackflag or force:
1594 if exc is None:
1607 if exc is None:
1595 exc = sys.exc_info()
1608 exc = sys.exc_info()
1596 cause = getattr(exc[1], 'cause', None)
1609 cause = getattr(exc[1], 'cause', None)
1597
1610
1598 if cause is not None:
1611 if cause is not None:
1599 causetb = traceback.format_tb(cause[2])
1612 causetb = traceback.format_tb(cause[2])
1600 exctb = traceback.format_tb(exc[2])
1613 exctb = traceback.format_tb(exc[2])
1601 exconly = traceback.format_exception_only(cause[0], cause[1])
1614 exconly = traceback.format_exception_only(cause[0], cause[1])
1602
1615
1603 # exclude frame where 'exc' was chained and rethrown from exctb
1616 # exclude frame where 'exc' was chained and rethrown from exctb
1604 self.write_err('Traceback (most recent call last):\n',
1617 self.write_err('Traceback (most recent call last):\n',
1605 ''.join(exctb[:-1]),
1618 ''.join(exctb[:-1]),
1606 ''.join(causetb),
1619 ''.join(causetb),
1607 ''.join(exconly))
1620 ''.join(exconly))
1608 else:
1621 else:
1609 output = traceback.format_exception(exc[0], exc[1], exc[2])
1622 output = traceback.format_exception(exc[0], exc[1], exc[2])
1610 self.write_err(encoding.strtolocal(r''.join(output)))
1623 self.write_err(encoding.strtolocal(r''.join(output)))
1611 return self.tracebackflag or force
1624 return self.tracebackflag or force
1612
1625
1613 def geteditor(self):
1626 def geteditor(self):
1614 '''return editor to use'''
1627 '''return editor to use'''
1615 if pycompat.sysplatform == 'plan9':
1628 if pycompat.sysplatform == 'plan9':
1616 # vi is the MIPS instruction simulator on Plan 9. We
1629 # vi is the MIPS instruction simulator on Plan 9. We
1617 # instead default to E to plumb commit messages to
1630 # instead default to E to plumb commit messages to
1618 # avoid confusion.
1631 # avoid confusion.
1619 editor = 'E'
1632 editor = 'E'
1620 else:
1633 else:
1621 editor = 'vi'
1634 editor = 'vi'
1622 return (encoding.environ.get("HGEDITOR") or
1635 return (encoding.environ.get("HGEDITOR") or
1623 self.config("ui", "editor", editor))
1636 self.config("ui", "editor", editor))
1624
1637
1625 @util.propertycache
1638 @util.propertycache
1626 def _progbar(self):
1639 def _progbar(self):
1627 """setup the progbar singleton to the ui object"""
1640 """setup the progbar singleton to the ui object"""
1628 if (self.quiet or self.debugflag
1641 if (self.quiet or self.debugflag
1629 or self.configbool('progress', 'disable')
1642 or self.configbool('progress', 'disable')
1630 or not progress.shouldprint(self)):
1643 or not progress.shouldprint(self)):
1631 return None
1644 return None
1632 return getprogbar(self)
1645 return getprogbar(self)
1633
1646
1634 def _progclear(self):
1647 def _progclear(self):
1635 """clear progress bar output if any. use it before any output"""
1648 """clear progress bar output if any. use it before any output"""
1636 if not haveprogbar(): # nothing loaded yet
1649 if not haveprogbar(): # nothing loaded yet
1637 return
1650 return
1638 if self._progbar is not None and self._progbar.printed:
1651 if self._progbar is not None and self._progbar.printed:
1639 self._progbar.clear()
1652 self._progbar.clear()
1640
1653
1641 def progress(self, topic, pos, item="", unit="", total=None):
1654 def progress(self, topic, pos, item="", unit="", total=None):
1642 '''show a progress message
1655 '''show a progress message
1643
1656
1644 By default a textual progress bar will be displayed if an operation
1657 By default a textual progress bar will be displayed if an operation
1645 takes too long. 'topic' is the current operation, 'item' is a
1658 takes too long. 'topic' is the current operation, 'item' is a
1646 non-numeric marker of the current position (i.e. the currently
1659 non-numeric marker of the current position (i.e. the currently
1647 in-process file), 'pos' is the current numeric position (i.e.
1660 in-process file), 'pos' is the current numeric position (i.e.
1648 revision, bytes, etc.), unit is a corresponding unit label,
1661 revision, bytes, etc.), unit is a corresponding unit label,
1649 and total is the highest expected pos.
1662 and total is the highest expected pos.
1650
1663
1651 Multiple nested topics may be active at a time.
1664 Multiple nested topics may be active at a time.
1652
1665
1653 All topics should be marked closed by setting pos to None at
1666 All topics should be marked closed by setting pos to None at
1654 termination.
1667 termination.
1655 '''
1668 '''
1656 if self._progbar is not None:
1669 if self._progbar is not None:
1657 self._progbar.progress(topic, pos, item=item, unit=unit,
1670 self._progbar.progress(topic, pos, item=item, unit=unit,
1658 total=total)
1671 total=total)
1659 if pos is None or not self.configbool('progress', 'debug'):
1672 if pos is None or not self.configbool('progress', 'debug'):
1660 return
1673 return
1661
1674
1662 if unit:
1675 if unit:
1663 unit = ' ' + unit
1676 unit = ' ' + unit
1664 if item:
1677 if item:
1665 item = ' ' + item
1678 item = ' ' + item
1666
1679
1667 if total:
1680 if total:
1668 pct = 100.0 * pos / total
1681 pct = 100.0 * pos / total
1669 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1682 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1670 % (topic, item, pos, total, unit, pct))
1683 % (topic, item, pos, total, unit, pct))
1671 else:
1684 else:
1672 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1685 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1673
1686
1674 def makeprogress(self, topic, unit="", total=None):
1687 def makeprogress(self, topic, unit="", total=None):
1675 '''exists only so low-level modules won't need to import scmutil'''
1688 '''exists only so low-level modules won't need to import scmutil'''
1676 return scmutil.progress(self, topic, unit, total)
1689 return scmutil.progress(self, topic, unit, total)
1677
1690
1678 def log(self, service, *msg, **opts):
1691 def log(self, service, *msg, **opts):
1679 '''hook for logging facility extensions
1692 '''hook for logging facility extensions
1680
1693
1681 service should be a readily-identifiable subsystem, which will
1694 service should be a readily-identifiable subsystem, which will
1682 allow filtering.
1695 allow filtering.
1683
1696
1684 *msg should be a newline-terminated format string to log, and
1697 *msg should be a newline-terminated format string to log, and
1685 then any values to %-format into that format string.
1698 then any values to %-format into that format string.
1686
1699
1687 **opts currently has no defined meanings.
1700 **opts currently has no defined meanings.
1688 '''
1701 '''
1689
1702
1690 def label(self, msg, label):
1703 def label(self, msg, label):
1691 '''style msg based on supplied label
1704 '''style msg based on supplied label
1692
1705
1693 If some color mode is enabled, this will add the necessary control
1706 If some color mode is enabled, this will add the necessary control
1694 characters to apply such color. In addition, 'debug' color mode adds
1707 characters to apply such color. In addition, 'debug' color mode adds
1695 markup showing which label affects a piece of text.
1708 markup showing which label affects a piece of text.
1696
1709
1697 ui.write(s, 'label') is equivalent to
1710 ui.write(s, 'label') is equivalent to
1698 ui.write(ui.label(s, 'label')).
1711 ui.write(ui.label(s, 'label')).
1699 '''
1712 '''
1700 if self._colormode is not None:
1713 if self._colormode is not None:
1701 return color.colorlabel(self, msg, label)
1714 return color.colorlabel(self, msg, label)
1702 return msg
1715 return msg
1703
1716
1704 def develwarn(self, msg, stacklevel=1, config=None):
1717 def develwarn(self, msg, stacklevel=1, config=None):
1705 """issue a developer warning message
1718 """issue a developer warning message
1706
1719
1707 Use 'stacklevel' to report the offender some layers further up in the
1720 Use 'stacklevel' to report the offender some layers further up in the
1708 stack.
1721 stack.
1709 """
1722 """
1710 if not self.configbool('devel', 'all-warnings'):
1723 if not self.configbool('devel', 'all-warnings'):
1711 if config is None or not self.configbool('devel', config):
1724 if config is None or not self.configbool('devel', config):
1712 return
1725 return
1713 msg = 'devel-warn: ' + msg
1726 msg = 'devel-warn: ' + msg
1714 stacklevel += 1 # get in develwarn
1727 stacklevel += 1 # get in develwarn
1715 if self.tracebackflag:
1728 if self.tracebackflag:
1716 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1729 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1717 self.log('develwarn', '%s at:\n%s' %
1730 self.log('develwarn', '%s at:\n%s' %
1718 (msg, ''.join(util.getstackframes(stacklevel))))
1731 (msg, ''.join(util.getstackframes(stacklevel))))
1719 else:
1732 else:
1720 curframe = inspect.currentframe()
1733 curframe = inspect.currentframe()
1721 calframe = inspect.getouterframes(curframe, 2)
1734 calframe = inspect.getouterframes(curframe, 2)
1722 fname, lineno, fmsg = calframe[stacklevel][1:4]
1735 fname, lineno, fmsg = calframe[stacklevel][1:4]
1723 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1736 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1724 self.write_err('%s at: %s:%d (%s)\n'
1737 self.write_err('%s at: %s:%d (%s)\n'
1725 % (msg, fname, lineno, fmsg))
1738 % (msg, fname, lineno, fmsg))
1726 self.log('develwarn', '%s at: %s:%d (%s)\n',
1739 self.log('develwarn', '%s at: %s:%d (%s)\n',
1727 msg, fname, lineno, fmsg)
1740 msg, fname, lineno, fmsg)
1728 curframe = calframe = None # avoid cycles
1741 curframe = calframe = None # avoid cycles
1729
1742
1730 def deprecwarn(self, msg, version, stacklevel=2):
1743 def deprecwarn(self, msg, version, stacklevel=2):
1731 """issue a deprecation warning
1744 """issue a deprecation warning
1732
1745
1733 - msg: message explaining what is deprecated and how to upgrade,
1746 - msg: message explaining what is deprecated and how to upgrade,
1734 - version: last version where the API will be supported,
1747 - version: last version where the API will be supported,
1735 """
1748 """
1736 if not (self.configbool('devel', 'all-warnings')
1749 if not (self.configbool('devel', 'all-warnings')
1737 or self.configbool('devel', 'deprec-warn')):
1750 or self.configbool('devel', 'deprec-warn')):
1738 return
1751 return
1739 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1752 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1740 " update your code.)") % version
1753 " update your code.)") % version
1741 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1754 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1742
1755
1743 def exportableenviron(self):
1756 def exportableenviron(self):
1744 """The environment variables that are safe to export, e.g. through
1757 """The environment variables that are safe to export, e.g. through
1745 hgweb.
1758 hgweb.
1746 """
1759 """
1747 return self._exportableenviron
1760 return self._exportableenviron
1748
1761
1749 @contextlib.contextmanager
1762 @contextlib.contextmanager
1750 def configoverride(self, overrides, source=""):
1763 def configoverride(self, overrides, source=""):
1751 """Context manager for temporary config overrides
1764 """Context manager for temporary config overrides
1752 `overrides` must be a dict of the following structure:
1765 `overrides` must be a dict of the following structure:
1753 {(section, name) : value}"""
1766 {(section, name) : value}"""
1754 backups = {}
1767 backups = {}
1755 try:
1768 try:
1756 for (section, name), value in overrides.items():
1769 for (section, name), value in overrides.items():
1757 backups[(section, name)] = self.backupconfig(section, name)
1770 backups[(section, name)] = self.backupconfig(section, name)
1758 self.setconfig(section, name, value, source)
1771 self.setconfig(section, name, value, source)
1759 yield
1772 yield
1760 finally:
1773 finally:
1761 for __, backup in backups.items():
1774 for __, backup in backups.items():
1762 self.restoreconfig(backup)
1775 self.restoreconfig(backup)
1763 # just restoring ui.quiet config to the previous value is not enough
1776 # just restoring ui.quiet config to the previous value is not enough
1764 # as it does not update ui.quiet class member
1777 # as it does not update ui.quiet class member
1765 if ('ui', 'quiet') in overrides:
1778 if ('ui', 'quiet') in overrides:
1766 self.fixconfig(section='ui')
1779 self.fixconfig(section='ui')
1767
1780
1768 class paths(dict):
1781 class paths(dict):
1769 """Represents a collection of paths and their configs.
1782 """Represents a collection of paths and their configs.
1770
1783
1771 Data is initially derived from ui instances and the config files they have
1784 Data is initially derived from ui instances and the config files they have
1772 loaded.
1785 loaded.
1773 """
1786 """
1774 def __init__(self, ui):
1787 def __init__(self, ui):
1775 dict.__init__(self)
1788 dict.__init__(self)
1776
1789
1777 for name, loc in ui.configitems('paths', ignoresub=True):
1790 for name, loc in ui.configitems('paths', ignoresub=True):
1778 # No location is the same as not existing.
1791 # No location is the same as not existing.
1779 if not loc:
1792 if not loc:
1780 continue
1793 continue
1781 loc, sub = ui.configsuboptions('paths', name)
1794 loc, sub = ui.configsuboptions('paths', name)
1782 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1795 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1783
1796
1784 def getpath(self, name, default=None):
1797 def getpath(self, name, default=None):
1785 """Return a ``path`` from a string, falling back to default.
1798 """Return a ``path`` from a string, falling back to default.
1786
1799
1787 ``name`` can be a named path or locations. Locations are filesystem
1800 ``name`` can be a named path or locations. Locations are filesystem
1788 paths or URIs.
1801 paths or URIs.
1789
1802
1790 Returns None if ``name`` is not a registered path, a URI, or a local
1803 Returns None if ``name`` is not a registered path, a URI, or a local
1791 path to a repo.
1804 path to a repo.
1792 """
1805 """
1793 # Only fall back to default if no path was requested.
1806 # Only fall back to default if no path was requested.
1794 if name is None:
1807 if name is None:
1795 if not default:
1808 if not default:
1796 default = ()
1809 default = ()
1797 elif not isinstance(default, (tuple, list)):
1810 elif not isinstance(default, (tuple, list)):
1798 default = (default,)
1811 default = (default,)
1799 for k in default:
1812 for k in default:
1800 try:
1813 try:
1801 return self[k]
1814 return self[k]
1802 except KeyError:
1815 except KeyError:
1803 continue
1816 continue
1804 return None
1817 return None
1805
1818
1806 # Most likely empty string.
1819 # Most likely empty string.
1807 # This may need to raise in the future.
1820 # This may need to raise in the future.
1808 if not name:
1821 if not name:
1809 return None
1822 return None
1810
1823
1811 try:
1824 try:
1812 return self[name]
1825 return self[name]
1813 except KeyError:
1826 except KeyError:
1814 # Try to resolve as a local path or URI.
1827 # Try to resolve as a local path or URI.
1815 try:
1828 try:
1816 # We don't pass sub-options in, so no need to pass ui instance.
1829 # We don't pass sub-options in, so no need to pass ui instance.
1817 return path(None, None, rawloc=name)
1830 return path(None, None, rawloc=name)
1818 except ValueError:
1831 except ValueError:
1819 raise error.RepoError(_('repository %s does not exist') %
1832 raise error.RepoError(_('repository %s does not exist') %
1820 name)
1833 name)
1821
1834
1822 _pathsuboptions = {}
1835 _pathsuboptions = {}
1823
1836
1824 def pathsuboption(option, attr):
1837 def pathsuboption(option, attr):
1825 """Decorator used to declare a path sub-option.
1838 """Decorator used to declare a path sub-option.
1826
1839
1827 Arguments are the sub-option name and the attribute it should set on
1840 Arguments are the sub-option name and the attribute it should set on
1828 ``path`` instances.
1841 ``path`` instances.
1829
1842
1830 The decorated function will receive as arguments a ``ui`` instance,
1843 The decorated function will receive as arguments a ``ui`` instance,
1831 ``path`` instance, and the string value of this option from the config.
1844 ``path`` instance, and the string value of this option from the config.
1832 The function should return the value that will be set on the ``path``
1845 The function should return the value that will be set on the ``path``
1833 instance.
1846 instance.
1834
1847
1835 This decorator can be used to perform additional verification of
1848 This decorator can be used to perform additional verification of
1836 sub-options and to change the type of sub-options.
1849 sub-options and to change the type of sub-options.
1837 """
1850 """
1838 def register(func):
1851 def register(func):
1839 _pathsuboptions[option] = (attr, func)
1852 _pathsuboptions[option] = (attr, func)
1840 return func
1853 return func
1841 return register
1854 return register
1842
1855
1843 @pathsuboption('pushurl', 'pushloc')
1856 @pathsuboption('pushurl', 'pushloc')
1844 def pushurlpathoption(ui, path, value):
1857 def pushurlpathoption(ui, path, value):
1845 u = util.url(value)
1858 u = util.url(value)
1846 # Actually require a URL.
1859 # Actually require a URL.
1847 if not u.scheme:
1860 if not u.scheme:
1848 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1861 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1849 return None
1862 return None
1850
1863
1851 # Don't support the #foo syntax in the push URL to declare branch to
1864 # Don't support the #foo syntax in the push URL to declare branch to
1852 # push.
1865 # push.
1853 if u.fragment:
1866 if u.fragment:
1854 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1867 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1855 'ignoring)\n') % path.name)
1868 'ignoring)\n') % path.name)
1856 u.fragment = None
1869 u.fragment = None
1857
1870
1858 return bytes(u)
1871 return bytes(u)
1859
1872
1860 @pathsuboption('pushrev', 'pushrev')
1873 @pathsuboption('pushrev', 'pushrev')
1861 def pushrevpathoption(ui, path, value):
1874 def pushrevpathoption(ui, path, value):
1862 return value
1875 return value
1863
1876
1864 class path(object):
1877 class path(object):
1865 """Represents an individual path and its configuration."""
1878 """Represents an individual path and its configuration."""
1866
1879
1867 def __init__(self, ui, name, rawloc=None, suboptions=None):
1880 def __init__(self, ui, name, rawloc=None, suboptions=None):
1868 """Construct a path from its config options.
1881 """Construct a path from its config options.
1869
1882
1870 ``ui`` is the ``ui`` instance the path is coming from.
1883 ``ui`` is the ``ui`` instance the path is coming from.
1871 ``name`` is the symbolic name of the path.
1884 ``name`` is the symbolic name of the path.
1872 ``rawloc`` is the raw location, as defined in the config.
1885 ``rawloc`` is the raw location, as defined in the config.
1873 ``pushloc`` is the raw locations pushes should be made to.
1886 ``pushloc`` is the raw locations pushes should be made to.
1874
1887
1875 If ``name`` is not defined, we require that the location be a) a local
1888 If ``name`` is not defined, we require that the location be a) a local
1876 filesystem path with a .hg directory or b) a URL. If not,
1889 filesystem path with a .hg directory or b) a URL. If not,
1877 ``ValueError`` is raised.
1890 ``ValueError`` is raised.
1878 """
1891 """
1879 if not rawloc:
1892 if not rawloc:
1880 raise ValueError('rawloc must be defined')
1893 raise ValueError('rawloc must be defined')
1881
1894
1882 # Locations may define branches via syntax <base>#<branch>.
1895 # Locations may define branches via syntax <base>#<branch>.
1883 u = util.url(rawloc)
1896 u = util.url(rawloc)
1884 branch = None
1897 branch = None
1885 if u.fragment:
1898 if u.fragment:
1886 branch = u.fragment
1899 branch = u.fragment
1887 u.fragment = None
1900 u.fragment = None
1888
1901
1889 self.url = u
1902 self.url = u
1890 self.branch = branch
1903 self.branch = branch
1891
1904
1892 self.name = name
1905 self.name = name
1893 self.rawloc = rawloc
1906 self.rawloc = rawloc
1894 self.loc = '%s' % u
1907 self.loc = '%s' % u
1895
1908
1896 # When given a raw location but not a symbolic name, validate the
1909 # When given a raw location but not a symbolic name, validate the
1897 # location is valid.
1910 # location is valid.
1898 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1911 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1899 raise ValueError('location is not a URL or path to a local '
1912 raise ValueError('location is not a URL or path to a local '
1900 'repo: %s' % rawloc)
1913 'repo: %s' % rawloc)
1901
1914
1902 suboptions = suboptions or {}
1915 suboptions = suboptions or {}
1903
1916
1904 # Now process the sub-options. If a sub-option is registered, its
1917 # Now process the sub-options. If a sub-option is registered, its
1905 # attribute will always be present. The value will be None if there
1918 # attribute will always be present. The value will be None if there
1906 # was no valid sub-option.
1919 # was no valid sub-option.
1907 for suboption, (attr, func) in _pathsuboptions.iteritems():
1920 for suboption, (attr, func) in _pathsuboptions.iteritems():
1908 if suboption not in suboptions:
1921 if suboption not in suboptions:
1909 setattr(self, attr, None)
1922 setattr(self, attr, None)
1910 continue
1923 continue
1911
1924
1912 value = func(ui, self, suboptions[suboption])
1925 value = func(ui, self, suboptions[suboption])
1913 setattr(self, attr, value)
1926 setattr(self, attr, value)
1914
1927
1915 def _isvalidlocalpath(self, path):
1928 def _isvalidlocalpath(self, path):
1916 """Returns True if the given path is a potentially valid repository.
1929 """Returns True if the given path is a potentially valid repository.
1917 This is its own function so that extensions can change the definition of
1930 This is its own function so that extensions can change the definition of
1918 'valid' in this case (like when pulling from a git repo into a hg
1931 'valid' in this case (like when pulling from a git repo into a hg
1919 one)."""
1932 one)."""
1920 return os.path.isdir(os.path.join(path, '.hg'))
1933 return os.path.isdir(os.path.join(path, '.hg'))
1921
1934
1922 @property
1935 @property
1923 def suboptions(self):
1936 def suboptions(self):
1924 """Return sub-options and their values for this path.
1937 """Return sub-options and their values for this path.
1925
1938
1926 This is intended to be used for presentation purposes.
1939 This is intended to be used for presentation purposes.
1927 """
1940 """
1928 d = {}
1941 d = {}
1929 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1942 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1930 value = getattr(self, attr)
1943 value = getattr(self, attr)
1931 if value is not None:
1944 if value is not None:
1932 d[subopt] = value
1945 d[subopt] = value
1933 return d
1946 return d
1934
1947
1935 # we instantiate one globally shared progress bar to avoid
1948 # we instantiate one globally shared progress bar to avoid
1936 # competing progress bars when multiple UI objects get created
1949 # competing progress bars when multiple UI objects get created
1937 _progresssingleton = None
1950 _progresssingleton = None
1938
1951
1939 def getprogbar(ui):
1952 def getprogbar(ui):
1940 global _progresssingleton
1953 global _progresssingleton
1941 if _progresssingleton is None:
1954 if _progresssingleton is None:
1942 # passing 'ui' object to the singleton is fishy,
1955 # passing 'ui' object to the singleton is fishy,
1943 # this is how the extension used to work but feel free to rework it.
1956 # this is how the extension used to work but feel free to rework it.
1944 _progresssingleton = progress.progbar(ui)
1957 _progresssingleton = progress.progbar(ui)
1945 return _progresssingleton
1958 return _progresssingleton
1946
1959
1947 def haveprogbar():
1960 def haveprogbar():
1948 return _progresssingleton is not None
1961 return _progresssingleton is not None
1949
1962
1950 def _selectmsgdests(ui):
1963 def _selectmsgdests(ui):
1951 name = ui.config(b'ui', b'message-output')
1964 name = ui.config(b'ui', b'message-output')
1952 if name == b'stdio':
1965 if name == b'stdio':
1953 return ui.fout, ui.ferr
1966 return ui.fout, ui.ferr
1954 if name == b'stderr':
1967 if name == b'stderr':
1955 return ui.ferr, ui.ferr
1968 return ui.ferr, ui.ferr
1956 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
1969 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
General Comments 0
You need to be logged in to leave comments. Login now